Last active
January 16, 2020 17:36
-
-
Save thoferon/a4e1ee37fc85c6237d3c4cf3826b7390 to your computer and use it in GitHub Desktop.
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
import readline from "readline-sync" | |
// Side effects | |
class EffectfulImplementation { | |
getUserInput () { | |
return readline.question("> ") | |
} | |
listTodoItems () { | |
// Dummy implementation. | |
return [ | |
{ description: "Laundry", done: false }, | |
{ description: "Groceries", done: true } | |
] | |
} | |
printTodoItems (items) { | |
for (const item of items) { | |
console.log(item) | |
} | |
} | |
} | |
// Logic (pure code) | |
function parseCommand(input) { | |
// Dummy implementation. | |
return { | |
action: "list", | |
} | |
} | |
function cli(impl) { | |
const userInput = impl.getUserInput() | |
const command = parseCommand(userInput) | |
switch (command.action) { | |
case "list": | |
const items = impl.listTodoItems() | |
impl.printTodoItems(items) | |
// ... | |
} | |
} | |
// Glue code | |
cli(new EffectfulImplementation()); |
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
import parser from "./nonSequentialActions.js" | |
function generateDocumentation (parser) { | |
switch (parser.action) { | |
case "pure": | |
return null | |
case "stringField": | |
return { [parser.key]: "string" } | |
case "numberField": | |
return { [parser.key]: "number" } | |
case "objectField": | |
return { [parser.key]: generateDocumentation(parser.parser) } | |
case "apply": | |
const doc1 = generateDocumentation(parser.parser1) | |
const doc2 = generateDocumentation(parser.parser2) | |
if (doc1 === null) { | |
return doc2 | |
} else if (doc2 === null) { | |
return doc1 | |
} else { | |
return {...doc1, ...doc2} | |
} | |
} | |
} | |
console.log(generateDocumentation(parser)) |
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
function numberField (key) { | |
return { | |
action: "numberField", | |
key, | |
} | |
} | |
function stringField (key) { | |
return { | |
action: "stringField", | |
key, | |
} | |
} | |
function objectField (key, parser) { | |
return { | |
action: "objectField", | |
key, | |
parser, | |
} | |
} | |
function pure (value) { | |
return { | |
action: "pure", | |
value, | |
} | |
} | |
// Return a new parser that executes both parsers and apply the result of the | |
// second one to the function returned by the first one. | |
function apply (parser1, parser2) { | |
return { | |
action: "apply", | |
parser1, | |
parser2, | |
} | |
} | |
// Map a function to the result of a parser. | |
function mapParser (parser, f) { | |
switch (parser.action) { | |
case "pure": | |
return pure(f(parser.value)) | |
default: | |
return apply(pure(f), parser) | |
} | |
} | |
function interpret (parser, json) { | |
switch (parser.action) { | |
case "pure": | |
return parser.value | |
case "numberField": | |
if (typeof(json[parser.key]) === "number") { | |
return json[parser.key] | |
} else { | |
throw "ParsingError" | |
} | |
case "stringField": | |
if (typeof(json[parser.key]) === "string") { | |
return json[parser.key] | |
} else { | |
throw "ParsingError" | |
} | |
case "objectField": | |
if (typeof(json[parser.key]) === "object") { | |
return interpret(parser.parser, json[parser.key]) | |
} else { | |
throw "ParsingError" | |
} | |
case "apply": | |
const f = interpret(parser.parser1, json) | |
const x = interpret(parser.parser2, json) | |
return f(x) | |
} | |
} | |
const someJson = { | |
database: { | |
port: 5432, | |
username: "myapp", | |
} | |
} | |
const parser = | |
objectField( | |
"database", | |
apply( | |
apply( | |
pure(function (port) { | |
return function (username) { | |
return `postgresql://${username}:secret@localhost:${port}/myapp` | |
} | |
}), | |
numberField("port") | |
), | |
stringField("username") | |
) | |
) | |
// To execute the parser, we would call: | |
// const result = interpret(parser, someJson) | |
// For later: | |
export default parser |
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
import readline from "readline-sync" | |
function read () { | |
return { | |
action: "read", | |
next: pure, | |
} | |
} | |
function print (string) { | |
return { | |
action: "print", | |
string, | |
next: pure, | |
} | |
} | |
// A pure program that simply returns a value. | |
function pure (value) { | |
return { | |
action: "pure", | |
value, | |
} | |
} | |
function interpret (program) { | |
switch (program.action) { | |
case "read": | |
const input = readline.question("") | |
return interpret(program.next(input)) | |
case "print": | |
console.log(program.string) | |
return interpret(program.next(null)) | |
case "pure": | |
return program.value | |
} | |
} | |
// Bind two programs together. Return a new program which is equivalent to | |
// running the first program and feed its return value to the second one. | |
function bind (program, next) { | |
switch (program.action) { | |
case "pure": | |
return next(program.value) | |
default: | |
return { | |
...program, | |
next: (val) => bind(program.next(val), next) | |
} | |
} | |
} | |
const number = 42; | |
const game = | |
bind(print("Enter a guess"), _ => { | |
return bind(read(), (input) => { | |
const guess = Number(input) | |
if (guess === number) { | |
return print("Congratulations! You found it.") | |
} else if (guess < number) { | |
return bind(print("Higher"), _ => game) | |
} else if (guess > number) { | |
return bind(print("Lower"), _ => game) | |
} else { | |
return print("Just numbers, please.") | |
} | |
}) | |
}) | |
// To execute the program, we would just have to call: | |
// interpret(game) | |
// For later | |
export default game |
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
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
import _ from "underscore" | |
import game from "./sequentialActions.js" | |
function testInterpreter (program, inputs, accOutputs) { | |
switch (program.action) { | |
case "read": | |
const input = inputs[0] | |
const rest = inputs.slice(1) | |
return testInterpreter(program.next(input), rest, accOutputs) | |
case "print": | |
return testInterpreter( | |
program.next(), | |
inputs, | |
accOutputs.concat(program.string) | |
) | |
case "pure": | |
return accOutputs | |
} | |
} | |
const outputs = testInterpreter(game, ["3", "90", "32", "42"], []) | |
const expectedOutputs = [ | |
"Enter a guess", | |
"Higher", | |
"Enter a guess", | |
"Lower", | |
"Enter a guess", | |
"Higher", | |
"Enter a guess", | |
"Congratulations! You found it." | |
] | |
if (_.isEqual(outputs, expectedOutputs)) { | |
console.log("All is well") | |
} else { | |
console.log("Test failure", outputs, expectedOutputs) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment