Last active
November 22, 2021 23:24
-
-
Save newswim/4668aef8a1f1bc0dabe8 to your computer and use it in GitHub Desktop.
Douglas Crockford's Monads and Gonads
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
Monads / (mo problems) | |
Taken from Category Theory and imported into Haskell. | |
Has the strange quirk of being un-explainable. | |
The monad solves a particular problem that Haskell has | |
but it turns out to be a generally useful concept. | |
It starts with Functional Programming. There are two senses: | |
- programming with functions | |
- pure functional programming (mathematical) | |
Found its way into Fortran II (1958) | |
FUNCTION name(parameters) | |
DIMENSION ... | |
COMMON ... | |
name = expression | |
RETURN | |
END | |
It had types (integer and real). | |
If you wanted to make something common, you would | |
call it COMMON. | |
The RETURN statement didn't have an expression on it | |
so to tell FORTRAN what value to return, you would | |
have an assignment to the name of the function. | |
Instead of curly braces, there was just END to mark the end. | |
- - - - - - Pure Functional Programming - - - - - - | |
Functions in programming languages behave nothing like | |
mathematical functions, because a math function always | |
returns the same value. They are idempotent, immortal. | |
You can think of pure functions as maps. | |
The purpose is that mathematical functions are easier to | |
reason about. | |
"Programming without side-effects" | |
It's easy to turn JavaScript into a pure functional language: | |
> Remove assignment, loops (use recursion instead), | |
> freeze all array literals and object literals. | |
> Remove `Date` and `Math.random` (not mathematical) | |
> Remove the DOM | |
This however doesn't really work. How does one do I/O | |
in a language where nothing changes? | |
* Memoization | |
* Caching -- possibly the hardest thing in programming | |
In the real world, everything changes all the time, so | |
Immutability makes it hard to interact with the world. | |
...along came Haskell, and the application of... | |
- - - - - - Monads - - - - - - | |
Monads are a loophole in the function contract. | |
The Function Contract says that every time you call | |
a function with the same arguments, you get the same result, | |
and cause no side effects. | |
But, if you pass a function as a parameter to a function, | |
every function is different, in the sense of; new invocations, | |
closing over something its never closed over before, etc. | |
You're constantly in a state of newness. | |
This creates the "illusion of immutability" | |
In Haskell, they came up with the I/O monad. | |
The Socratic Haskell Programmer | |
-- "Suppose you have a function, and you want to trace it...." | |
-- "In order to learn monads, you must first learn Haskell" | |
-- "...then, you have to learn Category Theory" | |
In truth, Haskell is a great language -- there is much that | |
it can teach you. But, you don't need Haskell to learn Monads. | |
(more buts); ... But! you must first learn JavaScript. | |
> function unit(value) | |
> function bind(monad, function(value)) | |
/* All three functions return a monad */ | |
Ok, so whats a monad? It's an object. | |
`unit` takes a function, and returns an object... | |
Wait, that sounds like a constructor? | |
Yes. | |
- - - - - - Monad Axioms - - - - - | |
``` | |
bind (unit(value), f) ==== f(value) | |
bind(monad, unit) ==== monad | |
bind(bind(monad, f), g) | |
==== | |
bind(monad, function(value) { | |
return bind(f(value), g); | |
}) | |
``` | |
These first two axioms describe the relationship | |
between bind and unit. The third one (powerful one) | |
describes composition. | |
Composition turns out to be tHe ThiNg. | |
So, monads are usually described in the function form, | |
but it's more convenient to describe them as methods. | |
``` | |
bind(monad, func) // the OO transform | |
monad.bind(func) // | |
``` | |
In JavaScript, we can be doing things "method space" or | |
in "function space" and, generally, we choose the once | |
that is most convenient. | |
- - - - - - - The Macroid - - - - - - | |
* n: a macro-like thing used for making other functions | |
``` | |
function MONAD() { | |
return function unit(value) { | |
var monad = Object.create(null); | |
monad.bind = function (func) { | |
return func(value); | |
}; | |
return monad; | |
}; | |
} | |
``` | |
Context is much more important that 'what' a thing is. | |
Walking through.. this "macroid" will take a function, and | |
return an object with a bind method. | |
var unit = MONAD(); | |
var monad = unit("Hello world."); | |
monad.bind(alert); | |
This is commonly called "The Identity Monad" | |
Here are the Axioms, rewritten in method forms. | |
``` | |
unit(value).bind(f) ==== f(value) | |
monad.bind(unit) ==== monad | |
monad.bind(f).bind(g) | |
==== | |
monad.bind(function (value) { // "the Ajax monad" | |
return f(value).bind(g); | |
}) | |
``` | |
- - - - - Some examples - - - - - | |
"The Ajax Monad" | |
> > Interstate (2001) | |
> new Interform('text') | |
> .moveTo (100, 100) | |
> .setSize (400, 32) | |
> .moveInside() | |
> .setBgColor('pink') | |
> .select() | |
> .setZIndex(20000) | |
> .on('escapekey', 'erase') | |
* * * * * | |
> > ADsafe (2007) | |
> var input = dom.q("input_text") | |
> .on('enterkey', function (e) { | |
> dom.q('#ROMAN_RESULT') | |
> .value(roman(input | |
> .getValue())); | |
> input | |
> .select(); | |
> }) | |
> .focus(); | |
this allows for the querying of DOM, but | |
it doesn't return a node. Rather, a monad | |
which wrapped that node. | |
Monads have a potentially nice security quality. | |
In practice, the method notation is simpler. Contrast: | |
monad.bind(func) | |
monad.bind(func, [ | |
a, b, c // array of arguments | |
]) | |
vs. | |
monad.method() | |
monad.method(a, b, c) | |
- - - - - Extending the Macroid - - - - - | |
function MONAD() { | |
var prototype = Object.create(null); | |
function unit(value) { | |
var monad = Object.create(prototype); | |
monad.bind = function (func, args) { | |
// return func(value, ...args)); /* ES6 */ | |
return func.apply(undefined, | |
[value].concat(Array.prototype | |
.slice.apply(args || []))); | |
}; | |
if (typeof modifier === 'function') { // the Maybe monad | |
modifier(monad, value); | |
} | |
return monad; | |
} | |
unit.method = function (name, func) { | |
prototype[name] = func; | |
return unit; | |
}; | |
/* better: */ | |
unit.lift = function (name, func) { | |
prototype[name] = function (...args) { | |
return unit(this.bind(func, args)); | |
}; | |
return unit; | |
}; | |
return unit; | |
} | |
It fulfills the monad axiom because it: | |
- wraps the calling of bind by: | |
- taking function and calling bind on it | |
- then takes the result of bind, and calls `unit` on it | |
This gives the benefit of hiding all of the modadic (heh) operations. | |
,applying this to the Ajax monad --> | |
var ajax = MONAD() | |
.lift('alert', alert); | |
var monad = ajax("Hello world."); | |
monad.bind(alert); | |
monad.alert(); | |
we can now call alert via monad because it's been bound | |
thanks to the .lift method. | |
monad ==== ajax | |
- - - - - null (the Maybe Monad) - - - - - | |
Null Pointer Exception | |
usual cause: you didn't do a null pointer check. | |
It's similar to NaN, but for null values. | |
We've added to the Macroid to check for functions | |
and apply them automatically. Example use case: | |
var maybe = MONAD(function (monad, value) { | |
if (value === null || value === undefined) { | |
monad.is_null = true; | |
monad.bind = function () { | |
return monad; | |
} | |
} | |
}); | |
var monad = maybe(null); | |
monad.bind(alert); | |
Now we get a clear indication of a function's value; | |
if val === null, returns null monad, rather that *nothing* | |
This powerful monad lets you write apps w/o null checks. | |
- - - - - - Concurrency - - - - - | |
"Threads are evil" | |
Computers are getting better at it, but languages | |
generally are not. And neither are programmers. | |
Threads are very easy to get started with, but | |
very difficult to reason about later. | |
Design error warning! | |
Threads are subject to race conditions, deadlocks, etc. | |
This is the fault of Java(tm). | |
At the system level, they are a neccesary evil. | |
At the application level, they're just evil. | |
Reliability being reliant on a concurrency mechanism | |
is unsafe, and that's exactly what threads are. | |
-- enter Turn Based Programming -- | |
It's what we have in the browser and in places like | |
most UI frameworks, Nodejs, Twisted, Elko (servers). | |
* Single-threaded. Race-free. Deadlock free. | |
"Never wait, never block, finish fast" | |
No other turn can end, until that one ends. | |
Can be event-driven (browser) or message-driven (Node). | |
Asynchronicity can still be hard to manage.. | |
particularly if you have a bunch of events that | |
are serially dependent on each other. The naive | |
approach is to have nested event handlers. But | |
that can get very convoluted. | |
-- enter Promises -- | |
* Promises are an excellent mechanism for | |
managing asynchronicity. | |
* A promise is an object that represents a | |
possible future value. | |
* Every promise has a corresponding resolver | |
that is used to ultimately assign a value | |
to the promise. | |
* A promise can have one of three states: | |
'kept', 'broken', 'pending'. | |
Promises came out of Futures, which came out of | |
the Actor model. | |
It's created 'pending' and can move to one of | |
the other two states, after that it will | |
never change again. | |
* A promise is an event generator. It fires its | |
event when the value of the promise is ultimately | |
known. | |
* At any time after the making of a promise, | |
event handling functions can be registered | |
with the promise, which will be called | |
in order with the promise's value when | |
it is known. | |
* A promise can accept functions that will be | |
called with the value once the promise has | |
been kept or broken. | |
* (promise.when(success, failure)) returns | |
another promise for the result of your | |
'success' function. | |
* * * * New Promise Macroid * * * * | |
// Make a vow | |
var my_vow = VOW.make(); | |
.keep(value) | |
.break(reason) | |
.promise | |
.when(kept, broken) // returns another promise | |
* * * * Filesystem API * * * * | |
We've been doing filesystems wrong since Fortran. | |
A great way is for the FS to return a promise, then | |
go do other things when it's waiting for the promise | |
to come back. | |
read_file(name) | |
.when(function success(string) { | |
... | |
}, failure); | |
This also provides a failure function. | |
Q: Why not throw an exception? | |
A: The nature of turns. | |
-- Exceptions -- | |
* Exceptions modify the flow of control by | |
unwinding the state. | |
* In turn based system, the stack is emptied | |
at the end of every turn. | |
* Exceptions cannon be delivered across | |
turns. | |
* Broken promises can. | |
It's like time travel, you cannon throw something into | |
a previous call stack, that turn has already ended. | |
We can't go back, we can only go forward... and the | |
mechanism for doing that is promises. The failure | |
branch of the promise is the way you send exceptions | |
into the future. Additionally, they can cascade.... | |
-- Breakage flows to the end -- | |
my_promise | |
.when(success_a) | |
.when(success_b) | |
.when(success_c, failure); | |
success_a gets the value of my_promise | |
success_b gets the value of success_a | |
success_c gets the value of success_b | |
unless any promise breaks: | |
failure gets the reason |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Except the unfortunate confusion that Promise is a Monad (it isn't):
https://twitter.com/Dmitri145/status/1131026857065771008