clojure.algo.monad strange m-plus behaviour with parser-m - why is second m-plus evaluated?

Posted by Mark Fisher on Stack Overflow See other posts from Stack Overflow or by Mark Fisher
Published on 2013-10-21T21:44:58Z Indexed on 2013/10/21 21:53 UTC
Read the original article Hit count: 288

Filed under:
|
|

I'm getting unexpected behaviour in some monads I'm writing. I've created a parser-m monad with

(def parser-m (state-t maybe-m))

which is pretty much the example given everywhere (here, here and here)

I'm using m-plus to act a kind of fall-through query mechanism, in my case, it first reads values from a cache (database), if that returns nil, the next method is to read from "live" (a REST call).

However, the second value in the m-plus list is always called, even though its value is disgarded (if the cache hit was good) and the final return is that of the first monadic function.

Here's a cutdown version of the issue i'm seeing, and some solutions I found, but I don't know why.

My questions are:

  1. Is this expected behaviour or a bug in m-plus? i.e. will the 2nd method in a m-plus list always be evaluated if the first item returns a value?
  2. Minor in comparison to the above, but if i remove the call _ (fetch-state) from checker, when i evaluate that method, it prints out the messages for the functions the m-plus is calling (when i don't think it should). Is this also a bug?

Here's a cut-down version of the code in question highlighting the problem. It simply checks key/value pairs passed in are same as the initial state values, and updates the state to mark what it actually ran.

(ns monods.monad-test
  (:require [clojure.algo.monads :refer :all]))

(def parser-m (state-t maybe-m))

(defn check-k-v [k v]
  (println "calling with k,v:" k v)
  (domonad parser-m
           [kv (fetch-val k)
            _ (do (println "k v kv (= kv v)" k v kv (= kv v)) (m-result 0))
            :when (= kv v)
            _ (do (println "passed") (m-result 0))
            _ (update-val :ran #(conj % (str "[" k " = " v "]")))
            ]
           [k v]))

(defn filler []
  (println "filler called")
  (domonad parser-m
           [_ (fetch-state)
            _ (do (println "filling") (m-result 0))
            :when nil]
           nil))

(def checker
  (domonad parser-m
           [_ (fetch-state)
            result (m-plus
                    ;; (filler) ;; intitially commented out deliberately
                    (check-k-v :a 1)
                    (check-k-v :b 2)
                    (check-k-v :c 3))]
           result))

(checker {:a 1 :b 2 :c 3 :ran []})

When I run this as is, the output is:

> (checker {:a 1 :b 2 :c 3 :ran []})
calling with k,v: :a 1
calling with k,v: :b 2
calling with k,v: :c 3
k v kv (= kv v) :a 1 1 true
passed
k v kv (= kv v) :b 2 2 true
passed
[[:a 1] {:a 1, :b 2, :c 3, :ran ["[:a = 1]"]}]

I don't expect the line k v kv (= kv v) :b 2 2 true to show at all. The first function to m-plus (as seen in the final output) is what is returned from it.

Now, I've found if I pass a filler into m-plus that does nothing (i.e. uncomment the (filler) line) then the output is correct, the :b value isn't evaluated.

If I don't have the filler method, and make the first method test fail (i.e. change it to (check-k-v :a 2) then again everything is good, I don't get a call to check :c, only a and b are tested.

From my understanding of what the state-t maybe-m transformation is giving me, then the m-plus function should look like:

(defn m-plus
  [left right]
  (fn [state]
    (if-let [result (left state)]
      result
      (right state))))

which would mean that right isn't called unless left returns nil/false.

I'd be interested to know if my understanding is correct or not, and why I have to put the filler method in to stop the extra evaluation (whose effects I don't want to happen).

Apologies for the long winded post!

© Stack Overflow or respective owner

Related posts about clojure

Related posts about monads