Skip to content

Commit

Permalink
✨ [bare-function] Contexutal implements bare functions
Browse files Browse the repository at this point in the history
Problem:
- Contextual does not provide a context for the environment monad
implemented with bare functions.

Solution:
- Add a context for bare functions
  • Loading branch information
sabjohnso committed Nov 2, 2024
1 parent 3c0e76b commit 65c12ab
Show file tree
Hide file tree
Showing 5 changed files with 337 additions and 6 deletions.
10 changes: 7 additions & 3 deletions contextual.asd
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
(:file "list")
(:file "optional")
(:file "thunk")
(:file "bare"))))
(:file "bare")
(:file "bare-function"))))
:in-order-to ((test-op
(load-op :contextual)
(test-op :contextual/test))))
Expand All @@ -42,7 +43,8 @@
(:file "list-test")
(:file "optional-test")
(:file "thunk-test")
(:file "bare-test"))))
(:file "bare-test")
(:file "bare-function-test"))))
:perform (test-op (o s)
(symbol-call :binding-syntax-helpers-test :run-all-tests!)
(symbol-call :contextual-internal-test :run-all-tests!)
Expand All @@ -51,4 +53,6 @@
(symbol-call :contextual-list-test :run-all-tests!)
(symbol-call :contextual-optional-test :run-all-tests!)
(symbol-call :contextual-thunk-test :run-all-tests!)
(symbol-call :contextual-thunk-test :run-all-tests!)))
(symbol-call :contextual-thunk-test :run-all-tests!)
(symbol-call :contextual-bare-test :run-all-tests!)
(symbol-call :contextual-bare-function-test :run-all-tests!)))
131 changes: 131 additions & 0 deletions src/bare-function.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
(in-package :cl-user)

