-
-
Notifications
You must be signed in to change notification settings - Fork 95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fantasy land: add Fantasy Land functions #216
Conversation
Ah so this is where old |
index.js
Outdated
} | ||
|
||
}(function(R, $) { | ||
}(function(R, λ, $) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the quickest way to type this symbol without having to resort to copy and paste?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure. I placed it in the l
register in Vim so I was able to type " l p to insert it.
We should probably switch to an identifier that's easier to type. _
, perhaps? I like one-character identifiers for dependencies we reference dozens of times. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
L
for lambda?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, L
sounds good.
The problem actually lies with optional non-capturing groups. Since there's no type of which both S.map(S.I, /(foo)?(bar)?/.exec('foobar'));
// => ['foobar', 'foo', 'bar']
S.map(S.I, /(foo)?(bar)?/.exec('foo'));
// ! TypeError: Type-variable constraint violation
//
// map :: (Functor a, Functor b) => Function -> a -> b
// ^
// 1
//
// 1) ["foo", "foo", undefined, "index": 0, "input": "foo"] :: Array ???
//
// Since there is no type of which all the above values are members, the type-variable constraint has been violated. The problematic expression is |
I agree completely, it's better to deal with these sort of oddities in the safety of a function's scope rather than having them leak out, even if it's at the cost of the implementation's elegance. |
61547af
to
10f9831
Compare
32436ce
to
1f11c7d
Compare
ce4f395
to
082d153
Compare
b051ce7
to
c465bdd
Compare
2fc09c2
to
0a67de9
Compare
7246268
to
16c093a
Compare
a0d31c9
to
2b807c0
Compare
67d160b
to
22d2f66
Compare
//. > S.bimap(S.toUpper, Math.sqrt, S.Right(64)) | ||
//. Right(8) | ||
//. ``` | ||
S.bimap = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does that mean we don't need S.either
anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@svozza S.either
is for unpacking an Either a b
, it returns the return value of the mapper. S.bimap
returns a new Either
of the value returned by the mapper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes, of course, either
is similar to fromMaybe
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, although it's most similar to maybe
and maybe_
:
maybe :: b -> (a -> b) -> Maybe a -> b
maybe_ :: (() -> b) -> (a -> b) -> Maybe a -> b
either :: (a -> c) -> (b -> c) -> Either a b -> c
fromMaybe
and fromEither
are also closely related:
fromMaybe :: a -> Maybe a -> a
fromEither :: b -> Either a b -> b
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Laws looks good, but chainRec
signature is not correct.
index.js
Outdated
//. > S.equals(S.Just([1, 2, 3]), S.Just(['1', '2', '3'])) | ||
//. false | ||
//. ``` | ||
S.equals = def('equals', {}, [a, b, $.Boolean], Z.equals); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returns true if its arguments are of the same type and equal according to the type's fantasy-land/equals method; false otherwise.
Shouldn't it be (Setoid a) => a -> a -> Boolean
instead of a -> b -> Boolean
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That depends on whether we think S.equals('foo', 42)
should evaluate to false
or throw a type error. I'm in favour of the former, since the latter would make S.equals
cumbersome to use in test suites. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning false
is more practical indeed. What if we add equalsStrict
or something which has a -> a -> bool
and throws type error? but it could be done in other PR, after we are we really want it, personally I think it would be good, as it will prevent some type errors in programs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see value in providing a second function. We might want to name it something bland like equals_
, though, to avoid confusion with strict (===
) equality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice ⚡️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added equals_ :: Setoid a => a -> a -> Boolean
. I'm now wondering whether equals
and equals_
should be switched. The _
suffix is used elsewhere in the library as a subtle warning to users, and I believe we should discourage users from comparing values of different types. Do you agree?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in "your" codebase where you know types of values, you will only use equals when two values have same type, as if you know that one value has different type then the other, it's useless to use equals as it will return false for every value of those types.
But one might use equals in library like code, when someone outside of the library could pass some value of any type. But for cases like this one could just use S.unchecked.equals.
So after writing/thinking on it i think equals:: a -> a -> Boolean
would be nice if one could also do this S.unchecked.equals
to access unchecked version of equals
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So after writing/thinking on it i think
equals:: a -> a -> Boolean
would be nice if one could also do thisS.unchecked.equals
to access unchecked version ofequals
Great idea! Using the checkTypes
switch to avoid having two versions of the same function is very elegant. I've pushed a commit to replace S.equals
with the stricter variant.
In #262 Aldwin and I have been talking about bringing back S.unchecked
, which would allow S.unchecked.equals
as you suggest.
//. Nothing | ||
//. ``` | ||
S.zero = | ||
def('zero', {f: [Z.Plus]}, [TypeRep($.TypeVariable('f')), f(a)], Z.zero); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is it needed to have TypeRep($.TypeVariable('f'))
instead of TypeRep(f)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f :: Type -> Type
is not a member of sanctuary-def's internal Type type. Should it be? I don't know. For now, at least, we need to provide a value of type Type
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure, just looked strange
index.js
Outdated
//. Nothing | ||
//. ``` | ||
function traverse(typeRep, f, traversable) { | ||
function pure(x) { return Z.of(typeRep, x); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this part (and sequence) should be updated once sanctuary-js/sanctuary-type-classes#28 is merged
index.js
Outdated
//. ``` | ||
S.join = def('join', {m: [Z.Chain]}, [m(m(a)), m(a)], Z.join); | ||
|
||
//# chainRec :: ChainRec m => TypeRep m -> (a -> Either (m b) (m b)) -> a -> m b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Full implementation of chainRec
(I think it should be correct, but isn't tested)
def('chainRec',
{m: [Z.ChainRec]},
[TypeRep($.TypeVariable('m')), Fn(a, m($Either(a, b))), a, m(b)],
// :: ChainRec m => TypeRep m -> (a -> m (Either a b)) -> a -> m b
function chainRec(typeRep, f, x) {
// :: (a -> c, b -> c) -> Either a b -> c
function mapper(next, done) {
// :: Either a b -> c
return function mapper$inner(either) {
return either.fold(next, done)
}
}
// :: (a -> c, b -> c, a) -> m c
function step(next, done, x) {
// :: m Either a b
var mEither = f(x)
// :: Either a b -> c
var g = mapper(next,done)
return Z.map(g, mEither)
}
return Z.chainRec(typeRep, step, x)
})
)
Also Would be nice to note in comments, which part of Either represent Done and which Loop.
//. ``` | ||
S.filter = | ||
def('filter', | ||
{f: [Z.Applicative, Z.Foldable, Z.Monoid]}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(f a)
should be a Monoid not f
, (same for Monoid (m a)
in filterM), but looks like there is no other way right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a limitation of sanctuary-def. I'd love to work out how to properly capture this constraint.
test/internal/Compose.js
Outdated
}; | ||
|
||
ComposeFG.prototype[FL.ap] = function(other) { | ||
return ComposeFG(Z.ap(Z.map(function(u) { return function(y) { return Z.ap(u, y); }; }, other.value), this.value)); | ||
return ComposeFG(Z.ap(Z.map(S.ap, other.value), this.value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the reason to mix Z
and S
the fact that Z has uncurried functions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right. Perhaps it would be better to use our internal ap
function instead (in both cases). What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that would a good approach, as when we move this laws in a separate module, we might not want it to be depend on sanctuary, because it has curried functions .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test/sequence.js
Outdated
eq(typeof S.sequence, 'function'); | ||
eq(S.sequence.length, 2); | ||
eq(S.sequence.toString(), 'sequence :: (Applicative f, Traversable t) => TypeRep f -> t (f b) -> f (t b)'); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TK?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚡
eq(typeof S.promap, 'function'); | ||
eq(S.promap.length, 3); | ||
eq(S.promap.toString(), 'promap :: Profunctor p => (a -> b) -> (c -> d) -> p b c -> p a d'); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TK?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚡
eq(typeof S.of, 'function'); | ||
eq(S.of.length, 2); | ||
eq(S.of.toString(), 'of :: Applicative f => TypeRep f -> a -> f a'); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TK?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚡
test/join.js
Outdated
eq(S.join([[[1, 2, 3]]]), [[1, 2, 3]]); | ||
eq(S.join(S.Just(S.Just(1))), S.Just(1)); | ||
eq(S.join(S.Just(S.Just(S.Just(1)))), S.Just(S.Just(1))); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe also add failing case?
+ eq(S.join(S.Nothing)), S.Nothing);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚡
46a83e5
to
1488ac7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As chainRec
is fixed, this LGTM. only adding of some tests is left which is not a big issue ✌️
0223cc4
to
80c0696
Compare
index.js
Outdated
//. [[1, 2, 3]] | ||
//. | ||
//. > S.join(S.concat)('abc') | ||
//. 'abcabc' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yesterday when I asked about \x -> f x x
@brendanhay told me that join
is the W combinator! Very cool! See Stack Overflow if you're interested in the substitutions which show that S.join(f)
is equivalent to x => f(x)(x)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was just looking at it. Quite something. So we have:
map = B
ap = S
join = W
I'll add them to my table. ;)
15bcb69
to
eca1ece
Compare
index.js
Outdated
//. Function a (b -> c) -> Function a b -> Function a c | ||
//. (a -> b -> c) -> (a -> b) -> (a -> c) | ||
//. | ||
//. This is the S combinator from combinatory logic. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe move the conclusion above the explanation, so a reader knows why he's reading it, and may choose beforehand to skim over it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. "This" in the sentence above refers to (a -> b -> c) -> (a -> b) -> (a -> c)
rather than to ap
, which is more general. We could, though, combine this sentence with the sentence preceding the substitutions:
Replacing Apply f => f
with Function x
produces the S combinator from combinatory logic:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚡
index.js
Outdated
//. (b -> c) -> Function a b -> Function a c | ||
//. (b -> c) -> (a -> b) -> (a -> c) | ||
//. | ||
//. [`compose`](#compose) is thus a specialized form of `map`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same remark as with ap
index.js
Outdated
//. Function a (Function a b) -> Function a b | ||
//. (a -> a -> b) -> (a -> b) | ||
//. | ||
//. This is the W combinator from combinatory logic. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same remark as with ap
.
ca13bf2
to
982f175
Compare
test/chainRec.js
Outdated
: map(S.Left)(function(env) { return n + env.step; }); | ||
} | ||
|
||
eq(S.chainRec(Function, stepper, 0)({step: 2, inc: 100}), 3100); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm correct that this test is as much to examine stack behaviour as output, perhaps that warrants a comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea.
⚡
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm really excited about this!
@@ -0,0 +1,28 @@ | |||
'use strict'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It strikes me that it would be nice to test these typeclass functions on as minimal a case as possible i.e. the FreeFoo
.
Certainly out of scope for this PR, and I'm not sure where you get your functors from, but what do you think about it in principle?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you suggesting defining a type which provides only the methods and/or TypeRep functions necessary to satisfy the requirements of the type class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty much. I was thinking we could use the free structures (free monoids, applicatives, monads ...). You do need a base functor for those, though, and I'm not sure whether the natural choice (Identity
) actually gives you much assurance.
index.js
Outdated
//. | ||
//. Chain m => m (m a) -> m a | ||
//. Function x (Function x a) -> Function x a | ||
//. Function x (Function x b) -> Function x b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know how much the variable renaming helps understanding here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you suggesting this instead?
Chain m => m (m a) -> m a
Function x (Function x a) -> Function x a
(x -> x -> a) -> (x -> a)
I'm happy to make this change.
Are you suggesting removing the alpha-renaming from the map
and ap
documentation as well, or were you commenting on join
specifically?
I find the alpha-renaming useful for map
and ap
as I find the signatures more recognizable when the conventional identifiers are used. Also, it's a good idea to perform alpha-renaming one variable at a time if one is reusing identifiers. Otherwise variables may appear to move, which is not the correct interpretation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only chain
really stuck out to me, but perhaps it's better to be consistent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consistency led me to the longer version, but I like your shorter version. When there are only two type variables I'm happy to deal with x
and a
. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome!
2c5feff
to
18792e1
Compare
18792e1
to
7e54645
Compare
🦄 |
Closes #178; closes #252
This pull request adds the following functions:
alt :: Alt f => f a -> f a -> f a
ap :: Apply f => f (a -> b) -> f a -> f b
(ap
replacesS
)apFirst :: Apply f => f a -> f b -> f a
apSecond :: Apply f => f a -> f b -> f b
bimap :: Bifunctor f => (a -> b) -> (c -> d) -> f a c -> f b d
chain :: Chain m => (a -> m b) -> m a -> m b
chainRec :: ChainRec m => TypeRep m -> (a -> Either (m b) (m b)) -> a -> m b
empty :: Monoid a => TypeRep a -> a
equals :: Setoid a => a -> a -> Boolean
extend :: Extend w => (w a -> b) -> w a -> w b
extract :: Comonad w => w a -> a
filter :: (Applicative f, Foldable f, Monoid (f a)) => (a -> Boolean) -> f a -> f a
filterM :: (Monad m, Monoid (m a)) => (a -> Boolean) -> m a -> m a
join :: Chain m => m (m a) -> m a
map :: Functor f => (a -> b) -> f a -> f b
(map
replacesB
andlift
)of :: Applicative f => TypeRep f -> a -> f a
promap :: Profunctor p => (a -> b) -> (c -> d) -> p b c -> p a d
sequence :: (Applicative f, Traversable t) => TypeRep f -> t (f a) -> f (t a)
toString :: Any -> String
traverse :: (Applicative f, Traversable t) => TypeRep f -> (a -> f b) -> t a -> f (t b)
zero :: Plus f => TypeRep f -> f a
Original description: