From b4ac0fa66a3212fad3373a68d84d4b74c5fd309c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Mon, 18 Mar 2019 21:57:47 +0100 Subject: Wrote a bunch of documentation. --- monad.scm | 81 +++++++++++++++++++++++++++++++++++------------------- monad/optional.scm | 10 ++++++- monad/stack.scm | 13 +++++---- monad/state.scm | 33 ++++++++++++---------- 4 files changed, 87 insertions(+), 50 deletions(-) diff --git a/monad.scm b/monad.scm index 97f4af6..9d21b4c 100644 --- a/monad.scm +++ b/monad.scm @@ -1,44 +1,59 @@ +;;; Commentary: +;; +;; This is the base class of the monad interface. +;; It provides all the frames and glue required to use the library, and also +;; sets up the list monad (for multiple return values). +;; +;; To top it off, it gives default definitions to return and bind (>>=), +;; meaning that they will sort of work with any type. +;; +;;; Code: + (define-module (monad) #:use-module (srfi srfi-1) #:use-module (ice-9 match) #:use-module (ice-9 curried-definitions) #:use-module (oop goops) #:replace (do) - #:export (sequence mapM - fmap <$> cmap <*> - >> >>= return)) + #:export (sequence + mapM return + <$> <*> + >> >>=)) + (define-generic return) -(define-method (return (a )) identity) -(define-method (return (a )) list) -(define-generic >>=) +;; We start by defining our primitive operations, +(define-method (return (a )) + "@code{return :: Monad m => a -> m a} +Since we can't directly defer type from context we instead allow @code{return} +to take an object of the desired type for @code{return}. + +The default implementation is simple the identity function. +" + identity) + +(define-generic >>=) (define-method (>>= (a ) (proc )) - (proc a)) + "@code{bind :: Monad m => m a x (a -> m b) -> m b} -(define-method (>>= (this ) proc) '()) -(define-method (>>= (this ) - (proc )) - (concatenate! (map proc this))) +Bind (or >>=) takes a monad value along with a procedure taking a regular value +and returning a monad value. -(define-generic >>) +The default implementation simply applies proc to the value. Allowing any value +to be have the monadic type of being a scheme object. +" + (proc a)) +(define-generic >>) (define-method (>> (a ) (b )) (>>= a (lambda args b))) -(define-method (>> (a ) (b )) '()) -(define-method (>> (a ) (b )) '()) -(define-method (>> (a ) (b )) '()) -(define-method (>> (a ) (b )) - (concatenate! (map (const b) a))) - -;; bind :: Monad m => m a -> (a -> m b) -> m b -;; return :: Monad m => a -> m a -;; map :: Functor f => (a -> b) -> f a -> f b - ;;; ---------------------------------------- +;;- We replace Scheme's built in @code{do} with our own, which works exactly like +;;- Haskell's do. @code{let} and @code{<-} included. (define-syntax do (syntax-rules (<- let =) ((_ let ptrn = val rest ...) @@ -52,16 +67,15 @@ ;;; ---------------------------------------- -(define (fmap f m) - (>>= m (lambda (x) ((return m) (f x))))) +(define (<$> f m_) + "@code{map :: Functor f => (a -> b) x f a -> f b} -(define <$> fmap) - -;; Curried map -(define (cmap f) - (lambda (m) (fmap f m))) +@code{Fmap}; works on any monadic type since all monads are monoids in the +category of @emph{endofunctors}@footnote{What's the problem?}" + (>>= m_ (lambda (m) ((return m_) (f m))))) (define (<*> f_ i_) + "@code{applicative :: Functor f => f (a -> b) x f a -> f b}" (do f <- f_ i <- i_ ((return f_) (f i)))) @@ -98,3 +112,12 @@ left to right, and collect the results. For a version that ignores the results see mapM_. https://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Monad.html#g:4" (sequence (map (lambda (x) (>>= x proc)) items))) + +;;; ---------------------------------------- + +(define-method (return (a )) list) + +(define-method (>>= (this ) proc) '()) +(define-method (>>= (this ) + (proc )) + (apply append (map proc this))) diff --git a/monad/optional.scm b/monad/optional.scm index 015fc90..06c0e67 100644 --- a/monad/optional.scm +++ b/monad/optional.scm @@ -1,3 +1,10 @@ +;;; Commentary: +;; +;; Your classical optional (Maybe) monad. +;; It has the constructors @code{just} and @code{nothing}. +;; +;;; Code: + (define-module (monad optional) #:use-module (oop goops) #:use-module (ice-9 match) @@ -34,7 +41,8 @@ the value embedded in maybe-val" default)) (define ((wrap-maybe proc) . values) - "Wraps a function in an optional monad, where #f returns are translated to nothing." + "Wraps a function in an optional monad, where #f returns are translated to +nothing." (let ((v (apply proc values))) (if v (just v) (nothing)))) diff --git a/monad/stack.scm b/monad/stack.scm index 8d25303..fee48a6 100644 --- a/monad/stack.scm +++ b/monad/stack.scm @@ -1,14 +1,15 @@ +;;; Commentary: +;; Simple stateful stack module for showing the state monad +;; in action. These functions assume that they are in a +;; (state list) monad. But dynamic types! +;; +;; TODO test these for empty stack +;;; Code: (define-module (monad stack) #:export (pop peek push) #:use-module (monad) #:use-module (monad state)) -;;; Simple stateful stack module for showing the state monad -;;; in action. These functions assume that they are in a -;;; (state list) monad. But dynamic types! - -;;; TODO test these for empty stack - (define (pop) (do st <- (get) let top = (car st) diff --git a/monad/state.scm b/monad/state.scm index 471e756..40c9ff5 100644 --- a/monad/state.scm +++ b/monad/state.scm @@ -1,13 +1,26 @@ +;;; Commentary: +;; +;; The state monad! +;; State is best modelled monadically as a function taking a state object, and +;; returning another state object. This state object is in turn represented as i +;; list where the first value is the last value returned, and the secound value +;; is the internal state. +;; +;; All access to this internal value is done through the methods @code{get}, +;; @code{put}, and @code{modify}. +;; +;; One side effect of the @code{} object not being directly accessible is +;; that my trick for multiple dispatch return doesn't work. Which is why this +;; modules also exports @code{return-state} directly. +;; +;;; Code: + (define-module (monad state) #:use-module (oop goops) #:use-module (ice-9 match) #:use-module (monad) #:export (return-state run-state get put modify) - #:re-export (>>= >> fmap return)) - - -;; Alternative implementation of get. -;; See https://hackage.haskell.org/package/mtl-2.2.1/docs/src/Control.Monad.State.Class.html#get + #:re-export (>>= >> return)) ;;; newtype State = st-list -> st-list @@ -48,10 +61,6 @@ ((v _) ((proc (f v)) new-st-list))))) -;; (define-stateful-method ((>> (a ) (b )) st-list-a) -;; (let ((st-list-b ((proc a) st-list-a))) -;; ((proc b) st-list-b))) - (define-stateful ((return-state v) st-list) "Sets the return value to v" (cons v (cdr st-list))) @@ -69,15 +78,11 @@ (list '() v)) (define-stateful ((modify proc) st-list) + "Applies proc to the value stored in state, and stores it back" (match st-list ((r s) (list '() (proc s))))) -;; (define-stateful-method ((fmap (f ) (s )) st-list) -;; (match ((proc s) st-list) -;; ((r st) -;; (list (f r) st)))) - (define-method (run-state (st ) init) "Exec state with init as starting state value and st." ((proc st) (list init init))) -- cgit v1.2.3