Skip to content

Instantly share code, notes, and snippets.

@StephenWakely
Last active November 2, 2015 10:40
Show Gist options
  • Save StephenWakely/d64aec2b48aad30f317c to your computer and use it in GitHub Desktop.
Save StephenWakely/d64aec2b48aad30f317c to your computer and use it in GitHub Desktop.
Logging monad in Common Lisp
(defun mkStr (&rest args)
(with-output-to-string (s)
(dolist (a args) (princ a s))))
(defclass Out ()
((output :accessor output
:initarg :output)
(result :accessor result
:initarg :result)))
(defun return-out (result)
(make-instance 'Out :output "" :result result))
(defmethod >>= ((out Out) fn)
(with-slots ((x result) (ox output)) out
(with-slots ((oy output) (y result)) (funcall fn x)
(make-instance 'Out :output (mkStr ox oy) :result y))))
(defun line (num total)
(make-instance 'Out :output (mkStr "Number is :" num " ") :result total))
(defmacro doM (stmt &rest stmts)
"do macro (do is reserved in lisp, so it is called doM instead)"
(let* ((is-bound (eq '<- (second stmt)))
(param (if is-bound (first stmt) (gensym)))
(stmt* (if is-bound (third stmt) stmt)))
(if stmts
`(>>= ,stmt* (lambda (,param)
,@(when (not is-bound)
`((declare (ignore ,param))))
(doM ,@stmts)))
stmt*)))
(defun add-a-list (&rest nums)
"Add together the numbers in a list using the Out monad to
log each of the numbers being added together. Uses the raw
bind (>>=) functions to pass the monad along - this gets long
winded pretty quickly. The doM macro makes things much clearer.
Observe add-a-list-do."
(if nums
(>>=
(apply #'add-a-list (rest nums))
(lambda (total)
(>>=
(line (first nums) total)
(lambda (um)
(declare (ignore um))
(return-out (+ (first nums) total))))))
(return-out 0)))
(defun add-a-list-do (&rest nums)
"Same as above using the do macro instead"
(if nums
(doM
(total <- (apply #'add-a-list-do (rest nums)))
(line (first nums) total)
(return-out (+ (first nums) total)))
(return-out 0)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment