Search code examples
clojuremacrosmonadsstate-monad

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


I just built a state monad "object", and would like to be able to bind steps to variables, and link up successive computations, as if with bind/>>=, like how haskell works with monads, and like how clojure's various monad libs work. Here's my 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}))

Here's an example of using it:

(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))))

It's a little verbose. I don't mind right now, but I would like to be able to use do syntax, eg:

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

Or even some thready kind of function (although I know clj's threads are macros), eg:

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

I can look at existing monad libs in clojure for how to accomplish this with macros, and that's fine. I'm curious how this could be achieved without the use of macros, even if just an exercise to experiment with how it could be done.

I am aware of bindings in clojure, but haven't used them. How could I use this, or other appropriate constructs to achieve doish syntax?


Solution

  • Neither of those two styles are possible without a macro. Macros are the construct for introducing new syntactic structures in any lisp, so if you want a new syntax macros are the way to go.