See Conal Elliott's paper on denotational design and type class morphisms, or any of his recent talks based on it. One of the main points in the paper is that defining type class instances "can sometimes be derived mechanically from desired type class morphisms". Applying that to define, say, Promise's functor instance might go something like (using haskell-like syntax here, where fmap
is the functor operation):
fmap f (Promise a) = Promise (fmap f a)
IOW, an fmapped future value is the future fmapped value. Or as it's stated in the paper: "The instance’s meaning follows the meaning’s instance"
Functor Example
That opens up some interesting possibilities .. for example:
fmap f (Promise [a]) = Promise (fmap f [a])
If we have a promise for an array of numbers and want add 1 to each number in the array, in creed 1.0, we have to map twice:
import { fulfill } from 'creed';
const ints = fulfill([1,2,3]);
const intsPlusOne = ints.map(a => a.map(x => x+1));
However, using the fmap
definition above:
import { fulfill } from 'creed';
const ints = fulfill([1,2,3]);
const intsPlusOne = ints.map(x => x+1);
The exact same thing would work with a promise for a single number:
import { fulfill } from 'creed';
const one = fulfill(1);
const two = one.map(x => x+1);
Or for a promise for a tree of numbers, or a promise for a promise of a number, and so on. Applicative would likely be similar to Functor.
Questions
What about type classes for which there are two (or more) interesting behaviors? For example, creed's Promise has a monoid instance, with never
as the identity element and concat
returning the earlier of the two promises to settle. IOW, it doesn't rely on the monoid instance of the value type, but rather only on the time of the two promises.
That monoid definition is useful because it represents racing.
A monoid instance defined using the ideas in the paper would use the identity element of the value type (How would this even work??), and concat
would apply the concat
operation of the value type. IOW, concatenating two future arrays would produce a future concatenated array.
concat (Promise [a]) (Promise [a]) = Promise (concat [a] [a])
That's probably also useful, and I have no idea how to deal with wanting two monoid instances for Promise.
I also have no idea yet how to define a monad instance for Promise using this approach, if it's useful, or even possible. As far as I can tell, the paper doesn't much (if any) guidance on applying the technique for monad instances.