Skip to content

Instantly share code, notes, and snippets.

@joneshf
Created December 22, 2013 04:59
Show Gist options
  • Save joneshf/8078646 to your computer and use it in GitHub Desktop.
Save joneshf/8078646 to your computer and use it in GitHub Desktop.
More inquire garbage.

Terminology

  1. "value" is any JavaScript value, including any which have the structures defined below.
  2. "equivalent" is an appropriate definition of equivalence for the given value. The definition should ensure that the two values can be safely swapped out in a program that respects abstractions. For example:
    • Two lists are equivalent if they are equivalent at all indices.
    • Two plain old JavaScript objects, interpreted as dictionaries, are equivalent when they are equivalent for all keys.
    • Two promises are equivalent when they yield equivalent values.
    • Two functions are equivalent if they yield equivalent outputs for equivalent inputs.

Algebras

Semigroup

  1. a.concat(b).concat(c) is equivalent to a.concat(b.concat(c)) (associativity)

concat method

A value which has a Semigroup must provide a concat method. The concat method takes one argument:

s.concat(b)
  1. b must be a value of the same Semigroup

    1. If b is not the same semigroup, behaviour of concat is unspecified.
  2. concat must return a value of the same Semigroup.

Monoid

A value that implements the Monoid specification must also implement the Semigroup specficiation.

  1. m.concat(m.empty()) is equivalent to m (right identity)
  2. m.empty().concat(m) is equivalent to m (left identity)

empty method

A value which has a Monoid must provide an empty method on itself or its constructor object. The empty method takes no arguments:

m.empty()
m.constructor.empty()
  1. empty must return a value of the same Monoid

Functor

  1. u.map(function(a) { return a; })) is equivalent to u (identity)
  2. u.map(function(x) { return f(g(x)); }) is equivalent to u.map(g).map(f) (composition)

map method

A value which has a Functor must provide a map method. The map method takes one argument:

u.map(f)
  1. f must be a function,

    1. If f is not a function, the behaviour of map is unspecified.
    2. f can return any value.
  2. map must return a value of the same Functor

Applicative

A value that implements the Applicative specification must also implement the Functor specification.

A value which satisfies the specification of a Applicative does not need to implement:

  • Functor's map; derivable as function(f) { return this.of(f).ap(this); })}
  1. a.of(function(a) { return a; }).ap(v) is equivalent to v (identity)
  2. a.of(function(f) { return function(g) { return function(x) { return f(g(x))}; }; }).ap(u).ap(v).ap(w) is equivalent to u.ap(v.ap(w)) (composition)
  3. a.of(f).ap(a.of(x)) is equivalent to a.of(f(x)) (homomorphism)
  4. u.ap(a.of(y)) is equivalent to a.of(function(f) { return f(y); }).ap(u) (interchange)

ap method

A value which has an Applicative must provide an ap method. The ap method takes one argument:

a.ap(b)
  1. a must be an Applicative of a function,

    1. If a does not represent a function, the behaviour of ap is unspecified.
  2. b must be an Applicative of any value

  3. ap must apply the function in Applicative a to the value in Applicative b

of method

A value which has an Applicative must provide an of method on itself or its constructor object. The of method takes one argument:

a.of(b)
a.constructor.of(b)
  1. of must provide a value of the same Applicative

    1. No parts of b should be checked

Chain

  1. m.chain(f).chain(g) is equivalent to m.chain(function(x) { return f(x).chain(g); }) (associativity)

chain method

A value which has a Chain must provide a chain method. The chain method takes one argument:

m.chain(f)
  1. f must be a function which returns a value

    1. If f is not a function, the behaviour of chain is unspecified.
    2. f must return a value of the same Chain
  2. chain must return a value of the same Chain

Monad

A value that implements the Monad specification must also implement the Applicative and Chain specifications.

A value which satisfies the specification of a Monad does not need to implement:

  • Applicative's ap; derivable as function(m) { return this.chain(function(f) { return m.map(f); }); }
  • Functor's map; derivable as function(f) { var m = this; return m.chain(function(a) { return m.of(f(a)); })}
  1. m.of(a).chain(f) is equivalent to f(a) (left identity)
  2. m.chain(m.of) is equivalent to m (right identity)
