Skip to content
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

abandon Ramda-style currying in favour of simple currying #520

Merged
merged 1 commit into from
Apr 6, 2018

Conversation

davidchambers
Copy link
Member

@davidchambers davidchambers requested a review from Avaq April 4, 2018 13:08
@Avaq
Copy link
Member

Avaq commented Apr 5, 2018

That is a pretty daunting diff.

Most seem like stylistic changes of sorts, eg a, b -> a) (b. Could you point me to the significant changes, if any? Or otherwise if you'd really like me to take a good look at it, we could arrange a call of sorts so you can guide me through this.

@davidchambers
Copy link
Member Author

That is a pretty daunting diff.

I agree. I am reviewing the changes myself and don't know where to focus my attention. I'll post comments on some sections which strike me as potentially controversial. :)

},
},
],
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file allows us to verify that doctests and code snippets follow our desired formatting rules.

'CallExpression > ArrowFunctionExpression ArrowFunctionExpression > *',
'CallExpression > FunctionExpression > BlockStatement'
);
module.exports = indent;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ESLint does not like this indentation:

f (x)
  (y)
  (z)

Rather than disable the indent rule entirely, we can add exceptions for problematic cases.

cp README.md README.md.temp
rewrite <README.md.temp >README.md

node_modules/.bin/eslint --config eslint/es6.js -- README.md eslint
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script enables npm run lint to run eslint on files that would not otherwise be linted.

Linting the readme involves a number of steps:

  1. Save a copy of the file as it exists.
  2. Regenerate the readme (the VERSION and PREVIOUS_VERSION values are unimportant).
  3. Use rewrite to remove leading > and . characters, parenthesize object literals which would otherwise be parsed as blocks, and insert a semicolon at the end of each input and output line.
  4. Run eslint.
  5. Restore the readme to its original state (via an EXIT trap).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert.strictEqual (arguments.length, eq$1.length);
assert.strictEqual (toString (actual), toString (expected));
assert.strictEqual (equals (actual) (expected), true);
};
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The internal eq function is now curried.

return function throws$1(expected) {
assert.strictEqual (arguments.length, throws$1.length);
assert.throws (thunk, equals (expected));
};
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The internal throws function is now curried, and now takes a single value representing the expected error rather than a type representative and an error message.

Before:

throws(thunk, TypeError, 'Invalid value');

After:

throws (thunk) (new TypeError ('InvalidValue'));

return function Cons$1(tail) {
eq (arguments.length) (Cons$1.length);
return new _List ('Cons', head, tail);
};
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cons is now curried.

return function(y) {
return $.Function ([x, y]);
};
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fn is now curried.

(readmeUrl ('EitherType'))
(typeEq (eitherTypeIdent))
(either (of (Array)) (K ([])))
(either (K ([])) (of (Array)));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that functions such as K are curried (and hoisted) we're able to use them throughout the file. :)

//.
//. Returns `true` [iff][] the *second* argument is less than the first
//. according to [`Z.lt`][]. The arguments must be provided one at a time.
//. according to [`Z.lt`][].
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of the important changes listed in #438 (comment). Now that all functions are “simply” curried there's no reason for this function's type to be [a, $.Predicate (a)] rather than [a, a, $.Boolean]. :)

return function(filterable) {
return Z.filter (pred, filterable);
};
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are defining filter rather than setting impl to curry2 (Z.filter) in order to partially apply the function in other definitions.

};
function step(next, done, x) {
return Z.map (either (next) (done), f (x));
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to rely on function hoisting here to avoid interrupting the function > return > > return staircase.

return function(x) {
return function(y) {
return function(z) {
return f (v, w, x, y, z);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason this makes me happy. :D

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was typing a similar comment to curry:

So blissfully transparent! :D

...but then I scrolled down and saw curry2, which made me happier, so I removed the comment to move it over there. But then I scrolled down further to find curry3. Woah, how cool is this going to get? I scrolled all the way to curry5 and found your comment. :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤣

@@ -1711,7 +1773,7 @@
//. > S.Nothing.isNothing
//. true
//.
//. > S.Just(42).isNothing
//. > (S.Just (42)).isNothing
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a situation in which unnecessary parens are justified: isNothing in S.Just (42).isNothing is tightly bound, visually, to 42.

Fortunately, such property access is rare in code written with Sanctuary (one could use S.isNothing in this case).

//. 'Just([1, 2, 3])'
//. ```
Maybe.prototype.toString = function() {
return this.isJust ? 'Just(' + Z.toString(this.value) + ')' : 'Nothing';
return this.isJust ? 'Just(' + Z.toString (this.value) + ')' : 'Nothing';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point I would like to switch from 'Just(42)' to 'Just (42)', but doing so now would lead to inconsistencies such as 'Just (new Date(0))'. This order would be optimal, I believe:

  1. Create sanctuary-show, copying the Z.toString implementation but using f (x) style.
  2. Update sanctuary-maybe to depend on sanctuary-show, and replace Maybe#toString with Maybe#@@show which uses f (x) style.
  3. Update this project to depend on sanctuary-maybe and sanctuary-show, and replace S.toString with S.show.

function maybe(x) {
return function(f) {
return function(maybe) {
return maybe.isJust ? f (maybe.value) : x;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels right to define fromMaybe in terms of maybe rather than the other way around.

index.js Outdated
types: [Fn(a, $Maybe(b)), f(a), f(b)],
impl: mapMaybe
types: [Fn (a) ($Maybe (b)), f (a), f (b)],
impl: blackbird (justs) (map)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using blackbird may be a step too far. ;)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would think so. Especially if used sparsely and considering that the definition of blackbird itself is also somewhat mind-bending.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I've figured out what's going on, I also see the appeal though ;)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed blackbird. The implementation is now B (B (justs)) (map).

The implementations of encase2 and encase3 sell me on the idea:

B (B (B (eitherToMaybe))) (encaseEither2 (I))
B (B (B (B (eitherToMaybe)))) (encaseEither3 (I))

Beautiful!

On several occasions I've defended map (map (map (map (nbsp)))). I feel a similar way about B (B (B (B (eitherToMaybe)))). It looks strange at first, but once one learns how to interpret the nesting it's much clearer than the equivalent nested lambdas.

index.js Outdated
types: [$.Predicate(a), a, $.Boolean],
impl: complement
types: [$.Predicate (a), a, $.Boolean],
impl: compose (not)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curried compose is very useful!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been saying the same thing! This is one of the reasons why Ramda's compose is so crippled, but you don't really realise until you try it.

impl: find
};

//# unfoldr :: (b -> Maybe (Pair a b)) -> b -> Array a
//# unfoldr :: (b -> Maybe (Array2 a b)) -> b -> Array a
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're now using $.Array2, which replaced $.Pair.

Copy link
Member

@Avaq Avaq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be interested to find out how these changes affect performance. I would imagine JavaScript function context creation time has increased quite a lot.

@davidchambers davidchambers merged commit a3fd05a into master Apr 6, 2018
@davidchambers davidchambers deleted the davidchambers/simple-currying branch April 6, 2018 16:19
@dakom
Copy link

dakom commented Jul 6, 2018

I would be interested to find out how these changes affect performance. I would imagine JavaScript function context creation time has increased quite a lot.

That would be fascinating to see benchmarks, ideally of actual projects (not just adding a bunch of numbers)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants