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
[WIP] Fantasy land applicatives #315
base: master
Are you sure you want to change the base?
Conversation
src/Async/Async.spec.js
Outdated
t.end() | ||
}) | ||
|
||
// todo: What can we generalize for all Applicative law tests?? |
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.
Was thinking about setting something up for all law specs.
So it would be something like as a support file in the specs under src/test/laws.js
:
const equals = require('../core/equals')
exports.setoid = {
reflexivity: a => a.equals(a),
symmetry: (a, b) => a.equals(b) === b.equals(a),
transitivity: (a, b, c) => !(a.equals(b) && b.equals(c)) || a.equals(c),
}
exports.semigroup = {
associativity: (a, b, c) =>
equals(a.concat(b).concat(c), a.concat(b.concat(c))),
}
exports.monoid = {
rightIdentity: (type, m) => equals(m.concat(type.empty()), m),
leftIdentity: (type, m) => equals(type.empty().concat(m), m),
}
Then have another support file that checks for existing methods (like you need map
for ap
, etc).
If you would like to add the start (just for Apply
and Applicative
) that would be cool, if not just mimic how Apply
and Applicative
laws are tested for non-FL for now, then we can do a sweep and clean up all the other specs in a separate PR.
EDIT: Add equals
ref to code.
EDIT: There is fantasy-laws
but that puts a hard dependency around Sanctuary and only tests the fantasy-land/**
methods. These laws are easy enough to verify so doing this ourselves.
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 I am thinking the laws file would look something like this:
const fl = require('../core/flNames')
const compose =
f => g => x => f(g(x))
const identity =
x => x
const applyTo =
x => f => f(x)
module.exports = {
apply: {
composition: (equals, f, g, m) => equals(
f.map(compose).ap(g).ap(m),
f.ap(g.ap(m))
)
},
'fl/apply': {
composition: (equals, f, g, m) => equals(
m[fl.ap](g[fl.ap](f[fl.map](compose))),
m[fl.ap](g)[fl.ap](f)
)
},
applicative: type => ({
identity: (equals, x) => equals(
type.of(identity).ap(x),
x
),
homomorphism: (equals, f, x) => equals(
type.of(f).ap(type.of(x)),
type.of(f(x))
),
interchange: (equals, mFn, x) => equals(
mFn.ap(type.of(x)),
type.of(applyTo(x)).ap(mFn)
)
})
}
providing the equals
function on each test for those cases that we do not have a Setoid
(Async
is not a Setoid
in crocks
. That way we can set up a t.plan(n)
and use a function like:
const spec = (name, t) => (a, b) => {
const aRes = sinon.spy()
const bRes = sinon.spy()
a.fork(unit, aRes)
b.fork(unit, bRes)
t.same(aRes.args[0], bRes.args[0], name)
}
for testing equality on all laws in the Async
file. So the Apply
laws (for non-fantasyland) would look like:
test('Async ap properties (Apply)', t => {
t.plan(3)
t.ok(isFunction(Async(unit).map), 'implements the Functor spec')
t.ok(isFunction(Async(unit).ap), 'provides an ap function')
const times10 = x => x * 10
const plus10 = x => x + 10
laws.apply.composition(
spec('composition', t),
Async.of(times10),
Async.of(plus10),
Async.of(0)
)
})
But for cases like Maybe
that are indeed Setoid
and not lazy, etc, etc then it just looks like:
const equals = require('./compose')
test('Maybe ap properties (Apply)', t => {
const j = Maybe.Just(3)
const n = Maybe.Nothing()
t.ok(isFunction(j.ap), 'Just provides an ap function')
t.ok(isFunction(j.map), 'Just implements the Functor spec')
t.ok(isFunction(n.ap), 'Nothing provides an ap function')
t.ok(isFunction(n.map), 'Nothing implements the Functor spec')
const f = x => x * 10
const g = x => x + 10
t.ok(laws.apply.composition(equals, Maybe.of(f), Maybe.of(g), j), 'composition Just')
t.ok(laws.apply.composition(equals, Maybe.of(f), Maybe.of(g), n), 'composition Nothing')
t.end()
})
But I am still not 💯 on that API tho.
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 makes sense, I like the laws.{type}.{law_name}
scheme 👍
I'll add the Apply/Applicative laws like you recommended in this PR, then we can add others in a follow-up
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! Thanks for doing this, Super exciting!
src/Async/index.js
Outdated
|
||
const rejectOnce = once(reject) | ||
|
||
// todo: not sure if this is necessary |
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.
Yep, you are correct!
This is an old artifact. You can remove it from your applyTo
. Leave it in ap
for now and I will make an issue as that is already released to the world.
|
||
function resolveBoth() { | ||
if (!cancelled && resolvedA && resolvedF) { | ||
resolve(resolvedF(resolvedA)) |
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.
Like this better then the compose(resolve, resolvedF)(resolvedA)
that was implemented in the original ap
. This is better for the stack on these lazy types.
Will also change these silly compositions in ap
when we address the "pointless" cancelled flag.
37f5dae
to
b1fbe99
Compare
@pfgray when you get a chance you will wanna rebase against current master. |
d53130f
to
a672063
Compare
@evilsoft , it looks like there are 5 tests failing:
if I remove the addition of I'm not quite sure what they're testing, or how these should behave now that we have |
39d5526
to
6bd8050
Compare
@pfgray will look into that, In the end, all of the pointfree functions should call the fantasy land function first, then dispatch to the non-prefixed. will pull down the branch and see what is going on with dem specs. |
@pfgray I am not seeing those same failures locally. |
@evilsoft, ah yes, sorry... I pushed some changes to the tests in this commit since without these changes those tests fail. Are the Also, would this then be considered a "breaking" change? |
Ahh. sorry I misunderstood what was going on here. There are a few breaking changes that will be happening around this and traversable. EDIT: So to answer your question, yes this will be a breaking change and yes it is expected, I just did not anticipate it. So sorry about the 🌽fusion. |
And do not sweat the breaking change, we have a couple of them coming down the pipe (Fix to |
Also another thing I forgot to mention, each ADT has a |
For #209
Just trying to collect some code around these implementations:
applyTo