fantasyland / fantasy-land

Specification for interoperability of common algebraic structures in JavaScript
MIT License
10.11k stars 375 forks source link

Apply composition law does not equate #209

Closed jlmorgan closed 7 years ago

jlmorgan commented 7 years ago

It could be that my implementation is not correct, but here's the result of the test.

test.js

"use strict";

class Thing {
  constructor(value) {
    this.value = value;
  }
  ap(other) { return other.map(this.value); }
  map(f) { return new Thing(f(this.value)); }
}

const y = new Thing(x => x);
const left = y.ap(y.ap(y.map(f => g => x => f(g(x)))));
const right = y.ap(y).ap(y)

console.log("left === right:", left === right);
console.log("left value:", left.value.toString());
console.log("right value:", right.value.toString());

Running test.js

$ node test
left === right: false
left value: g => x => f(g(x))
right value: x => x
davidchambers commented 7 years ago

First, you shouldn't expect to be able to compare your values with ===. You'll need to define a fantasy-land/equals method. Secondly, y.ap(y) is correct (I think) but quite confusing. One can provide identity as an argument to identity (identity(identity)) but I think it wise to start with more straightforward applications such as Math.sqrt(9). For example:

'use strict';

var Z = require('sanctuary-type-classes');

function Thing(value) {
  if (!(this instanceof Thing)) return new Thing(value);
  this.value = value;
}

Thing.prototype.equals =
Thing.prototype['fantasy-land/equals'] = function(other) {
  return Z.equals(this.value, other.value);
};

Thing.prototype.ap =
Thing.prototype['fantasy-land/ap'] = function(other) {
  return Thing(other.value(this.value));
};

Thing.prototype.inspect =
Thing.prototype.toString = function() {
  return 'Thing(' + Z.toString(this.value) + ')';
};

Thing(9).ap(Thing(Math.sqrt));
// => Thing(3)

Z.ap(Thing(Math.sqrt), Thing(9));
// => Thing(3)

I hope this proves helpful. :)

safareli commented 7 years ago

also read about Comparison operators in JS

jlmorgan commented 7 years ago

My presumption on how .ap was suppose to work and what the test implied was that if the final value would ultimately just be the original identity function being passed along. ~Your correction to the implementation of .ap alters that perception as now the composition has more of an affect on the computation resulting in a differing function reference.~ My not using left.value and right.value in the check is what made the left === right not work out as they are obviously new instances thanks to .map.

const y = new Thing(x => x);
const left = y.ap(y.ap(y.map(f => g => x => f(g(x)))));
const right = y.ap(y).ap(y)

Something that would help immensely when learning this material is not having to learn Haskell in order to read JavaScript. Function signatures actually in JavaScript would help greatly. I understand that JS doesn't have types, but those things can be inferred through annotations or jsdoc stuffs.

On a side note, thanks to all for providing this reference project. = D

jlmorgan commented 7 years ago

So, for a brief moment on here, I saw:

ap(other) { return this.map(other.value); }

and above he has:

ap(other) { return Thing(other.value(this.value)); }

which is it?

safareli commented 7 years ago

you can fix your original ap this way too:

-  ap(other) { return other.map(this.value); }
+  ap(other) { return this.map(other.value); }

and if you inline definition of map you get:

ap(other) { return other.value(this.value); }
davidchambers commented 7 years ago

Function signatures actually in JavaScript would help greatly. I understand that JS doesn't have types, but those things can be inferred through annotations or jsdoc stuffs.

sanctuary-def, unlike TypeScript, can accurately express the type of ap. It's pretty noisy, though! I think learning to read Haskell type signatures is a better use of time than learning how sanctuary-def works. ;)

Haskell:

ap :: Apply f => f (a -> b) -> f a -> f b

JavaScript:

//    a :: Type
const a = $.TypeVariable('a');

//    b :: Type
const b = $.TypeVariable('b');

//    f :: Type -> Type
const f = $.UnaryTypeVariable('f');

//    ap :: Apply f => f (a -> b) -> f a -> f b
const ap = def('ap', {f: [Z.Apply]}, [f($.Function([a, b])), f(a), f(b)], Z.ap);
jlmorgan commented 7 years ago

So, does this mean that the intention of ap is to apply boxed functions to a boxed value? In a sense where [Apply] is a box, add1 increments, and mul2 multiplies by 2:

[Apply 1].ap([Apply add1]).ap([Apply mul2]) -> [Apply 4]
davidchambers commented 7 years ago

You may find the examples for Z.ap instructive, @jlmorgan. I find Haskell examples clearer, though:

λ [(*10),(*100),(*1000)] <*> [1,2,3]
[10,20,30,100,200,300,1000,2000,3000]

<*> is the name for ap in Haskell. It takes the "thing" of functions as its left operand and the "thing" of arguments as its right operand.

joneshf commented 7 years ago

ap exists in Prelude as well.

λ: ap [(*10),(*100),(*1000)] [1,2,3]
[10,20,30,100,200,300,1000,2000,3000]
davidchambers commented 7 years ago

I didn't know that, Hardy. Thanks for the tip. :)

I'll close this issue as there's nothing to fix or change, but feel free to keep commenting.

SimonRichardson commented 7 years ago

:+1:

On Mon, 12 Dec 2016, 21:35 David Chambers, notifications@github.com wrote:

I didn't know that, Hardy. Thanks for the tip. :)

I'll close this issue as there's nothing to fix or change, but feel free to keep commenting.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/fantasyland/fantasy-land/issues/209#issuecomment-266560064, or mute the thread https://github.com/notifications/unsubscribe-auth/ACcaGMkstrXtgSd7feV2oSLN8yKXupQ_ks5rHb4sgaJpZM4LKop0 .

jlmorgan commented 7 years ago

When I read the above code with the mindset of function first, it make me think the following should occur:

const mul = left => right => left * right;

Thing(mul(100)).ap(Thing(2));
// => [Thing 200]

And not how it seems implemented above which is more like:

Thing(2).ap(Thing(mul(100));
// => [Thing 200]

Thinking function-first, data-last would seem to suggest that the function would be boxed first, then applying values to that function. Similar to Function#apply or a curried Function#call. Also, where's a good place to have interactive conversations with people that know both sides of this well enough? I have other questions and don't want to drag on this "issue" too much.

davidchambers commented 7 years ago

Similar to Function#apply or a curried Function#call.

Don't contaminate your thoughts with this sort of comparison. ;)

I suggest comparing ap to map. They're very similar:

> Thing(9).map(Math.sqrt)
Thing(3)

> Thing(9).ap(Thing(Math.sqrt))
Thing(3)

The type signatures are instructive:

map :: Functor f => (a -> b) -> f a -> f b
ap  :: Apply f => f (a -> b) -> f a -> f b
joneshf commented 7 years ago

Also, where's a good place to have interactive conversations with people that know both sides of this well enough? I have other questions and don't want to drag on this "issue" too much.

As @davidchambers mentioned, please feel free to continue the discussion in this issue if you'd like. If you want more real-time communication you can join the gitter channel: https://gitter.im/fantasyland/fantasy-land Though, it's longer to get a conversation started there :\

joneshf commented 7 years ago

I didn't know that, Hardy. Thanks for the tip. :)

I think that's actually where ap comes from. :)

xgrommx commented 7 years ago

@joneshf <*> == ap and also ap doesn't exists in Prelude, ap contains in Control.Monad