sanctuary-js / sanctuary

:see_no_evil: Refuge from unsafe JavaScript
https://sanctuary.js.org
MIT License
3.03k stars 94 forks source link

ap on objects that have varying value types #576

Closed shdpl closed 6 years ago

shdpl commented 6 years ago

I'm trying switch from ramda to sanctuary which seems to be better designed library. I don't know lots of the 'pro' functional semantics, but from my perspective having error like that is not intuitive. Do you treat it as a bug, or you enforce object values to be typed the same?

import { ap } from 'sanctuary';

const ok = {list: [1,2,3]};
const ko = {list: [1,2,3], obj: {}};

const func = ap ({list: _=>_});

console.log(func(ok));
// { list: [ 1, 2, 3 ] }
console.log(func(ko));
// /home/shd/src/ebms/cmpp-ea-mgmt-tool/node_modules/sanctuary-def/index.js:2421
//     if (either.isLeft) throw either.value ();
//                        ^

// TypeError: Type-variable constraint violation

// ap :: Apply f => f (a -> b) -> f a -> f b
//                                  ^
//                                  1

// 1)  [1, 2, 3] :: Array Number, Array FiniteNumber, Array NonZeroFiniteNumber, Array Integer, Array NonNegativeInteger, Array ValidNumber
//     {} :: Object, StrMap c

// Since there is no type of which all the above values are members, the type-variable constraint has been violated.

//     at typeVarConstraintViolation (/home/shd/src/ebms/cmpp-ea-mgmt-tool/node_modules/sanctuary-def/index.js:2327:12)
//     at undefined.value (/home/shd/src/ebms/cmpp-ea-mgmt-tool/node_modules/sanctuary-def/index.js:1165:20)
//     at assertRight (/home/shd/src/ebms/cmpp-ea-mgmt-tool/node_modules/sanctuary-def/index.js:2421:37)
//     at /home/shd/src/ebms/cmpp-ea-mgmt-tool/node_modules/sanctuary-def/index.js:2515:27
//     at Object.<anonymous> (/home/shd/src/ebms/cmpp-ea-mgmt-tool/s_test.js:9:13)
//     at Module._compile (module.js:577:32)
//     at loader (/home/shd/src/ebms/cmpp-ea-mgmt-tool/node_modules/babel-register/lib/node.js:144:5)
//     at Object.require.extensions.(anonymous function) [as .js] (/home/shd/src/ebms/cmpp-ea-mgmt-tool/node_modules/babel-register/lib/node.js:154:7)
//     at Module.load (module.js:494:32)
//     at tryModuleLoad (module.js:453:12)
davidchambers commented 6 years ago

Let's consider the general type of S.ap:

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

We could replace Apply f => f with StrMap, giving:

ap :: StrMap (a -> b) -> StrMap a -> StrMap b

Here's an example of this type in use:

> S.ap ({x: Math.sqrt, y: S.add (1), z: S.sub (1)}) ({w: 4, x: 4, y: 4})
{x: 2, y: 5}

{list: [1,2,3], obj: {}}, though, is not a string map: its values are of different types. It is a record:

{list: [1,2,3], obj: {}} :: { list :: Array Number, obj: {} }, { list :: Array Number, obj :: StrMap Boolean }, ...

{list: [1,2,3], obj: {}} is a member of infinitely many record types due to the polymorphic nature of {}, but none of these types is compatible with S.ap.

S.ap is not intended to work with record types. One could, though, define func like this:

//    func :: { list :: Array a, obj :: b } -> { list :: Array a, obj :: b }
const func = record => ({
  list: S.I (record.list),
  obj: record.obj,
});

I hope this is helpful, @shdpl. :)

shdpl commented 6 years ago

I was hoping for ap (a) partial determining expected b type by a (as it doesn't need "interface" for all keys, just the ones it's using). I'll fallback to use sanctuary+ramda, or ramda for the time being. Thank you.