Comments (15)
constructor
is part of every Javascript object by default and requires no extra work to deal with. of
belongs best on the constructor, because otherwise you would need an existing object of the type to create a new object.
from fantasy-land.
+1 - @Raynos, I was just writing the same ticket.
@Twisol of
can be made accessible on the prototype, as is commonly available (whether directly in pure prototypal inheritance, or via Ctor.prototype). It'd be better not to have to either mislead with the structure of the object, or otherwise impact on the creation of the object on the implementors part.
For instance, if I were to create a monad using a pure prototypal style, I could happily do:
var Base = require('viral').Base
var Maybe = Base.extend({
of: function(v){ /* ... */ },
then: function(f){ /* ... */ }
})
OR the traditional constructor style:
var Maybe = function(){}
Maybe.prototype.of = function(v){ /* .. */ }
Maybe.prototype.then = function(f){ /* .. */ }
OR adhoc
var maybe = {
of: function(v){ /* ... */ },
then: function(f){ /* ... */ }
}
from fantasy-land.
Hmm. I agree that there are certainly many ways to construct Javascript objects. I'm not sure if the ad-hoc method is a good match for any kind of monad, however: then
(or chain
or whatever we call it) is expected to return a new monad of the same type, and you'll end up refactoring the object creation into its own function anyway just to keep your code DRY.
The viral example makes a good point. I think we should err on the side of "don't allow too many deviations from the core", personally. Although I understand the scoping benefit, I also think it's a pretty odd thing to call of
on an instance.
from fantasy-land.
Hmm. I agree that there are certainly many ways to construct Javascript objects. I'm not sure if the ad-hoc method is a good match for any kind of monad, however: then (or chain or whatever we call it) is expected to return a new monad of the same type, and you'll end up refactoring the object creation into its own function anyway just to keep your code DRY.
You're certainly right that ad-hoc would be an outside case. A more common case would be the almost-constructor-but-without-new case:
var Maybe = function(){
return {
of: function(v){ /* ... */ },
then: function(){ /* ... */ }
}
}
The viral example makes a good point. I think we should err on the side of "don't allow too many deviations from the core", personally. Although I understand the scoping benefit, I also think it's a pretty odd thing to call of on an instance.
I agree that there should be a narrow definition for what counts as a monad; however, I don't think that definition should make avoidable demands on the implementor. That increases the obligation on the implementor, rather than decreasing it, as a lightweight spec would.
from fantasy-land.
A more common case would be the almost-constructor-but-without-new case:
Yes, you're right; that's the approach when.js uses.
If this change happens, I think we should find a better name than of
- or at least a different name for the one that appears on the prototype. It seems semantically similar to Applicative's *>
, but it doesn't quite have the right type.
from fantasy-land.
My reason for using of
was that it's already on Array - thus accessible from a Array value via the constructor
. ES6 Array is not a monad but there's always hope and I wanted to align with ES6, instead of coming up with yet another name.
I also thought there was a somewhat interesting rule that could be derived. Haskell's type classes are explicit but we have to pass explicit dictionaries around in JavaScript. Anything that needs to dispatch on an input type to a method gets translated like so:
// bind :: m a -> (a -> m b) -> m b
m.prototype.chain = function(f) { /* ... */ };
Whereas things that need to dispatch on return type get translated like so:
// return :: a -> m a
m.of = function(a) { /* ... */ };
And then when we need to dispatch on return type, we either make functions take a value called constructor
and pass m
to it or if we have an instance of it, value.constructor
.
So the rules I had in my head:
- Dispatching on inputs, go on value
- Dispatching on return type, go on value's constructor object
Now, as observed, not everything is constructed using a Function so won't have a constructor
object. That's fine since there's no reason why we can't do:
function Maybe(){
return {
constructor: {
of: function(v){ /* ... */ }
},
then: function(){ /* ... */ }
};
}
That shouldn't break anything, right?
from fantasy-land.
@pufuwozu we can't do .constructor.of
it breaks 100%.
Let's imagine I have a monad called Continuable<T>
. This Monad is a function
// Continuable<T>: (callback: (err: Error, value: Value) => void) => void
// readFile := (file: String) => Continuable<T>
function readFile(file) {
return function continuable(callback) {
fs.readFile(file, callback)
}
}
So it's like a promise but function / callback based.
Now I want it to be a monad. So I write an asMonad function that takes a Continuable function and implements then
and of
.
If I was forced to implement it as .constructor.of
then I would set a property of
on continuable.constructor
which is Function.prototype
. The alternative is prototypical inheritance for functions which is a nightmare. The other alternative is monads can't be functions.
"monads can't be functions" in a functional language sounds like a really bad idea.
// Monad<A>: { then: (lambda: A => Monad<B>) => Monad<B>, of: (A) => Monad<A> }
// asMonad := (Continuable<T>) => Monad<T>
function asMonad(continuable) {
// then (this: Continuable<A>, lambda: (A) => Continuable<B>) => Continuable<B>
continuable.then = function then(lambda) {
return flatten(map(lambda)(continuable))
}
continuable.of = of
return continuable
}
// map := (lambda: (A) => B) => (Continuable<A>) => Continuable<B>
function map(lambda) {
return function duplex(source) {
return function continuable(callback) {
source(function (err, value) {
return err ? callback(err) : callback(null, lambda(value))
})
}
}
}
// flatten := (Continuable<Continuable<T>>) => Continuable<T>
function flatten(source) {
return function continuable(callback) {
source(function (err, next) {
return err ? callback(err) : next(callback)
})
}
}
// of := (value: Value) => Continuable<Value>
function of(value) {
return function continuable(callback) {
callback(null, value)
}
}
from fantasy-land.
@Raynos urgh, that's a really good point. We should allow that.
Of course, you could do this:
continuable.constructor = {of: of}
But that's really not nice.
We will need to pass dictionaries around and I thought the constructor was a great way to do that:
function point(dict, a) {
return dict.of(a);
}
point(Array.constructor, 1);
Maybe we could add another object that it try first? Then it could be like:
function returnDict(a) {
return a.returnDict || a.constructor;
}
Which allows us to do explicit dictionary passing like so:
point(returnDict(Array), 1);
point(returnDict(asMonad(x)), 1);
Is that also broken?
from fantasy-land.
As far as the core types go, we could do something newtype
-ish and wrap such values in a wrapper type. If you're dealing with monads in general, you wouldn't care whether the thing you're dealing with is a Function or an MFunction. And if you're dealing directly with Function, you don't need the wrapper.
The downside, of course, is when you're interfacing between generic code and specialized code. Then you have to muck with the wrapper explicitly, and that's not so fun.
from fantasy-land.
My reason for using of was that it's already on Array - thus accessible from a Array value via the constructor. ES6 Array is not a monad but there's always hope and I wanted to align with ES6, instead of coming up with yet another name.
That's understandable, but I still think a 'what on earth..' price gets paid - which is a higher one.
I also thought there was a somewhat interesting rule that could be derived. Haskell's type classes are explicit but we have to pass explicit dictionaries around in JavaScript. Anything that needs to dispatch on an input type to a method gets translated like so:
// bind :: m a -> (a -> m b) -> m b
m.prototype.chain = function(f) { /* ... */ };
Whereas things that need to dispatch on return type get translated like so:// return :: a -> m a
m.of = function(a) { /* ... */ };
And then when we need to dispatch on return type, we either make functions take a value called constructor and pass m to it or if we have an instance of it, value.constructor.So the rules I had in my head:
Dispatching on inputs, go on value
Dispatching on return type, go on value's constructor object
While this is very clean, JavaScript has a far, far more casual relationship with the idea of a 'type', meaning that the interface spec only targets a particular style of implementing object creation that's nowhere near universal (as Haskell's are necessarily)
Now, as observed, not everything is constructed using a Function so won't have a constructor object. That's fine since there's no reason why we can't do:
function Maybe(){
return {
constructor: {
of: function(v){ /* ... / }
},
then: function(){ / ... */ }
};
}
That shouldn't break anything, right?
To my mind, there certainly is a reason - constructor is now not a function, and certainly not the constructor of any of any object in the prototype chain. This breaks the principle of least surprise quite badly, even if it would 'work'.
As with my first reply in this comment, you pay more 'what on earth' prices.
from fantasy-land.
One more thought RE array interoperability:
If you want to be able to interop with Array defined directly with the literal syntax/new Array, then you have to add a then
(or chain
, etc) method to Array.prototype in any case. At that point, you can simply Array.prototype.of = Array.of.
However, I'm sure that most people's eyes would pop at that - and that most practical monads based on arrays would wrap them:
var List = function(array){
this.array = array
}
List.of = function(v){ if (v instanceof List) return v; else new List(v) }
List.prototype.then = function(){ /* ... */ }
To my mind, this suggests that interop with Array.of is a moot point.
from fantasy-land.
function of(monad, value) {
return (monad.of || monad.constructor.of)(value)
}
from fantasy-land.
@hughfdjackson that of
is very broken. The specification forbids checking anything about the input value (i.e. you can't do instanceof
on List to get different behaviour). It breaks some laws - for example the left identity law for a monad:
of(a).chain(f)
is equivalent tof(a)
@Raynos I didn't like that at first but I think it's the best thing we can do. The specification should allow return-based methods to live on the value or the constructor. Sound good?
from fantasy-land.
@pufuwozu that works for me. You can simplify it by saying "methods should live on the value only".
We may make unnecessary problems by trying too hard to do idiomatic prototypical types.
from fantasy-land.
@hughfdjackson that of is very broken. The specification forbids checking anything about the input value (i.e. you can't do instanceof on List to get different behaviour). It breaks some laws - for example the left identity law for a monad:
of(a).chain(f) is equivalent to f(a)
I concede that my implementation may be awful - but i believe the placement to be correct ;)
from fantasy-land.
Related Issues (20)
- [Question] Type signature for ap HOT 2
- Adding Folktale to implementations.md
- Equivalent for Serial typeclass? HOT 1
- Is really necessary specify "No parts of <...> value should be checked"? HOT 11
- Add Traversable1 HOT 3
- Help understanding use of identity in Traversable laws HOT 2
- Fantasy Land specification version number HOT 1
- Switch to Symbols HOT 2
- Wrong argument in Traversable HOT 4
- Is monet.js compatible? HOT 1
- Why FL specifies the ChainRec typeclass when there is the trampoline monad? HOT 7
- What does "and" mean? HOT 2
- Can someone explain me how I should understand Semigroup? HOT 5
- Remove all OOP features HOT 21
- Traversable and Foldable use multiple variables as arguments HOT 12
- Profunctor should require Contravariant HOT 10
- Implementation of `Compose` is confusing HOT 3
- should Chain properties depends on fantasy-land/ap (and possibly .map)? HOT 4
- Why putting "fantasy-land/" before everything in readme? HOT 3
- Is `Pair` a valid instance of `MonadRec`?
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from fantasy-land.