During a recent conversation between @davidchambers and I, we came up with a way to define an Applicative Const Functor that can be used to implement Lenses, without needing a Monoid up front. The conversation is starting to escape my memory, so I'll just put the idea down here lest we forget.
The Problem
As I understand, the need for a Monoid on Const currently blocks progress on this library. In particular, there currently is no way to implement Fantasy Land's Applicative for Const in the way Haskell does:
data Const a b = Const {getConst :: a}
instance Monoid m => Applicative (Const m) where
pure _ = Const mempty
(<*>) = coerce (mappend :: m -> m -> m)
Here we see Applicative implemented for Const m by restricting to ms that have Monoid, and just putting mempty as the actual context-value of the Const. Haskell then relies on the type system to make it so when getConst is resolved, it gets a value of the correct specific Monoid.
In JavaScript (and specifically, Fantasy Land as it is right now), we don't have a way to carry type information along with our Const instances so that when getConst is used, we can put a value of the correct Monoid there:
Const['fantasy-land/of'] = function(_){
return new Const(/* What do I put here?! */)
}
Solutions
An alternative that I came up with is to have a binary type implementation for Const, where reliance on the type system is removed, and the fallback value is provided by the user when evaluating the Const:
import {tagged} from 'daggy';
const Constant = tagged('Const', {
Const: ['context'],
NoConst: [],
});
// We can implement pure by simply returning NoConst:
Constant['fantasy-land/of'] = function(_){
return Constant.NoConst;
};
Constant.prototype['fantasy-land/map'] = function(_){
return this;
};
Constant.prototype['fantasy-land/ap'] = function(other){
return other.cata({
NoConst: () => this,
Const: otherContext => this.cata({
NoConst: () => other,
Const: context => Constant.Const (otherContext['fantasy-land/concat'](context))
})
});
};
// The provision of a Monoid is moved all the way to the evaluation step:
const getConst = monoidRepr => c => c.cata({
NoConst: () => monoidRepr['fantasy-land/empty'](),
Const: context => context
});
A few observations:
- Const itself is almost Monoidal here, were it not for the other type param (
NoConst ++ Const a = Const a, etc)
- It's the classic layer of indirection: represent your effects instead of embedding them, and run them during interpretation
What do you think? Does this approach have any benefits over the one taken in #1 ?
/cc @masaeedu
During a recent conversation between @davidchambers and I, we came up with a way to define an Applicative Const Functor that can be used to implement Lenses, without needing a Monoid up front. The conversation is starting to escape my memory, so I'll just put the idea down here lest we forget.
The Problem
As I understand, the need for a Monoid on Const currently blocks progress on this library. In particular, there currently is no way to implement Fantasy Land's Applicative for Const in the way Haskell does:
Here we see
Applicativeimplemented forConst mby restricting toms that haveMonoid, and just puttingmemptyas the actual context-value of theConst. Haskell then relies on the type system to make it so whengetConstis resolved, it gets a value of the correct specific Monoid.In JavaScript (and specifically, Fantasy Land as it is right now), we don't have a way to carry type information along with our Const instances so that when
getConstis used, we can put a value of the correct Monoid there:Solutions
An alternative that I came up with is to have a binary type implementation for Const, where reliance on the type system is removed, and the fallback value is provided by the user when evaluating the Const:
A few observations:
NoConst ++ Const a = Const a, etc)What do you think? Does this approach have any benefits over the one taken in #1 ?
/cc @masaeedu