def's signature
See original GitHub issueI wonder if there is any reason for choosing this signature: https://github.com/sanctuary-js/sanctuary-def/blob/master/index.js#L62
// add :: Number -> Number -> Number
def('add', {}, [$.Number, $.Number, $.Number], (x, y) => x + y);
The {}
looks counter-intuitive (unless one goes deep into the code, of course, and reads the definitions of the variables, which, however, are not visible from the function invocation in this text aimed at the newcomers).
Also putting both function arguments and return values inside a single array feels somewhat confusing as their meaning is fundamentally different. (Even mathematically, you would have to switch to duals when moving variables between source and target of a function.)
But even more importantly, the correct signature of this function seems to be
// add :: (Number, Number) -> Number,
which is not the same as the curried one above, and works differently in JavaScript. Even worse things can happen like placing the curried version
const add = x => y => x + y
into Ramda’s reduce
, which leads to different (and less intuitive) results
than its expected uncurried sister
const add = (x, y) => x + y
So the question arises, which of these two signatures is reflected in this declaration:
def('add', {}, [$.Number, $.Number, $.Number], (x, y) => x + y);
Looking at the def
definition
https://github.com/sanctuary-js/sanctuary-def/blob/master/index.js#L2346,
I wonder if there any reason not to declare it as
function def({name, constraints, type, impl}) { ...
Then
// add :: (Number, Number) -> Number,
would become
def({
name: 'add',
type: $.Function([$.Number, $.Number], $.Number),
impl: (x, y) => x + y
})
whereas
// add :: Number -> Number -> Number,
would become
def({
name: 'add',
type: $.Function($.Number, $.Function($.Number, $.Number)),
impl: (x, y) => x + y
})
and the more complex signature
// add :: (Number -> Number) -> Number,
would become
def({
name: 'eval',
type: $.Function($.Function($.Number, $.Number)),
target: $.Number,
impl: (f, x) => f(x)
})
or, the real world example
// Future :: ( a -> (), (b -> ()) -> Cancel ) -> Future a b
would become
def({
name: 'Future',
type: $.Function($.Function(A, $.Empty), $.Function($.Function(B, $.Empty), Cancel), Future A B)),
impl: (x, y) => x + y
})
Issue Analytics
- State:
- Created 7 years ago
- Comments:11 (6 by maintainers)
Top GitHub Comments
Once you learn that this object holds the type class constraints I think it becomes rather intuitive. Or at least explicit. The empty object shows us that there are no constraints on the type variables.
I think it’s meant to closely resemble Hindley-Milner syntax, where the return value of the function is simply the last item in a list separated by
->
.Actually,
def
also curries the “implementation”. SoNumber -> Number -> Number
is correct. The fact that it’s also possible to call it by passing multiple arguments at once is kind of like a “syntactic sugar” to make it less painful to apply multiple arguments with JavaScript.I’m not sure which “curried version” you are referring to. You shouldn’t pass manually curried functions such as your curried
add
intodef
, asdef
will fail to call them properly (assuming that’s what you mean?).If defined via
def
correctly, a function can be safely treated as either curried or uncurried. It should therefore be safe to pass it into functions like Ramda’sreduce
(which expects(a, b) -> a
) or Sanctuary’sreduce
(which expectsa -> b -> a
).Sanctuary’s API consistently chooses positional function arguments over named arguments (with the exception of
create
, after some debate). It’s a preference of @davidchambers, I believe. It also makes it easier to curry these functions, since “named arguments” is really just a single argument of an object with mixed value types.❤️ this:
@dmitriz take a look at:
they demonstrate how data-last approach with currying is great.