论文翻译:µKanren —— 关系编程的最小化函数式核心

Work in Progress (15%)

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