(defpackage :contextual-bare-function
(:use :cl :binding-syntax-helpers :contextual :contextual-utility)
(:export #:make-bare-function-context
#:+bare-function+
#:bf-run
#:bf-fmap
#:bf-pure
#:bf-fapply
#:bf-mreturn
#:bf-flatmap
#:bf-flatten
#:bf-ask
#:bf-asks
#:bf-local
#:let*-fun/bf
#:let-fun/bf
#:let-app/bf
#:let*-mond/bf
#:let-mon/bf))

(in-package :contextual-bare-function)

(eval-when (:load-toplevel :compile-toplevel)
(defun bf-run (e mx)
"Run a bare function context with the environment `E'."
(declare (type function mx))
(funcall mx e))

(defun bf-fmap (f mx)
(lambda (e)
(funcall f (bf-run e mx))))

(defun bf-pure (x)
(lambda (e)
(declare (ignore e))
x))

(defun bf-mreturn (x)
(lambda (e)
(declare (ignore e))
x))

(defun bf-fapply (mf mx)
(lambda (e) (funcall (bf-run e mf) (bf-run e mx))))

(defun bf-product (mx my)
(lambda (e)
(list (bf-run e mx) (bf-run e my))))

(defun bf-flatmap (f mx)
(lambda (e)
(bf-run e (funcall f (bf-run e mx)))))

(defun bf-flatten (mmx)
(lambda (e)
(bf-run e (bf-run e mmx))))

(defun bf-ask ()
#'identity)

(defun bf-asks (f)
"Return the environment"
(declare (type function f))
(lambda (e)
(funcall f e)))

(defun bf-local (f mx)
"Run a bare function with a locally modified environemnt"
(lambda (e)
(bf-run (funcall f e) mx)))

(defun make-bare-function-context ()
"Return a monadic context for bare functions."
(make-instance 'monad-operators
:fmap #'bf-fmap
:fapply #'bf-fapply
:product #'bf-product
:mreturn #'bf-mreturn
:flatmap #'bf-flatmap
:flatten #'bf-flatten)))

(defmacro let*-fun/bf ((&rest bindings) &body body)
(make-sequential-functor-binding
'let*-fun/bf
:fmap 'bf-fmap
:bindings bindings
:body body))

(defmacro let-fun/bf ((&rest bindings) &body body)
(make-parallel-functor-binding-ez
'let-fun/bf
:let-sequential 'let*-fun/bf
:bindings bindings
:body body))

(defmacro let-app/bf ((&rest bindings) &body body)
(make-parallel-applicative-binding-ez
'let-app/bf
:fmap 'bf-fmap
:fapply 'bf-fapply
:bindings bindings
:body body))

(defmacro progn-mon/bf (&body body)
(make-monad-progn-ez
'progn-mon/bf
:flatmap 'bf-flatmap
:body body))

(defmacro let*-mon/bf ((&rest bindings) &body body)
(make-sequential-monad-binding-ez
'let*-mon/bf
:flatmap 'bf-flatmap
:monad-progn 'progn-mon/bf
:bindings bindings
:body body))

(defmacro let-mon/bf ((&rest bindings) &body body)
(make-parallel-monad-binding
'let-mon/bf
:flatmap 'bf-flatmap
:sequential-let-name 'let*-mon/bf
:monad-progn 'progn-mon/bf
:bindings bindings
:body body))


(define-constant +bare-function+
(make-bare-function-context))
5 changes: 3 additions & 2 deletions src/internal.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
(defstruct contextual func)

(defun ctx-run (context cx)
(declare (type contextual cx))
(funcall (contextual-func cx) context))
(if (contextual-p cx)
(funcall (contextual-func cx) context)
cx))

(defun ctx-return (x)
(make-contextual
Expand Down
105 changes: 104 additions & 1 deletion src/syntax-helpers.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@
#:make-sequential-monad-binding
#:make-parallel-monad-binding
#:make-parallel-applicative-binding
#:make-parallel-functor-binding))
#:make-parallel-functor-binding

#:make-sequential-functor-binding-ez
#:make-parallel-functor-binding-ez
#:make-parallel-applicative-binding-ez
#:make-monad-progn-ez
#:make-sequential-monad-binding-ez
#:make-parallel-monad-binding-ez))

(in-package :binding-syntax-helpers)

Expand Down Expand Up @@ -136,3 +143,99 @@
:more-body more-body)))
`(funcall (lambda (,@new-vars) ,body)
,@exprs)))))

(defun make-sequential-functor-binding-ez (let-name &key fmap bindings body)
"Return syntax for sequential functor bindings"
(declare (type symbol let-name fmap)
(type list bindings body))

(assert (not (null bindings)))
(assert (not (mull body)))

(destructuring-bind (binding . more-bindings) bindings
(destructuring-bind (body . more-body) body
(make-sequential-functor-binding
let-name
:fmap fmap
:binding binding
:more-bindings more-bindings
:body body
:more-body more-body))))

(defun make-parallel-functor-binding-ez (let-name &key let-sequential bindings body)
(declare (type symbol let-name let-sequential)
(type list bindings body))

(assert (not (null bindings)))
(assert (not (null body)))

(destructuring-bind (binding . more-bindings) bindings
(destructuring-bind (body . more-body) body
(make-parallel-functor-binding
let-name
:let-sequential let-sequential
:binding binding
:more-bindings more-bindings
:body body
:more-body more-body))))

(defun make-parallel-applicative-binding-ez (let-name &key fmap fapply bindings body)
(declare (type symbol let-name fmap fapply)
(type list bindings body))
(assert (not (null bindings)))
(assert (not (null body)))

(destructuring-bind (binding . more-bindings) bindings
(destructuring-bind (body . more-body) body
(make-parallel-applicative-binding
let-name
:fmap fmap
:fapply fapply
:binding binding
:more-bindings more-bindings
:body body
:more-body more-body))))


(defun make-monad-progn-ez (progn-name &key flatmap body)
(declare (type symbol progn-name flatmap)
(type list body))
(assert (not (null body)))
(make-monad-progn progn-name :flatmap flatmap :body (car body) :more-body (cdr body)))


(defun make-sequential-monad-binding-ez (let-name &key flatmap monad-progn bindings body)
(declare (type symbol let-name flatmap monad-progn)
(type list bindings body))
(assert (not (null body)))
(if (null bindings)
(macroexpand `(,monad-progn ,@body))
(destructuring-bind (binding . more-bindings) bindings
(destructuring-bind (body . more-body) body
(make-sequential-monad-binding
let-name
:flatmap flatmap
:monad-progn monad-progn
:binding binding
:more-bindings more-bindings
:body body
:more-body more-body)))))


(defun make-parallel-monad-binding-ez (let-name &key flatmap sequential-let-name monad-progn bindings body)
(declare (type symbol let-name flatmap sequential-let-name monad-progn)
(type list bindings body))
(assert (not (null body)))
(if (null bindings)
(macroexpand `(,monad-progn ,@body))
(destructuring-bind (binding . more-bindings) bindings
(destructuring-bind (body . more-body) body
(make-parallel-monad-binding
'let-name
:flatmap flatmap
:sequential-let-name sequential-let-name
:monad-progn monad-progn
:binding binding
:more-bindings more-bindings
:body body
:more-body more-body)))))
92 changes: 92 additions & 0 deletions test/bare-function-test.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
(in-package :cl-user)

(defpackage :contextual-bare-function-test
(:use :cl :5am :contextual :contextual-bare-function)
(:export #:run-all-tests!))

(in-package :contextual-bare-function-test)

(defun run-all-tests! ()
(run! 'bare-function))

(def-suite bare-function)

(in-suite bare-function)

(test specific-context-functions
(is (eq 'x (bf-run 'e (bf-pure 'x))))
(is (eq 'x (bf-run 'e (bf-mreturn 'x))))
(is (equal "X" (bf-run 'e (bf-fmap #'symbol-name (bf-pure 'x)))))
(is (equal "E" (bf-run 'e (bf-fmap #'symbol-name (bf-ask)))))
(is (equal "E" (bf-run 'e (bf-fapply (bf-pure #'symbol-name) (bf-ask)))))
(is (equal "EX" (bf-run 'e
(bf-flatmap
(lambda (x)
(let-app/bf ((e (bf-ask)))
(concatenate 'string (symbol-name e) (symbol-name x))))
(bf-pure 'x)))))
(is (eq 'e (bf-run 'e
(bf-flatten (bf-mreturn (bf-ask)))))))

(test generic-context-functions
(let ((context (make-bare-function-context)))
(is (eq 'x (bf-run 'e (ctx-run context (pure 'x)))))
(is (eq 'e (bf-run 'e (ctx-run context (bf-ask)))))
(is (equal "X" (bf-run 'e (ctx-run context (fmap #'symbol-name (pure 'x))))))
(is (equal '(x e) (bf-run 'e
(ctx-run context
(product (pure 'x) (bf-ask))))))
(is (equal "X" (bf-run 'e
(ctx-run context
(flatmap (lambda (x)
(lambda (e)
(declare (ignore e))
(symbol-name x)))
(pure 'x))))))
(is (eq 'x (bf-run 'e
(ctx-run context
(flatten (pure (pure 'x)))))))
(is (equal "E"
(bf-run 'e
(ctx-run context
(bf-asks #'symbol-name)))))
(is (equal "E"
(bf-run 'e
(ctx-run context
(bf-local
#'symbol-name
(bf-ask))))))))


(test binding-syntax
(flet ((lookup (name) (lambda (e) (cdr (assoc name e)))))
(is (= 3
(bf-run '((x . 1) (y . 2))
(ctx-run +bare-function+
(flatten
(let-fun ((x (bf-asks (lookup 'x)))
(y (bf-asks (lookup 'y))))
(+ x y)))))))
(is (= 3
(bf-run '((x . 1) (y . 2))
(ctx-run +bare-function+
(let-app ((x (bf-asks (lookup 'x)))
(y (bf-asks (lookup 'y))))
(+ x y))))))

(is (= 3
(bf-run '((x . 1) (y . 2))
(ctx-run +bare-function+
(let-mon ((x (bf-asks (lookup 'x)))
(y (bf-asks (lookup 'y))))
(mreturn (+ x y)))))))

(is (= 5
(bf-run '((x . 1) (y . 2))
(ctx-run +bare-function+
(let-mon ((x (bf-asks (lookup 'x))))
(bf-local (lambda (e) (cons `(x . ,(+ x 2)) e))
(ctx-run +bare-function+
(let-app ((x (bf-asks (lookup 'x)))
(y (bf-asks (lookup 'y))))
(+ x y)))))))))))

0 comments on commit 65c12ab

Please sign in to comment.