Skip to content

Instantly share code, notes, and snippets.

@avernet
Forked from igstan/state-monad.coffee
Created May 3, 2011 02:48
Show Gist options
  • Save avernet/952737 to your computer and use it in GitHub Desktop.
Save avernet/952737 to your computer and use it in GitHub Desktop.
State Monad in CoffeeScript
#### Monad - common code
# bind takes a step and processor,
# and combines them into a step
# (S t) -> (t -> S u) -> (S u)
bind = (step, processor) -> (container) ->
result = step container
(processor result.value) result.container
# result builds a step that returns just a result
# t -> S t
result = (value) -> (container) ->
{value: value, container: container}
#### General list monad
# L t: List -> {t, List}
# Create a processor that discards the current list, and creates a new one based on the parameters
create = (start, end) -> (list) ->
{value: null, container: [start..end]}
# Length is a step
length = (list) ->
{value: list.length, container: list}
# Add is processor taking the increment as parameter
add = (i) -> (list) ->
{value: null, container: e + i for e in list }
# Sum is a step returning the list unchanged with the sum of all the items
sum = (list) ->
{value: (v = 0; v += e for e in list; v), container: list }
chain = bind(create(1, 4), ()->
bind(length, (l) ->
bind(add(l), () ->
sum
)
)
)
console.log(chain([]).value)
# Let's construe the list monad not as a container, but as a function (step)
# General monadic type: M t: I -> O t
# Particular case of pipeline step: S t: X -> (X, t)
# Particular case of list: L t: -> List t
# Unit processor: t -> L t
unit = (x) -> -> [x]
# bind takes a step and processor, and combines them into a step
# (M t) -> (t -> M u) -> (M u)
bind = (m) -> (p) ->
-> r = []; (r.push u for u in (p t)()) for t in m(); r
# Double processor
double = (x) -> -> [2*x]
# Children age step: L t
childrenAge = -> [6, 8]
# Is teenager processor: t -> M t
isTeenager = (t) -> -> if 13 <= t <= 19 then [t] else []
console.log ((bind childrenAge) double)() # [12, 16]
console.log ((bind ((bind childrenAge) double)) isTeenager)() # [16]
# A monad is like a step
# that is a function: XML -> {result, XML}
# We can write the type of a step as S t
# e.g. S boolean for the validation step
# A processor is a function parameter -> step
# Processor: t -> S u
# e.g. Schema processor
# take a schema as parameter
# returns a step: XML -> {boolean, XML}
# schema -> S boolean
# e.g. Save-if is a processor
# take a boolean as parameter
# return a step: XML -> {(), XML}
# boolean -> S ()
# Processor could be called step builder
# bind takes a step and processor,
# and combines them into a step
# (S t) -> (t -> S u) -> (S u)
bind = (stackOperation, continuation) -> (stack) ->
result = stackOperation stack
(continuation result.value) result.stack
# result builds a step that returns just a result
# t -> S t
result = (value) -> (stack) ->
{value: value, stack: stack}
# Example: posted XML, extract1, extract2, concat
# bind(extract(xpath), (e1) ->
# bind(extract(xpath), (e2) ->
# result(e1+e2)
# )
# )
push = (element) -> (stack) ->
newStack = [element].concat stack
{value: element, stack: newStack}
pop = (stack) ->
element = stack[0]
newStack = stack.slice 1
{value: element, stack: newStack}
initialStack = []
computation1 = bind \
(push 4), -> bind \
(push 5), -> bind \
pop, (a) -> bind \
pop, (b) ->
result a + ":" + b
computation2 = bind \
(push 2), -> bind \
(push 3), -> bind \
pop, (a) -> bind \
pop, (b) ->
result a + ":" + b
composed = bind \
computation1, (a) -> bind \
computation2, (b) ->
result a + ":" + b
finalResult = composed initialStack
console.log finalResult.value
# Let's implement a general list monad
# L t: List -> {t, List}
# Create a processor that discards the current list, and creates a new one based on the parameters
create = (start, end) -> (list) ->
{value: null, stack: [start..end]}
# Length is a step
length = (list) ->
{value: list.length, stack: list}
# Add is processor taking the increment as parameter
add = (i) -> (list) ->
{value: null, stack: e + i for e in list }
# Sum is a step returning the list unchanged with the sum of all the items
sum = (list) ->
{value: (v = 0; v += e for e in list; v), stack: list }
chain = bind(create(1, 4), ()->
bind(length, (l) ->
bind(add(l), () ->
sum
)
)
)
console.log(chain([]).value)
# In the case where the step can't return a value, we can simplify
# L t to be List instead of List -> {t, List}
# Then a step builder is something that builds a list from a parameter: a list builder
# Combining list builder is done with foldMap
# But: less general then above, doesn't allow to implement a length
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment