论文翻译:µKanren —— 关系编程的最小化函数式核心
Source: http://webyrd.net/scheme-2013/papers/HemannMuKanren2013.pdf
摘要
本论文提出了 µKanren ,关系编程/逻辑编程中的 miniKanren 家族的最小语言。 其实现不到 40 行 Scheme 代码。 我们在对最小化 miniKanren 的需求的驱动,迭代开发出了一套完成的搜索策略。 最终我们展示了……
关键词:miniKanren 、关系编程(renational programming)、逻辑编程(logical programming)、Scheme
正文
介绍
miniKanren 是关系(逻辑)编程语言中这对同名家族的主要成员。 其中很多关键设计决定是在 Prolog 以及其他知名的第五代编程语言(5GL)影响下的反应。 其中一个不同是,当典型的 Prolog 实现需要几千行 C 语言代码时,一个 miniKanren 解释器仅仅需要不到一千行。 虽然 miniKanren 语言存在很多的变体以及功能集(参见 https://miniKanren.org ),最早被发布的实现仅仅包含了 265 行 Scheme 代码。 在这短短的百来行代码中,展现了可以和 Prolog 的真子集的实现相比的表现力。
不过,我们认为,在那 265 行的 miniKanren 实现中还藏着一个小而美的关系编程语言亟待发掘。 我们相信 µKanren 就是那个语言。 通过最小化那些紧密关联的算子直到仅含关系编程的必需功能的程度,并将其大多数界面直接放在用户的控制下,我们极简化了其实现并且展示了剩余需建的角色以及相互关系。 我们使整个实现函数式并且避免了宏、……
µKanren 语言
在这里,我们简短地介绍 µKanren 程序的语法,尤其是其关注的领域和早期的 miniKanren 语言不同的地方。 读者们……
> (define empty-state '(() . 0))
> ((call/fresh (λ (q) (≡ q 5))) empty-state)
((((#(0) . 5)) . 1))设计哲学
µKanren 实现
有限深度优先搜索
无线流
数据交织(Interleaving)
用户层函数
重塑 miniKanren 的控制符
结论及未来的工作
在本论文中,我们介绍了 µKanren ,一个羽量级的纯关系(逻辑)编程语言的实现。 µKanren 是 miniKanren 家族的最小集。 其内核是纯函数的,没有任何宏,仅包括了 14 个定义以及 39 行代码。 因此,我们相信该实现相比其他的 miniKanren 家族,即更易于理解,也更开箱即用。
由于它旨在成为一个最简练的实现方式,因此某些功能不可避免地会被舍弃。……
致谢
我们要向 Will Byrd 以及 Will Ness 因其提供的灵感表示感谢。 我们同时也要感谢 Adam Foltzer 以及 Andre Kulenschimidt ,他们在早期提出了评论以及建议。 我们在这里尤其要感谢 Chung-chieh Shan 、 Jeremy Siek 、 Cameron Swords 以及 Sam Tobin-Hochstadt ,因为他们协助阐明本文的表述方式。
附录
Glossary
| 原文 | 翻译 | 含义 |
|---|
A
(define (var c) (vector c))
(define (var? x) (vector? x))
(define (var==? x1 x2) (== (vector-ref x1 0) (vector-ref x2 0)))
(define (walk u s) (
(let ()) ;; ???
(if pr (walk (cdr ps) s) u)))
(define (ext-s x v s) '((,x . ,y) . ,s))
;; TBD额外补充
生词
| 原文 | 翻译 | 原文 | 翻译 |
|---|---|---|---|
| critical | 緊要的,關鍵性的,危急的;批評的,批判的,評論性的 | eponymous | 同名的 |
| though | 雖然,儘管, 可是,不過,然而, 可是,不過,然而 | illuminated | |
| comprises | 包含 | directly portable | 直接便携的(無需複雜設定或安裝,開箱即用、可隨身攜帶、移動方便) |
| bare-bones | 梗概 | interrelationships |
Elixir 实现
defmodule Logic.Core do
@moduledoc "附带 occurs-check 以及 fair-search 的 microKanren 实现。"
# --- 数据结构 ---
defmodule Var do
@moduledoc "逻辑变量。"
@type t :: %__MODULE__{id: integer()}
@type maybe_term :: t() | term()
defstruct [:id]
def var?(%__MODULE__{}), do: true
def var?(_), do: false
# 为了让日志更好看,实现 String.Chars 协议
defimpl String.Chars do
def to_string(%{id: id}), do: "?#{id}"
end
end
defmodule State do
@moduledoc """
状态结构体。
* `subst`: 替换表
* `counter`: 用于生成新逻辑变量的计数器
"""
@type substitution :: %{Var.t() => term()}
@type t :: %__MODULE__{
subst: substitution(),
counter: non_neg_integer(),
}
defstruct subst: %{}, counter: 0
end
# --- 核心操作 ---
def walk(%Var{} = u, subst) do
case Map.fetch(subst, u) do
{:ok, v} -> walk(v, subst)
:error -> u
end
end
def walk(u, _), do: u
@spec unify(Var.maybe_term(), Var.maybe_term(), State.t()) ::
nil | State.t()
def unify(u, v, %State{subst: s} = state) do
result = unify_terms(u, v, s)
case result do
nil ->
nil
new_subst ->
%{state | subst: new_subst}
end
end
defp unify_terms(u, v, s) do
u = walk(u, s)
v = walk(v, s)
cond do
u == v ->
s
Var.var?(u) ->
ext_s(u, v, s)
Var.var?(v) ->
ext_s(v, u, s)
is_list(u) and is_list(v) ->
unify_lists(u, v, s)
is_tuple(u) and is_tuple(v) and tuple_size(u) == tuple_size(v) ->
unify_terms(Tuple.to_list(u), Tuple.to_list(v), s)
true ->
nil
end
end
defp unify_lists([], [], s), do: s
defp unify_lists([u | us], [v | vs], s) do
case unify_terms(u, v, s) do
nil -> nil
s_prime -> unify_terms(us, vs, s_prime)
end
end
defp unify_lists(_, _, _), do: nil
defp ext_s(u, v, s) do
if occurs?(u, v, s) do
nil
else
Map.put(s, u, v)
end
end
defp occurs?(x, v, s) do
v = walk(v, s)
cond do
x == v ->
true
Var.var?(v) ->
false
is_list(v) ->
occurs_list?(x, v, s)
is_tuple(v) ->
v
|> Tuple.to_list()
|> Enum.any?(fn elem -> occurs?(x, elem, s) end)
true ->
false
end
end
defp occurs_list?(_x, [], _s), do: false
defp occurs_list?(x, [h | t], s) do
occurs?(x, h, s) or occurs?(x, t, s)
end
defp occurs_list?(_x, _non_list_tail, _s), do: false
# --- 一些搜索策略之类的 ---
@type stream :: [] | {:mature, State.t(), stream()} | {:immature, (-> stream())}
@type goal :: (State.t() -> stream())
@spec mzero :: stream()
def mzero, do: []
@spec unit(State.t()) :: stream()
def unit(%State{} = state), do: {:mature, state, []}
@spec mplus(stream(), stream()) :: stream()
def mplus([], s2), do: s2
def mplus({:immature, f}, s2), do: {:immature, fn -> mplus(s2, f.()) end}
def mplus({:mature, h, t}, s2), do: {:mature, h, mplus(t, s2)}
@spec bind(stream(), goal()) :: stream()
def bind([], _g), do: []
def bind({:immature, f}, g), do: {:immature, fn -> bind(f.(), g) end}
def bind({:mature, h, t}, g), do: mplus(g.(h), bind(t, g))
# --- Goals (Constructors) ---
@spec eq(Var.maybe_term(), Var.maybe_term()) :: goal()
def eq(u, v) do
fn %State{} = state ->
case unify(u, v, state) do
nil -> mzero()
new_state -> unit(new_state)
end
end
end
@spec conj(goal(), goal()) :: goal()
def conj(g1, g2) do
fn %State{} = state -> bind(g1.(state), g2) end
end
@spec disj(goal(), goal()) :: goal()
def disj(g1, g2) do
fn %State{} = state -> mplus(g1.(state), g2.(state)) end
end
# 负责将 goal 的求值推迟
@spec delay((-> goal())) :: goal()
def delay(goal_fun) do
fn %State{} = state -> {:immature, fn -> goal_fun.().(state) end} end
end
# 把流推进到 mature 或 [] 为止
defp pull([]), do: []
defp pull({:immature, f}), do: pull(f.())
defp pull({:mature, _, _} = s), do: s
def take(_stream, 0), do: []
def take(stream, n) do
case pull(stream) do
[] -> []
{:mature, h, t} -> [h | take(t, n - 1)]
end
end
def take_all(stream) do
case pull(stream) do
[] -> []
{:mature, h, t} -> [h | take_all(t)]
end
end
# call_fresh 负责引入逻辑变量
def call_fresh(f) do
fn %State{counter: c} = s ->
# 1. 创建新变量
v = %Var{id: c}
# 2. 获取用户闭包中的 Goal (f.(v) 返回的是 Goal)
goal = f.(v)
# 3. 更新计数器
new_state = %{s | counter: c + 1}
# 4. === 关键修正 ===
# 必须在这里调用 goal 并传入 state,才能返回 Stream
goal.(new_state)
end
end
# --- Toolkit ---
# 深度 walk
def walk_star(v, s) do
v = walk(v, s)
cond do
Var.var?(v) ->
v
is_list(v) ->
walk_star_list(v, s)
is_tuple(v) ->
v |> Tuple.to_list() |> Enum.map(&walk_star(&1, s)) |> List.to_tuple()
true ->
v
end
end
defp walk_star_list([], _s), do: []
defp walk_star_list([h | t], s), do: [walk_star(h, s) | walk_star(t, s)]
# 把变量重命名成 _0, _1 ...
defp reify_s(v, s) do
v = walk(v, s)
cond do
Var.var?(v) -> Map.put(s, v, :"_#{map_size(s)}")
is_list(v) -> reify_s_list(v, s)
is_tuple(v) -> v |> Tuple.to_list() |> Enum.reduce(s, &reify_s/2)
true -> s
end
end
defp reify_s_list([], s), do: s
defp reify_s_list([h | t], s), do: reify_s(t, reify_s(h, s))
def reify(var, %State{subst: s}) do
walked = walk_star(var, s)
walk_star(walked, reify_s(walked, %{}))
end
# run:引入查询变量 q,跑 n 个答案
def run(n, f) do
call_fresh(f).(%State{})
|> take(n)
|> Enum.map(&reify(%Var{id: 0}, &1))
end
def run_all(f) do
call_fresh(f).(%State{}) |> take_all() |> Enum.map(&reify(%Var{id: 0}, &1))
end
end