import Data.Bitraversable
-- | The relation between a key and value "time=now" or "cat!=dog"
data Relation = Equal
| NEqual
| GThan
| GThanE
| LThan
| LThanE
deriving Eq
-- | The boolean operation between a group of Inquires
data GBool = And
| Or
deriving Eq
-- | This is an optional negation wrapping an Inquire.
data WBool = NoBool
| Not
deriving Eq
-- | The meat of our package. This encapsulates our query logic.
data Inquire k v = Atom
| Predicate k Relation v
| Group (Inquire k v) GBool (Inquire k v)
| Wrap WBool (Inquire k v)
deriving Eq
instance Bitraversable Inquire where
bitraverse _ _ Atom = pure Atom
bitraverse f g (Predicate k r v) = liftA3 Predicate (f k) (pure r) (g v)
bitraverse f g (Group i1 b i2) =
Group <$> bitraverse f g i1 <*> pure b <*> bitraverse f g i2
bitraverse f g (Wrap b i) = Wrap <$> pure b <*> bitraverse f g i
class Inquire
(@op, @key, @val) ~>
and: (i) -> new Group new And, this, i
or: (i) -> new Group new Or, this, i
not: -> new Wrap new Not, this
/*
Fantasy land stuff.
*/
concat: @and
empty: -> new Atom
map: -> @bimap id, it
# TODO: This needs to be some default value.
of: -> @biof '*', it
ap: @biap
/*
Extra algebra stuff.
*/
# TODO: Need the rest of {Bi-}foldable.
foldr: (f, z) -> @bifoldr ((_, c) -> c), f, z
biof: (k, v) -> new Pred new Eq, k, v
module.exports.Atom = class Atom extends Inquire
to-string: -> ''
bimap: (f, g) -> this
bifoldr: (f, g, z) -> z
biap: (i) -> this
biap-pred: (i) -> this
/*
We don't have any type information,
so we pray that whatever these functions are,
they return something for an Atom.
Then we call the `of` method on whatever was returned,
this injects our Atom into their context.
*/
bitraverse: (f, g) ->
g-val = g this
if g-val.of or g-val.@@.of then that this else ...
module.exports.Pred = class Pred extends Inquire
to-string: -> "#{@key}#{@op}#{@val}"
/* Predicates have to shove an identity through the first func for biap. */
ap: (i) -> (new Pred @op, id, @val).biap i
bimap: (f, g) -> new Pred @op, (f @key), g @val
bifoldr: (f, g, z) -> f @key, g @val, z
/* We can use double dispatch to avoid worrying about what we're ap-ing to. */
biap: (i) -> i.biap-pred this
biap-pred: (i) -> new Pred @op, (i.key @key), i.val @val
bitraverse: (f, g) ->
f-key = f @key
g-val = g @val
/*
We need the context of the applicative we're traversing.
Assume g-val because we might be doing a `traverse`.
*/
if g-val.of or g-val.@@.of
lift-a3 ((op, key, val) -> new Pred op, key, val), (that @op), f-key, g-val
else
...
module.exports.Group = class Group extends Inquire
to-string: -> "(#{@key})#{@op}(#{@val})"
bimap: (f, g) -> new Group @op, (@key.bimap f, g), @val.bimap f, g
bifoldr: (f, g, z) -> @key.bifoldr f, g, @val.bifoldr f, g, z
biap: (i) -> new Group @op, (@key.biap i), @val.biap i
biap-pred: (i) -> new Group @op, (i.biap @key), i.biap @val
bitraverse: ...
module.exports.Wrap = class Wrap extends Inquire
to-string: -> "#{@op}(#{@key})"
bimap: (f, g) -> new Wrap @op, @key.bimap f, g
bifoldr: (f, g, z) -> @key.bifoldr f, g, z
biap: (i) -> new Wrap @op, @key.biap i
biap-pred: (i) -> new Wrap @op, i.biap @key
bitraverse: ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment