Skip to content

Instantly share code, notes, and snippets.

@jethrolarson
Last active June 26, 2016 21:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jethrolarson/afdf6ed67b5d2cfc8d8d to your computer and use it in GitHub Desktop.
Save jethrolarson/afdf6ed67b5d2cfc8d8d to your computer and use it in GitHub Desktop.
AGeneric interface fantasy-land objects
var Maybe = require('./maybe.js')
var List = require('./list.js')
//Pass fantasy implementations to it
var {concat, map} = require('fantasy')({Maybe, List})
mapDouble = map(a => 2 * a)
mapDouble([1, 2])
//[2, 4]
mapDouble(Maybe.Just(3))
//{'@@type': 'Maybe', value: 6}
map(mapDouble)([Just(1), Nothing()])
//[{'@@type': 'Maybe', value: 2}, {'@@type': 'Maybe', value: null}]
concat([1])([2])
// [1, 2]
concat(Just(1))(Just(2))
// Error: Function (concat) is not implemented for (Maybe)
//@@type is just proposed convention
//:: a -> String
const defaultEvaluator = a =>
var type = a['@@type']
if(type){
return type
}
}
// Get the name of an algebraic type's type.
//:: [(a -> String)] -> a -> String
const ftype = evaluators => a => {
// check the value against provided evaluator functions
for(var i = 0; i < evaluators.length; i++){
var result = evaluators[i](a)
if(result){
return result
}
}
return ''
}
//:: String -> (a -> Boolean) -> String
const evaluator = (name, f) => x => f(x) ? name : ''
// Collects implementations of `is` for type matching
//:: {{typeEvaluator: (a -> Boolean)}} -> [(a -> String)]
const getTypeEvaluators = xface => {
var typeEvaluators = []
for(var k in xface){
if(xface.hasOwnProperty(k) && xface[k].typeEvaluator){
typeEvaluators.push(evaluator(k, xface[k].typeEvaluator))
}
}
return typeEvaluators
}
// xface is a map from types to their implementations
module.exports = xface => {
if(!xface) throw "no implementations of fantasy interface are provided"
const evaluators = [defaultEvaluator].concat(getTypeEvaluators(xface))
//:: a -> String
const checkType = ftype(evaluators)
//:: String, a -> (a -> b)
const getMethod = (name, a) => {
var type = checkType(a)
if(!type) throw 'Type is not defined'
var namespace = xface[type]
if(!namespace) throw `Type (${type}) is not registered`;
var method = namespace[name]
if(!method) throw `Function (${name}) is not implemented for (${type})`
return method
}
//These specify which argument should be used to ascertain the type
const dispatch = name => a => getMethod(name, a)(a)
const dispatch2 = name => a => b => getMethod(name, b)(a)(b)
const dispatch3 = name => a => b => c => getMethod(name, c)(a)(b)(c)
//TODO figure out how to extend this api externally. (Open/Closed Principle)
// PUBLIC API
//-----------
return {
type: checkType,
dispatch, dispatch2, dispatch3,
// Setoid
//:: S a -> S b -> Boolean
equals: dispatch('equals'),
// Semigroup
//:: S a -> S b -> S c
concat: dispatch('concat'),
// Monoid
// M a -> M _
empty: dispatch('empty'),
// Functor
//:: (a -> b) -> F a -> F b
map: dispatch2('map'),
// Apply
//:: F (a -> b) -> F a -> F b
ap: dispatch2('ap'),
//Foldable
//:: (b -> a -> b) -> b -> [a] -> b
reduce: dispatch3('reduce'),
// Monad
// Satisfied by chain and applicative
// Applicative
//:: F a -> b -> F b
of: dispatch('of'),
// Chain
// a.k.a flatmap or bind
//:: (a -> M b) -> M a -> M b
chain: dispatch2('chain'),
//Extract
//:: M a -> a
extract: dispatch('extract')
}
}
//example partial implementation of list
module.exports = {
typeEvaluator: val =>
(val != null &&
val.length >= 0 &&
Object.prototype.toString.call(val) === '[object Array]')
, map: f => list => list.map(f)
, empty: list => []
, concat: a => b => a.concat(b)
}
//example partial implementation of maybe
const nothing = {'@@type': 'Maybe', value: null}
const Just = a => {'@@type': 'Maybe', value: a}
const Nothing = ()=> nothing
module.exports = {
map: f => m => m.value != null ? Just(f(m.value)) : nothing
, empty: m => nothing
, Just
, Nothing
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment