Skip to content

Instantly share code, notes, and snippets.

@kputnam
Created April 3, 2012 02:48
Show Gist options
  • Save kputnam/2288938 to your computer and use it in GitHub Desktop.
Save kputnam/2288938 to your computer and use it in GitHub Desktop.
IO monad in CoffeeScript
# Syntax sugar, more or less: wrap each monadic action with
# another function that binds to the next action `k`.
####################################################################
confirm_ = (msg, k) ->
bind confirm(msg), k
alert_ = (msg, k) ->
bind alert(msg), k
prompt_ = (msg, detent = "", k) ->
bind prompt(msg, detent), k
setTTY_ = (tty, k) ->
bind setTTY(tty), k
# Examples
####################################################################
test =
prompt_ "What's yer name?", "Ed", (name) ->
alert_ "Hi, #{name}!", ->
prompt_ "Wait, who are you?", "Jo", (conf) ->
# This is a poor example of still needing the non-wrapped
# actions. We want to avoid duplication, but both branches
# need to bind to the same action (unit). This is a poor
# example because we could just define next = unit "..."
# and then change both branches to alert_ "...", next.
next = if name == conf \
then alert "Of course. Bai now!"
else alert "LIAR!"
bind next, ->
unit "Names: #{name}, #{conf}"
# This returns a log of all IO actions
exec(test, (x) -> console.log(x))
nullTTY =
confirm: (msg, k) -> k Math.random() > 0.5
prompt: (msg, detent, k) -> k detent
alert: (msg, k) -> k null
# Browser and REPL "TTYs"
####################################################################
if window?
defaultTTY =
confirm: (msg, k) -> k window.confirm(msg),
prompt: (msg, detent, k) -> k window.prompt(msg, detent),
alert: (msg, k) -> k window.alert(msg)
else
readline = require("readline")
reader = readline.createInterface(process.stdin, process.stdout, null)
defaultTTY =
confirm: (msg, k) -> reader.question(msg + " ", k)
prompt: (msg, detent, k) -> reader.question(msg + " ", k)
alert: (msg, k) -> k reader.write(msg + "\n")
# Actions
####################################################################
confirm = (msg) -> (log, tty, k) ->
tty.confirm msg, (answer) ->
k tty: tty, value: answer, log: log.concat("confirm: #{msg} => #{answer}")
alert = (msg) -> (log, tty, k) ->
tty.alert msg, (answer) ->
k tty: tty, value: answer, log: log.concat("alert: #{msg}")
prompt = (msg, detent = "") -> (log, tty, k) ->
tty.prompt msg, detent, (answer) ->
k tty: tty, value: answer, log: log.concat("prompt: #{msg} => #{answer}")
setTTY = (tty) -> (log, tty, k) ->
k tty: tty, value: null, log: log.concat("setTTY")
# Combinators
####################################################################
unit = (value) -> (log, tty, k) ->
k tty: tty, log: log, value: value
bind = (x, f) -> (log, tty, k) ->
x log, tty, (tuple) ->
f(tuple.value)(tuple.log, tuple.tty, k)
# Evaluators
####################################################################
run = (computation, k) ->
computation [], defaultTTY, k
exec = (computation, k) ->
computation [], defaultTTY, (tuple) -> k tuple.log
eval = (computation, k) ->
computation [], defaultTTY, (tuple) -> k tuple.value
# Examples
####################################################################
test =
bind prompt("What's yer name?", "Ed"), (name) ->
bind alert("Hi, #{name}!"), ->
bind prompt("Wait, who are you?", "Jo"), (conf) ->
next = if name == conf \
then alert "Of course. Bai now!"
else alert "LIAR!"
bind next, ->
unit "Names: #{name}, #{conf}"
# This returns a log of all IO actions
exec(test, (x) -> console.log(x))
# This returns the computed "Names: ..." value
# eval(test, (x) -> console.log(x))
if exports?
exports.alert = alert
exports.prompt = prompt
exports.confirm = confirm
exports.setTTY = setTTY
exports.bind = bind
exports.unit = unit
exports.exec = exec
exports.nullTTY = nullTTY
exports.defaultTTY = defaultTTY
exports.test = test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment