Last active
August 29, 2015 13:56
-
-
Save quelgar/9238217 to your computer and use it in GitHub Desktop.
Basic demonstration of purely functional I/O in Javascript. This is just a demonstration of the concept, I doubt it's suitable for real-world use!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta http-equiv="Content-type" content="text/html; charset=utf-8"> | |
<title>Functional I/O Demo</title> | |
<script type="text/javascript" src="http://code.jquery.com/jquery-2.1.0.min.js"></script> | |
<script type="text/javascript" charset="utf-8" src="iodemo.js"></script> | |
<style type="text/css" media="screen"> | |
#id { | |
border: 3px; | |
border-color: black; | |
height: 300px; | |
} | |
#result { | |
color: green; | |
font-weight: bold; | |
} | |
</style> | |
</head> | |
<body id="iodemo" onload=""> | |
<h1>Functional I/O Demo</h1> | |
<div id="output"> | |
</div> | |
<input type="text" name="input" value="" id="input" size="50"> | |
<div id="result"></div> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict;" | |
function id(v) { | |
return v; | |
} | |
Function.constant = function (x) { | |
return function (y) { | |
return x; | |
}; | |
} | |
// function composition | |
Function.prototype.compose = function (f) { | |
var g = this; | |
return function () { | |
return g(f.apply(null, arguments)); | |
} | |
} | |
// function composition, flipped | |
Function.prototype.andThen = function (f) { | |
return f.compose(this); | |
} | |
// and I/O "action" to be performed by the runtime interpreter | |
// this forms a Monad | |
function IO(isDone) { | |
this.isDone = isDone; | |
} | |
// and I/O action that does nothing but hold a value to be | |
// passed on to the next action, or if this is the final | |
// action, forms the program's return value | |
IO.unit = function (v) { | |
var o = new IO(true); | |
o.done = v; | |
return o; | |
} | |
// an I/O action to be performed by the runtime interpreter | |
// operation - a string indicating the type of operation | |
// data - data of a type that depends on the operation | |
// can be undefined for operations that don't need it | |
// next - a function accepting the value produced by | |
// executing this action, which returns the next | |
// action to run | |
IO.more = function (operation, next, data) { | |
var o = new IO(false); | |
o.operation = operation; | |
o.data = data; | |
o.next = next; | |
return o; | |
} | |
// Do nothing without a value | |
IO.nop = IO.unit(undefined); | |
// I/O action to output a message. As this does not produce a value, | |
// undefined is passed to the next function | |
IO.log = function (msg) { | |
return IO.more("log", IO.unit, msg); | |
} | |
// RI/O action to read a string. | |
IO.read = function () { | |
return IO.more("read", IO.unit, undefined); | |
} | |
// Appends an I/O action to an existing chain of actions. | |
// Monadic bind. | |
IO.prototype.flatMap = function (f) { | |
var outer = this; | |
return this.isDone ? f(this.done) : IO.more(outer.operation, function (x) { | |
return outer.next(x).flatMap(f); | |
}, outer.data); | |
} | |
// Lift non-I/O functions into I/O action context | |
// Functor map. | |
IO.prototype.map = function (f) { | |
return this.flatMap(f.andThen(IO.unit)); | |
} | |
// flatMap where the value from the previous action is not needed | |
// Simply pass it the next I/O action to be performed | |
IO.prototype.chain = function (n) { | |
return this.flatMap(Function.constant(n)); | |
} | |
// Useful to have an action chain return a specified value | |
IO.prototype.withVal = function (x) { | |
return this.chain(IO.unit(x)); | |
} | |
IO.prototype.toString = function () { | |
return this.isDone ? "IO Done (" + this.done + ")" : "IO More (" + this.operation + ")"; | |
} | |
// Program that keeps asking for a number until it gets one ≥ 0 | |
var readInt = IO.log("Enter a number ≥ 0") | |
.chain(IO.read()).map(parseInt) // does not handle non-numbers being entered - it's just a demo | |
.flatMap(function (i) { | |
return i >= 0 ? IO.log("Got " + i).withVal(i) : IO.log("Value must be ≥ 0").chain(readInt); | |
}); | |
// Program to read two numbers and add them, with user prompts being output | |
// This code is referentially transparent - not side effects anywhere here! | |
// For demo purposes, this program will only add numbers ≥ 0. If a | |
// negative number is entered, it will ask for another number | |
var example = IO.log("Welcome to the silly number adder") | |
.chain(readInt) | |
.flatMap(function (a) { | |
return readInt.flatMap(function (b) { | |
var i = a + b; | |
return IO.log("Result of " + a + " + " + b + " = " + i).withVal(i); | |
}); | |
}); | |
// side-effects below | |
// These are the runtime interpreters of I/O action programs | |
// It is possible, and often very useful, to also write a | |
// functional interpreter for testing purposes | |
// CONSOLE - intended to be run with the JDK's "jrunscript" command | |
function runConsole(program) { | |
var current = program; | |
while (!current.isDone) { | |
if (current.operation === "log") { | |
print(current.data); | |
current = current.next(undefined); | |
} else { | |
var line = readLine(); | |
current = current.next(line); | |
} | |
} | |
return current.done; | |
} | |
// uncomment these lines to use the console | |
// var result = runConsole(example); | |
// print("Result of run = " + result); | |
// BROWSER - load iodemo.html in your browser | |
var current = example; | |
function inputHandler(e) { | |
var line = $( this ).val(); | |
$( this ).val(""); | |
current = current.next(line); | |
runBrowser(); | |
} | |
function runBrowser() { | |
$("#input").hide(); | |
while (!current.isDone) { | |
if (current.operation === "log") { | |
$("<p>" + current.data + "</p>").appendTo($("#output")); | |
current = current.next(undefined); | |
} else { | |
$("#input").show(); | |
return; | |
} | |
} | |
$("#result").text("Result of run = " + current.done); | |
$("#result").show(); | |
} | |
// comment out these lines if you want to run the console interpreter | |
$( document ).ready(function() { | |
$("#input").change(inputHandler); | |
$("#result").hide(); | |
runBrowser(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment