Skip to content

Instantly share code, notes, and snippets.

@quelgar
Last active August 29, 2015 13:56
Show Gist options
  • Save quelgar/9238217 to your computer and use it in GitHub Desktop.
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!
<!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>
"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