自定义 `do`/`let` 语法的想法(没有宏)?

Ideas for custom `do`/`let` syntax (without macros)?

我刚刚构建了一个状态 monad "object",并且希望能够将步骤绑定到变量,并且 link 进行连续计算,就好像 bind/>>=,比如 haskell 如何与 monad 一起工作,以及 clojure 的各种 monad 库如何工作。这是我的单子:

(def state-m
  (let [pure (fn [x] (fn [s] [x s]))
        bind (fn [mv mf] (fn [s] ; mv :: s -> (a, s) || mf :: a -> (s -> (b, s))
                           (let [[a s'] (mv s)]
                             ((mf a) s'))))

        then     (fn [mv mf] (bind mv (fn [_] mf))) ; eg `>>`
        get-s    (fn [s] [s s])
        put-s    (fn [s] (fn [_] [nil s]))
        update-s (fn [f] (fn [s] [nil (f s)]))] ; f :: s->s

    {:pure     pure
     :bind     bind
     :then     then
     :get-s    get-s
     :put-s    put-s
     :update-s update-s}))

这是一个使用它的例子:

(let [pure     (:pure state-m)
      >>=      (:bind state-m)
      >>       (:then state-m)
      get-s    (:get-s state-m)
      put-s    (:put-s state-m)
      update-s (:update-s state-m)]
  (and (= [:v :s] ((pure :v) :s))
       (= [5 :state] ((>>= (pure 3)
                           (fn [x] (pure (+ 2 x))))
                      :state))
       (= [3 3] ((>>= get-s (fn [n] (pure n))) 3))
       (= [4 5] ((>>= get-s (fn [n] (>> (put-s (+ n 1))
                                        (pure n)))) 4))
       (= [4 8] ((>>= get-s (fn [n] (>> (update-s (partial * 2))
                                        (pure n)))) 4))))

有点冗长。我现在不介意,但我 想要能够使用 do 语法,例如:

(my-do [a (get-s)
        _ (put-s 33)
        b (* 2 a)
        _ (put-s 44)
        c (* a b)]
  c)

甚至是某种线程函数(虽然我知道 clj 的线程是宏),例如:

(my-> (pure 1)
      (>>= (fn [a] ...))
      (>>= (fn [b] ...))
      (>> (* a b))) ; notice non-local bindings `a` and `b` are available here

我可以查看 clojure 中现有的 monad 库,了解如何使用宏完成此操作,这很好。我很好奇如何在不使用宏的情况下实现这一点,即使这只是一个试验如何完成的练习。

我知道 clojure 中有 bindings,但没用过。我如何使用这个或其他适当的结构来实现 doish 语法?

如果没有宏,这两种样式都不可能。宏是 构造,用于在任何 lisp 中引入新的语法结构,所以如果你想要一个新的语法宏是一种方式。

与作为抽象方法的函数相比,宏的唯一额外目的是延迟求值。如果您要对所有内容使用函数,则需要更改契约,以便用户将函数作为参数传递,而宏会延迟表达式的计算。这里有一个重制的例子 if:

(defn if* [p a c] 
  (if p (a) (c)))

(if* true
     (fn [] 1)
     (fn [] 2)) ; ==> 1

如果你能处理更多的样板文件,你永远不需要宏,但从长远来看运行我很感激我所有的论点都不必是thunks。

经验法则是尝试使用函数进行抽象,但是在没有太多模糊的情况下这是不可能的,使用宏将更简单的语法转换为样板文件是拥有它们的语言的最重要特征。您不像 Java 那样依赖 Clojure 中语言的新版本,因为大多数功能都可以使用更多样板和宏来简化语法,因此它与新语言功能无法区分。