Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add polymorphic type catamorphisms #280

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

gabejohnson
Copy link
Member

@gabejohnson gabejohnson commented Jan 10, 2018

As discussed in #151 (comment), #154 and #185 (comment), I propose a specification for Either by providing a definition of a folding method named either.

It isn't really a type specification, it's more of an interface or algebra specification as multiple structures could be isomorphic to Either and hence provide an either method.

Open question: Should this specification be displayed in README.md, or in a type specific file?

@i-am-tom
Copy link

i-am-tom commented Jan 10, 2018

So, Gabe and I have talked about this, and I thought it would be a good time to share a proposal I wrote on the back of a figurative beer mat about 6 months ago.

Types are really simple, especially when church-encoded. Here are a bunch of them:

Maybe.js

exports['@@fantasyland-maybe/just'] = x => { cata: (_, f) => f(x) }
exports['@@fantasyland-maybe/nothing'] = x => { cata: (x, _) => x }

Either.js

exports['@@fantasyland-either/left'] = x => { cata: (f, _) => f(x) }
exports['@@fantasyland-either/right'] = x => { cata: (_, g) => g(x) }

Reader.js

exports['@@fantasyland-reader/reader'] = f => { cata: env => f(env) }

For all these types, there are well-defined instances for the various typeclasses:

Later in Maybe.js...

...

exports['@@fantasyland/map'] = f => m => m.cata({
  nothing: exports['@@fantasyland-maybe/nothing'],
  just: f
})

So on, so forth. When reduced to Church encoding, everything has a nice, clean implementation that is as basic as it could possibly be. It is also enough of a structure to define all the implementations for all the spec.


NOW, say I'm writing a library, ReallyCoolMaybe, that I want to implement the fantasyland spec. I write it like this:

const MyReallyCoolMaybe = Object.assign({}, require('fantasyland/types/Maybe'), {
  // My really cool methods

  just: ... // My own error checking on my own constructor
})

I can write my library exactly how I like, with my own methods, never feeling pinned to a particular implementation of the spec. I can even worry about tail-recursion and the like by overriding the basic fold (providing that the new fold is indistinguishable to the user).

I now have a common interface, which means values of my custom type will work with any other FL-compatible library. I now have my own methods, so I'm not restricted by a Fantasy Land implementation. This, to me, is the minimum possible structure we can require, while retaining all the utility. I have all the FL interfaces implemented, as well as the "constructors" for building church-encoded types, which I am then free to augment as I please. The point is that, whatever the situation, I know that a fantasy-land-compatible type has this cata (or whatever colour we paint the shed) function with a well-documented signature, and anything else is vendor-specific frill.

My 2c, anyway.

@gabejohnson gabejohnson mentioned this pull request Jan 11, 2018
@gabejohnson gabejohnson changed the title Add Either type specification Add polymorphic type catamorphisms Jan 17, 2018
README.md Outdated

Array.prototype['fantasy-land/identity'] = function identity(f) {
return f(this[0]);
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's safe to define Array#fantasy-land/identity since [] exists.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are correct David. Perhaps identity isn't the right example to use.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[] :: Identity Undefined :p

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about

function Some(x) {
  this.x = x;
};
var None = {};

Some.prototype['fantasy-land/maybe'] = function maybe(_, f) {
  return f(this.x);
};
None['fantasy-land/maybe'] = function maybe(d, _) {
  return d;
};

Array.prototype['fantasy-land/maybe'] = function maybe(d, f) {
  if (this.length === 0) return d;
  return f(this[0]);
};

function maybe(d, f, m) {
  return m['fantasy-land/maybe'](d, f);
}

var head = maybe.bind(null, None, Some);

var none = head([]);
none // None

var some = head([1, 2, 3]);
some // Some(1);

function fromMaybe(d, m) {
  return maybe(d, x => x, m);
}
fromMaybe(0, none) // 0
fromMaybe(0, some) // 1

?
Edit: Added maybe and fromMaybe for clarity

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that example, Gabe. 👍

@gabejohnson gabejohnson mentioned this pull request Feb 16, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants