Giter VIP home page Giter VIP logo

Comments (15)

Twisol avatar Twisol commented on July 18, 2024

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.

hughfdjackson avatar hughfdjackson commented on July 18, 2024

+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.

Twisol avatar Twisol commented on July 18, 2024

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.

hughfdjackson avatar hughfdjackson commented on July 18, 2024

@Twisol

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.

Twisol avatar Twisol commented on July 18, 2024

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.

puffnfresh avatar puffnfresh commented on July 18, 2024

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.

Raynos avatar Raynos commented on July 18, 2024

@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.

puffnfresh avatar puffnfresh commented on July 18, 2024

@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.

Twisol avatar Twisol commented on July 18, 2024

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.

hughfdjackson avatar hughfdjackson commented on July 18, 2024

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.

hughfdjackson avatar hughfdjackson commented on July 18, 2024

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.

Raynos avatar Raynos commented on July 18, 2024
function of(monad, value) {
  return (monad.of || monad.constructor.of)(value)
}

from fantasy-land.

puffnfresh avatar puffnfresh commented on July 18, 2024

@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)

@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.

Raynos avatar Raynos commented on July 18, 2024

@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 avatar hughfdjackson commented on July 18, 2024

@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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.