-
Notifications
You must be signed in to change notification settings - Fork 28
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
Currying #818
Comments
In terms of practical use, I've less commonly needed that and more often something like |
Not a specific shorthand but that could be done with the same general syntax using a placeholder argument at the end like f := (x, _) =>> ... The underscore here is nothing special of course, just an argument with name const f = (x) => {
return (_) => {
...
}
} If you wanted to have typing support for this you'd need to do f := (x : any, _ : void) =>> ... Otherwise it would be nice to have a Unit type-like notation as an addition to this so that you could write |
Could you give some (ideally real-world) examples where such currying is helpful/convenient? I think that would help the conversation. I worry that curried functions are annoying to apply because |
Ah yes, I've used that pattern before too. Honestly Similarly, note that the original examples can already be written as add := (a) => (b) =>
a + b
add2 := (a) -> (b) ->
a + b That is a lot of parentheses, though, so I can see why some shorthand might be nice, especially for a bunch of parameters... but I'd like to see an example where it's more than two. 🙂 |
It turns out the space in the double arrow isn't even necessary; both Civet and CoffeeScript accept |
Sorry in advance for explaining stuff which you already know, it felt better to put it all together like this... So I think the main use of currying within civet would be to use alongside the pipe operator and when working with higher order functions in general. Additionaly a somewhat niche case would be writing FFI for or working with functions from purescript modules. Of course most things you could do via currying can be solved through other means and I agree that since function application/calling is different from Haskell, using partial application with more than one argument is somewhat inconvenient and using a curried function with more than 2 arguments where only the first was partially applied would also be inconvenient. A classic simple example would be printing with units. (all the examples assume we have currying function declarations) print_as := (unit, value) =>>
`${value}${unit}`
1.4
|> print_as "m"
|> console.log
142
|> print_as "cm"
|> console.log Here currying is a convenient way to reuse a function without typing out lambdas everywhere. But when applying multiple arguments it becomes increasingly more inconvenient because of the parens needed as @edemaine said. print_as := (unit, precision, value) =>>
`${value.toFixed(precision)}${unit}`
1.4142135
|> print_as("m") 4
|> console.log
6.48074069
|> print_as("cm") 2
|> console.log I still think it's nice to have currying even in this manner but I agree that it's not ideal. The case where it becomes even less ideal is where we want to use the curried function that is left with more than 1 arguments not applied at the final destination. For example passing a partially applied function to Array.map where we want to use both the element and the index. If currying was everywhere this would work nicely: print_unit_indexed := (unit, value, index) =>>
`${index} : ${value}${unit}`
[1, 41, 42].map print_unit_indexed "cm"
|> console.log But instead we'd actually need to write [1, 41, 42]
.map (v, i) => print_unit_indexed("cm")(v) i Which makes currying the opposite of handy. Overall I think I rest this proposal for currying. Now if I allowed myself to dream further I could envision a few helpers that could make currying more convenient. Take the following with a grain of salt, I haven't thought it through that much (similar to suggesting currying I suppose lol). Curry powderSo Haskell has the
Inversely in civet we could have something that does kind of the opposite like print_as := (unit, precision, value) =>>
`${value.toFixed(precision)}${unit}`
1.4142135
|> print_as . "m" . 4
|> console.log
Lambda pipeAlternatively, there could be a pipe-like symbol that switched to curried call mode for the scope afterwards:
Partial curryingThe other glaring issue is with interfacing with the rest of javascript where expecting curried functions is not common, like with the Array.map example above. So what if we could define arbitrary groupings inside a curry definition. Of course this makes currying less modular but it might be more useful in the context of js. fn := (a, b, (c, d)) =>> would be the same as fn := (a) => (b) => (c, d) => |
To me, these examples seem to point to "explicit currying" (which is already possible with multiple arrows) as the obvious solution; it lets you control what gets specified when, and with single or multiple arguments, which lets you integrate with the rest of JavaScript. Here's how we'd write your examples: print_as := (unit) => (value) =>
`${value}${unit}`
1.4
|> print_as "m"
|> console.log
142
|> print_as "cm"
|> console.log
print_as := (unit, precision) => (value) =>
`${value.toFixed(precision)}${unit}`
1.4142135
|> print_as "m", 4
|> console.log
6.48074069
|> print_as "cm", 2
|> console.log
print_unit_indexed := (unit) => (value, index) =>
`${index} : ${value}${unit}`
[1, 41, 42].map print_unit_indexed "cm"
|> console.log Personally I find I think where this approach falls apart some is when you have a function that's used in multiple different ways. For example, consider |
as a pipe & auto-currying lover, the proposal looks great and feels very natural to me. however, I get why explicit currying is better to be. but writing explicit curried functions is very painful when there are so many curried variants used in the codebase. So I wonder what if the compiler generates a curried function on-demand when the curried scenario occurs so that programmers don't care whether it's curried or not. mad := (a, x, b) => a * x + b
---
const mad = (a, x, b) => a * x + b mad := (a, x, b) => a * x + b
3
|> mad 1 2
3
|> mad 1
---
const mad = (a, x, b) => a * x + b
const mad_2_1 = (a, x) => (b) => a * x + b // note that _2_1 thing means the arity of curried function
const mad_1_1_1 = (a) => (x) => (b) => a * x + b // would be co-located by the original function so that it can be reused
mad_2_1(1,2)(3)
mad_1_1_1(1)(2) function mad(a, x, b)
a * x + b
3
|> mad 1 2
3
|> mad 1
---
function mad(a, x, b){ return a * x + b }
function mad(a, x){ return function (b){ return a * x + b }}
function mad(a){ return function (x){ return function (b) { return a * x + b }}}
mad(1,2)(3)
mad(1)(2) or... just create whole permutations of each function's arguments, and let the bundlers do the tree shake... not sure about optional arguments btw.. |
A neat idea! But optional arguments are problematic, and in JavaScript, all arguments are optional. So I think you'd need to be explicit about which arguments should remain, which seems like we're back to placeholders again instead of currying. |
So I know there's an issue #75 from the past but currying got a bit lost in there so I just wanted to make a fresh single-purpose feature request for it.
would become
I vote
=>>
/>=>
over==>
/~=>
because=>
b =>
)==>
is easier to confuse at a glance with=>
especially when using ligatures=>>
/->>
/>=>
/>->
while none for~=>
,~->
~=>
can look bad like˜=>
I'd pick
=>>
over>=>
because=>
>
>=>
) from HaskellThe text was updated successfully, but these errors were encountered: