monad.scm 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. (module (arguile monad)
  2. #:export (;; Monads.
  3. monad monad? monad-bind monad-return
  4. ;; Syntax.
  5. >>= return w/monad mlet mlet* mdo mwhen munless
  6. lift0 lift1 lift2 lift3 lift4 lift5 lift6 lift7 lift
  7. listm foldm mapm seq anym
  8. ;; Concrete monads.
  9. ident-monad state-monad
  10. state-return state-bind curr-state curr-state!
  11. state-push state-pop run-w/state))
  12. (use ((system syntax)
  13. #:select (syntax-local-binding))
  14. (arguile base) (arguile guile) (arguile data) (arguile loop)
  15. (arguile generic)
  16. (ice-9 match)
  17. (srfi srfi-26))
  18. (data monad (bind return)
  19. #:init (mke-monad bind return)) ; TODO: Add 'plus' and 'zero'
  20. (mac monad (bind return)
  21. ((_ name (bind b) (return r))
  22. (let-syn data (syn (+ '% (dat #'name)) #'name)
  23. #'(do
  24. ;; The data type, for use at run time.
  25. (def data (mke-monad b r))
  26. (mac name (%bind %return)
  27. ;; An "inlined record", for use at expansion time. The goal is
  28. ;; to allow 'bind' and 'return' to be resolved at expansion time
  29. ((_ %bind) #'b)
  30. ((_ %return) #'r)
  31. ((_) #'rtd))))))
  32. (syn-param >>=
  33. (fn (s)
  34. (syn-violation '>>= ">>= (bind) used outside of 'w/monad'" s)))
  35. (syn-param return
  36. (fn (s)
  37. (syn-violation 'return "return used outside of 'w/monad'" s)))
  38. (mac bind-syn
  39. "Return a macro transformer that handles the expansion of '>>=' expressions
  40. using BIND as the binary bind operator.
  41. This macro exists to allow the expansion of n-ary '>>=' expressions, even
  42. though BIND is simply binary, as in:
  43. (w/monad state-monad
  44. (>>= (return 1)
  45. (lift 1+ state-monad)
  46. (lift 1+ state-monad))) "
  47. ((_ bind)
  48. #'(fn (stx)
  49. (def (expand body)
  50. (syntax-case body ()
  51. ((_ mval mproc)
  52. #'(bind mval mproc))
  53. ((x mval mproc0 mprocs (... ...))
  54. (expand #'(>>= (>>= mval mproc0)
  55. mprocs (... ...))))))
  56. (expand stx))))
  57. (mac w/monad
  58. "Evaluate BODY in the context of MONAD, and return its result."
  59. ((_ monad body ...)
  60. (if (eq? 'macro (syntax-local-binding #'monad))
  61. ;; Expansion time
  62. #'(w/syn-params ((>>= (bind-syn (monad %bind)))
  63. (return (identifier-syntax (monad %return))))
  64. body ...)
  65. ;; Run time
  66. #'(w/syn-params ((>>= (bind-syn
  67. (monad-bind monad)))
  68. (return (identifier-syntax
  69. (monad-return monad))))
  70. body ...))))
  71. (mac mlet* (->)
  72. "Bind the given monadic vals MVAL to the given variables VAR. When the
  73. form is (VAR -> VAL), bind VAR to the non-monadic value VAL in the same way as
  74. 'let'."
  75. ((_ monad () body ...)
  76. #'(w/monad monad body ...))
  77. ((_ monad ((var mval) rest ...) body ...)
  78. #'(w/monad monad
  79. (>>= mval
  80. (fn (var)
  81. (mlet* monad (rest ...)
  82. body ...)))))
  83. ((_ monad ((var -> val) rest ...) body ...)
  84. #'(let var val
  85. (mlet* monad (rest ...)
  86. body ...))))
  87. (mac mlet
  88. ((_ monad ((var mval ...) ...) body ...)
  89. (let-syn (temp ...) (gen-tmps #'(var ...))
  90. #'(mlet* monad ((temp mval ...) ...)
  91. (_let ((var temp) ...)
  92. body ...)))))
  93. (mac mdo
  94. "Bind the given monadic expressions in seq, returning the result of
  95. the last one."
  96. ((_ %curr-monad mexp) #'mexp)
  97. ((_ %curr-monad mexp rest ...)
  98. #'(>>= mexp
  99. (fn (unused-value)
  100. (mdo %curr-monad rest ...))))
  101. ((_ monad mexp)
  102. #'(w/monad monad mexp))
  103. ((_ monad mexp rest ...)
  104. #'(w/monad monad
  105. (>>= mexp
  106. (fn (unused-value)
  107. (mdo monad rest ...))))))
  108. (mac mwhen
  109. "When CONDITION is true, evaluate EXP0..EXP* as in an 'mdo'. When
  110. CONDITION is false, return *unspecified* in the curr monad."
  111. ((_ condition exp0 exp* ...)
  112. #'(if condition
  113. (mdo %curr-monad
  114. exp0 exp* ...)
  115. (return *unspecified*))))
  116. (mac munless
  117. "When CONDITION is false, evaluate EXP0..EXP* as in an 'mdo'. When
  118. CONDITION is true, return *unspecified* in the curr monad."
  119. ((_ condition exp0 exp* ...)
  120. #'(if condition
  121. (return *unspecified*)
  122. (mdo %curr-monad
  123. exp0 exp* ...))))
  124. (mac def-lift
  125. ((_ liftn (args ...))
  126. #'(mac liftn
  127. "Lift PROC to MONAD---i.e., return a monadic function in MONAD."
  128. ((liftn proc monad)
  129. ;; Inline the result of lifting PROC, such that 'return' can in
  130. ;; turn be open-coded.
  131. #'(fn (args ...)
  132. (w/monad monad
  133. (return (proc args ...)))))
  134. ((liftn id)
  135. (id? #'id)
  136. ;; Slow path: Return a closure-returning procedure (we don't
  137. ;; guarantee (eq? LIFTN LIFTN), but that's fine.)
  138. #'(fn (proc monad)
  139. (fn (args ...)
  140. (w/monad monad
  141. (return (proc args ...)))))))))
  142. (def-lift lift0 ())
  143. (def-lift lift1 (a))
  144. (def-lift lift2 (a b))
  145. (def-lift lift3 (a b c))
  146. (def-lift lift4 (a b c d))
  147. (def-lift lift5 (a b c d e))
  148. (def-lift lift6 (a b c d e f))
  149. (def-lift lift7 (a b c d e f g))
  150. (def lift (proc monad)
  151. "Lift PROC, a procedure that accepts an arbitrary number of arguments, to
  152. MONAD---i.e., return a monadic function in MONAD."
  153. (fn args
  154. (w/monad monad
  155. (return (apply proc args)))))
  156. (def foldm (monad mproc init lst)
  157. "Fold MPROC over LST and return a monadic value seeded by INIT.
  158. (foldm state-monad (lift2 cons state-monad) '() '(a b c))
  159. => '(c b a) ;monadic "
  160. (w/monad monad
  161. (loop lp ((lst lst)
  162. (result init))
  163. (match lst
  164. (()
  165. (return result))
  166. ((head tail ...)
  167. (>>= (mproc head result)
  168. (fn (result)
  169. (lp tail result))))))))
  170. (def mapm (monad mproc lst)
  171. "Map MPROC over LST and return a monadic list.
  172. (mapm state-monad (lift1 1+ state-monad) '(0 1 2))
  173. => (1 2 3) ;monadic"
  174. (mlet monad ((result (foldm monad
  175. (fn (item result)
  176. (>>= (mproc item)
  177. (fn (item)
  178. (return (cons item result)))))
  179. '()
  180. lst)))
  181. (return (rev result))))
  182. ;; XXX: Making it a macro is a bit brutal as it leads to a lot of code
  183. ;; duplication. However, it allows >>= and return to be open-coded, which
  184. ;; avoids struct-ref's to MONAD and a few closure allocations when using
  185. ;; STATE-MONAD.
  186. (mac seq
  187. "Turn the list of monadic vals LST into a monadic list of vals, by
  188. evaluating each item of LST in seq."
  189. ((_ monad lst)
  190. #'(w/monad monad
  191. (loop seq ((lstx lst)
  192. (result '()))
  193. (match lstx
  194. (()
  195. (return (rev result)))
  196. ((head . tail)
  197. (>>= head
  198. (fn (item)
  199. (seq tail (cons item result))))))))))
  200. (def anym (monad mproc lst)
  201. "Apply MPROC to the list of vals LST; return as a monadic value the first ;
  202. value for which MPROC returns a true monadic value or #f. For example:
  203. (anym state-monad (lift1 odd? state-monad) '(0 1 2))
  204. => #t ;monadic
  205. "
  206. (w/monad monad
  207. (loop lp ((lst lst))
  208. (match lst
  209. (()
  210. (return #f))
  211. ((head tail ...)
  212. (>>= (mproc head)
  213. (fn (result)
  214. (if result
  215. (return result)
  216. (lp tail)))))))))
  217. (mac listm
  218. "Return a monadic list in MONAD from the monadic vals MVAL."
  219. ((_ monad mval ...)
  220. (let-syn (val ...) (gen-tmps #'(mval ...))
  221. #'(mlet monad ((val mval) ...)
  222. (return (list val ...))))))
  223. (inline identity-return (value)
  224. value)
  225. (inline identity-bind (mvalue mproc)
  226. (mproc mvalue))
  227. (monad ident-monad
  228. (bind identity-bind)
  229. (return identity-return))
  230. (inline state-return (value)
  231. (fn (state)
  232. (vals value state)))
  233. (inline state-bind (mvalue mproc)
  234. "Bind MVALUE, a value in the state monad, and pass it to MPROC."
  235. (fn (state)
  236. (c/vals
  237. (fn ()
  238. (mvalue state))
  239. (fn (value state)
  240. ;; Note: as of Guile 2.0.11, declaring a variable to hold the result
  241. ;; of (mproc value) prevents a bit of unfolding/inlining.
  242. ((mproc value) state)))))
  243. (monad state-monad
  244. (bind state-bind)
  245. (return state-return))
  246. (def run-w/state (mval #:o (state '()))
  247. "Run monadic value MVAL starting with STATE as the initial state. Return
  248. two vals: the resulting value, and the resulting state."
  249. (mval state))
  250. (inline curr-state ()
  251. "Return the curr state as a monadic value."
  252. (fn (state)
  253. (vals state state)))
  254. (inline curr-state! (value)
  255. "Set the curr state to VALUE and return the previous state as a monadic
  256. value."
  257. (fn (state)
  258. (vals state value)))
  259. (def state-pop ()
  260. "Pop a value from the curr state and return it as a monadic value. The
  261. state is assumed to be a list."
  262. (fn (state)
  263. (match state
  264. ((head . tail)
  265. (vals head tail)))))
  266. (def state-push (value)
  267. "Push VALUE to the curr state, which is assumed to be a list, and return
  268. the previous state as a monadic value."
  269. (fn (state)
  270. (vals state (cons value state))))