sanctuary-js / sanctuary-type-classes Goto Github PK
View Code? Open in Web Editor NEW:rainbow: Standard library for Fantasy Land
License: MIT License
:rainbow: Standard library for Fantasy Land
License: MIT License
There appears to be a problem at Gitter's end. I'll try again tomorrow.
fantasyland/fantasy-land@v3.1.0...v3.2.0
I believe that @gabejohnson is already working on this change. Could you confirm this, Gabe?
Object
appears to be compatible with Traversable
// (TypeRep f, (a -> f b), StrMap a) -> f (StrMap b)
> Z.traverse(S.Maybe, S.parseInt(16), {a:'A', b:'B', c:'C'})
Just({a: 10, b: 11, c: 12})
> Z.traverse(Array, x => [x], {a: 'A', b: 'B', c: 'C'});
[{a: 'A', b: 'B', c: 'C'}]
This will allow sequence
as well
// (TypeRep f, StrMap (f a)) -> f (StrMap a)
> Z.sequence(S.Maybe, {a: S.Just('A'), b: S.Just('B'), c: S.Just('C')})
Just({a: 'A', b: 'B', c: 'C'})
EDIT: I originally thought it was worse, I thought Buffer#toString
throws an error when not given its encoding argument. Turns out the default behavior just returns a string of Unicode identifiers.
> Z.toString({x: new Buffer([1,2,3])})
'{"x": \u0001\u0002\u0003}'
Should we special-case this? Maybe provide an override toString
function for Buffer somehow?
EDIT: I would say "no" now that I've realized that it's not throwing an error.
In the Gitter room David demonstrated that ap
can be implemented for Object
. of
is trivial.
// ap :: StrMap (a -> b) -> StrMap a -> StrMap b
Z.ap({}, {})
// {}
Z.ap({x: S.words, y: S.words}, {})
// {}
Z.ap({}, {x: 'foo bar', y: 'baz'})
// {}
Z.ap({x: S.words, y: S.words}, {x: 'foo bar'})
// {x: ['foo', 'bar']}
Z.ap({x: S.words, y: S.words}, {x: 'foo bar', y: 'baz'})
// {x: ['foo', 'bar'], y: ['baz']}
// of :: Object a -> StrMap a
Z.of(Object, {a: 1})
// {a : 1}
Z.of(Object, 1)
// {}
@davidchambers do I have this right?
IE9 behaviour:
> Z.toString('foo')
'new String(<Circular>)'
Expected behaviour:
> Z.toString('foo')
'"foo"'
Ecma-262 Edition 5.1 § 15.3.4.4:
The thisArg value is passed without modification as the this value. This is a change from Edition 3, where a undefined or null thisArg is replaced with the global object and ToObject is applied to all other values and that result is passed as the this value.
IE9 does not respect the 'use strict';
pragma so still has the ES3 behaviour. When evaluating String$prototype$toString.call('foo')
, 'foo'
is promoted to a String object (which explains the new String(…)
wrapper in the return value). This would lead to an infinite promotion–demotion loop (since String$prototype$toString
invokes valueOf
) were it not for the circular-reference detector, which is triggered when x
is 'foo'
and $seen
already contains 'foo'
(resulting in <Circular>
).
I don't know whether it's possible to fix this without giving up our elegant implementation. For now, at least, this library does not support IE9.
@masaeedu shared this snippet in the Gitter room:
export const capitalizeKeys = o => pipe([ keys, map(k => ({ [k.toUpperCase()]: o[k] })), reduce(concat)({}) ])(o);
After some discussion we agreed this code could be significantly nicer if S.pairs
returned an array of honest-to-goodness pairs. One could then write:
// capitalizeKeys :: StrMap a -> StrMap a
const capitalizeKeys = S.pipe ([S.pairs, S.map (S.mapLeft (S.toUpper)), S.fromPairs]);
This is not possible because ['foo', true]
does not support bimap
/mapLeft
. It could do, but a problem arises when one considers map
. How should S.map (S.toUpper) (['foo', 'bar'])
evaluate? If one sees ['foo', 'bar']
as a member of Array String
, the result should be ['FOO', 'BAR']
. If, on the other hand, one sees ['foo', 'bar']
as a member of Pair String String
, the result should be ['foo', 'BAR']
. (Treating ['foo', true]
as Pair String Boolean
while treating ['foo', 'bar']
as Array String
is out of the question.)
I'm reluctant to support bimap
and mapLeft
while leaving map
out in the cold. I would rather not treat two-element arrays specially at all, and have functions such as S.pairs
use the real Pair a b
type soon to be released as sanctuary-pair.
@masaeedu posed an intriguing question:
Is
mapRight
not a thing?
It certainly could be a thing. It's trivial to derive from bimap
, as was the case with mapLeft
. We could provide the following four functions:
map :: Functor f => (a -> b) -> f a -> f b
bimap :: Bifunctor f => (a -> b) -> (c -> d) -> f a b -> f c d
mapLeft :: Bifunctor f => (a -> b) -> f a c -> f b c
mapRight :: Bifunctor f => (b -> c) -> f a b -> f a c
mapRight
would be equivalent to map
for Either a b
and various other types. For Pair a b
, though, or Array2 a b
as it will be known if sanctuary-js/sanctuary-def#182 is merged, mapRight
would behave differently from map
:
> S.map (S.toUpper) (['foo', 'bar'])
['FOO', 'BAR']
> S.mapRight (S.toUpper) (['foo', 'bar'])
['foo', 'BAR']
> S.mapRight (S.not) (['foo', true])
['foo', false]
I'd still like to consider having S.pairs
return a real pair rather than a two-element array, but improving the usefulness of values such as ['foo', true]
may be a good idea regardless. What do you think?
fantasyland/fantasy-land@v3.2.0...v3.3.0
Feel free to assign this issue to yourself if you would like to work on it, @gabejohnson.
It would be nice to be able to access the same facilities Sanctuary uses to define instances for types like Array
, StrMap
, etc. to define instances for even more "unwrapped" values, with type membership dictated by user-specifiable predicates.
I think this might eventually boil down to the same feature as "implement static land support", but there might be other ways to accomplish this.
Dependent on fantasyland/fantasy-laws#1
What's the appropriate type of Z.of
? There appear to be two options:
of :: Applicative f => (f a, b) -> f b
of :: Applicative f => ({ fantasy-land/of :: a -> f a }, b) -> f b
The advantage of the first option is that given Applicative f => f a
we can find the of
function in either of the valid locations:
typeof x['fantasy-land/of'] === 'function' ? x['fantasy-land/of']
: x.constructor['fantasy-land/of']
With the second option the caller would need to do this work:
Z.of(typeof x['fantasy-land/of'] === 'function' ? x : x.constructor, 42)
We shouldn't optimize for the case in which we already have a value of the type, though. After all, the purpose of of
is to allow us to create values of the type so we shouldn't need to create a value of the type in order to create a value of the type! Thus the second option seems more sensible. Do others agree?
Whatever we decide here should determine the type of Z.chainRec
as well. Z.empty
is a different matter because an empty
method is strictly more powerful than an empty
function (though this will no longer apply if and when fantasyland/fantasy-land#164 is merged).
sanctuary-type-classes should support Set
, Map
, and to a lesser extent, WeakSet
and WeakMap
.
empty
- Set
, Map
, WeakSet
, WeakMap
of
- Set
, Map
WeakSet
, WeakMap
zero
- Set
, Map
, WeakSet
, WeakMap
equals
- Set
, Map
toString
- Set
, Map
concat
- Set
, Map
map
- Set
, Map
ap
- Set
, Map
alt
- Set
, Map
reduce
- Set
, Map
chainRec
- Set
chain
- Set
traverse
- Set
extend
- Set
S.is(function Number () {}, 4)
// -> true
While documentation says:
is :: TypeRep a -> Any -> Boolean
Takes a type representative and a value of any
type and returns true
iff the given value is of the specified type.
Subtyping is not respected.
EDIT: Sorry, wrong repo.
EDIT2: sanctuary-js/sanctuary#502.
I just realized it would be strange to use
Z.TypeClass
in a project and for it to be the only uncurried function. -- @davidchambers
Our equals implementations look like we call fantasy-land/equals
twice, I think it unnecessary. If we try to
check equality of List
or Array
, we will iterate the structure twice! So, let's remove the second check.
Sanctuary:
> Z.concat({x: 1, y: 1}, {y: 2, z: 2})
{x: 1, y: 2, z: 2}
Haskell:
> M.fromList [('x', 1), ('y', 1)] <> M.fromList [('y', 2), ('z', 2)]
fromList [('x', 1), ('y', 1), ('z', 2)]
We didn't discuss this behaviour in #2 as far as I can see; I think we diverged from Haskell's behaviour unintentionally.
/cc @puffnfresh
@masaeedu was asking about implementing Haskell's foldMap :: Monoid m => (a -> m) -> t a -> m
In Haskell it's a class member of Foldable
(as well as an alternate minimal definition).
I think reduceMap :: (Foldable t, Monoid m) => TypeRep m -> (a -> m) -> t a -> m
would be an excellent addition to this project.
For example, Array a
satisfies the requirements of Setoid if and only if a
satisfies the requirements of Setoid. Z.Setoid.test
currently ignores this constraint:
> var Z = require ('sanctuary-type-classes')
> var Useless = require ('sanctuary-useless')
> Z.Setoid.test (Useless)
false
> Z.Setoid.test ([Useless])
true
Z.Setoid.test ([])
and Z.Setoid.test ([1, 2, 3])
should evaluate true
but Z.Setoid.test ([Useless])
should evaluate false
. The behaviour of other test
methods should also change.
The filterM
in this library has this type
filterM :: (Alternative m, Monad m) => (a -> Boolean, m a) -> m a
whereas filterM
in Haskell is (reframed slighly):
filterM :: (Monad m, Traversable f) => (a -> m Boolean, f a) -> f a
The important part is that the predicate returns inside the monad. This allows for example succinct implementation of powerset:
powerset :: [a] -> [[a]]
powerset = filterM (const [False, True])
as well as enabling logging/state/etc inside the predicate:
countingFilterM :: (a -> Bool) -> [a] -> State Int [a]
countingFilterM p = filterM (\ x -> modify succ >> return (p x))
Please consider exporting filterM
to have the same functionality as in Haskell's Control.Monad.
title say it all, small repro:
const flip = (f, x) => map(T(x), f)
flip(a => b => a - b, 1)(5) //> 4
flip({a: x => x - 1, b: x => x + 1}, 1) //> {a: 0, b: 2}
See https://hackage.haskell.org/package/functors-0.1/docs/Data-Functor-Syntax.html#v:flip
@gabejohnson in sanctuary-js/sanctuary#435 (comment):
I don't know that we necessarily should match the semantics of
S.concat
as it works on objects in general. I think we may [inadvertently] cause confusion by not explicitly definingStrMap
. In my opinion inherited properties should not be copied for aStrMap
.
This is worth discussing. Here's an example which is not completely contrived:
function DigitCounter() {}
DigitCounter.count = ($counter, s) => {
for (let idx = 0; idx < s.length; idx += 1) {
const c = s[idx];
if (/\d/.test(c)) $counter[c] += 1;
}
};
for (let n = 0; n <= 9; n += 1) {
DigitCounter.prototype[n] = 0;
}
const $counter = new DigitCounter();
DigitCounter.count($counter, String(Math.PI));
$counter;
// => {'1': 2, '2': 1, '3': 3, '4': 1, '5': 3, '6': 1, '7': 1, '8': 1, '9': 3}
Z.map(x => x, $counter);
// => {'0': 0, '1': 2, '2': 1, '3': 3, '4': 1, '5': 3, '6': 1, '7': 1, '8': 1, '9': 3}
Note that mapping x => x
over $counter
"promotes" the inherited property to an own property. The question is whether the inherited property should be ignored.
I believe the current behaviour is not just defensible but preferable. My reasoning is as follows:
We currently use for ... in
which respects all enumerable properties (both own and inherited). It ignores non-enumerable properties such as toString
and valueOf
. It's unlikely that people are affected one way or the other by our handling of enumerable inherited properties; I imagine that close to 100% of string maps are plain objects.
Although obscure, the example above shows that respecting enumerable inherited properties can be useful. We should not disallow such code without good reason.
Z.concat
is one of the library's foundational functions. Performing a hasOwnProperty
check for each key would affect the performance of several other functions as well as of Z.concat
itself.
I look forward to understanding your reasons for suggesting that all enumerable inherited properties should be ignored, @gabejohnson. You may change my mind. :)
sanctuary-js/sanctuary-def#164 contains related discussion about what constitutes a record field.
Initially suggested here: #147 (comment)
When Z-functions are given invalid input, they throw very nondescript error messages. We may want to improve this.
$ bower register sanctuary-type-classes git://github.com/sanctuary-js/sanctuary-type-classes.git
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.