Earlier quoted context omitted.
You can still do this though: let result = (barbalyze(c, d, $) . foobinade(a, b, $)) input Or if you prefer left-to-right: let result = input |> foobinade(a, b, $) |> barbalyze(c, d, $) Maybe what isn't clear is that this hole operator would bind to the innermost function call, not the whole statement.
Even better, this method lets you pipeline into a parameter which isn't the last one: let result = input |> add_prefix_and_suffix("They said '", $, "'!")
A case against currying
51–60 of 135 posts
Re: A case against currying
#52One "feature of currying" in Haskell that isn't mentioned in the fine article is that parts of the function may not be dependent on the last argument(s) and only needs to be evaluated once over many application of the last argument(s) which can be very useful when partially applied functions are passed to higher-order functions. Functions can be done explicitly written to do this or it can be achieved through compile…
It's also a question of whether this is exclusive to a curried definition or if such an optimization may also apply to partial application with a special operator like in the article. I think it could, but the compiler might need to do some extra work?
Re: A case against currying
#53There are good ideas in functional languages that other languages have borrowed, but there are bad ideas too: currying, function call syntax without parentheses, Hindley-Milner type inference, and laziness by default (Haskell) are experiments that new languages shouldn’t copy.
Re: A case against currying
#54There is one situation, however, where Standard ML prefers currying: higher-order functions. To take one example, the type signature of `map` (for mapping over lists) is `val map : ('a -> 'b) -> 'a list -> 'b list`. Because the signature is given in this way, one can "stage" the higher-order function argument and represent the function "increment all elements in the list" as `map (fn n => n + 1)`.
That being said, because of the value restriction [0], currying is less powerful because variables defined using partial application cannot be used polymorphically.
Re: A case against currying
#55One "feature of currying" in Haskell that isn't mentioned in the fine article is that parts of the function may not be dependent on the last argument(s) and only needs to be evaluated once over many application of the last argument(s) which can be very useful when partially applied functions are passed to higher-order functions. Functions can be done explicitly written to do this or it can be achieved through compile…
That's a very good point, I never thought really about how this relates to the execution model & graph reduction and such. Do you have an example of a function where this can make a difference? I might add something to the article about it. It's also a question of whether this is exclusive to a curried definition or if such an optimization may also apply to partial application with a special operator like in the arti…
getClosest :: Set Point -> Point -> Point
You could imagine getClosest build a quadtree internally and that tree wouldn't depend on the second argument. I say slightly contrived because I would probably prefer to make the tree explicit if this was important.
Another example would be if you were wrapping a C-library but were exposing a pure interface. Say you had to create some object and lock a mutex for the first argument but the second was safe. If this was a function intended to be passed to higher-order functions then you might avoid a lot of unnecessary lock contention.
You may be able to achieve something like this with optimisations of your explicit syntax, but argument order is relevant for this. I don't immediately see how it would be achieved without compiling a function for every permutation of the arguments.
Re: A case against currying
#561. Looking at a function call, you can't tell if it's returning data, or a function from some unknown number of arguments to data, without carefully examining both its declaration and its call site
2. Writing a function call, you can accidentally get a function rather than data if you leave off an argument; coupled with pervasive type inference, this can lead to some really tiresome compiler errors
3. Functions which return functions look just like functions which take more arguments and return data (card-carrying functional programmers might argue these are really the same thing, but semantically, they aren't at all - in what sense is make_string_comparator_for_locale "really" a function which takes a locale and a string and returns a function from string to ordering?)
3a. Because of point 3, our codebase has a trivial wrapper to put round functions when your function actually returns a function (so make_string_comparator_for_locale has type like Locale -> Function string -> order>), so now if you actually want to return a function, there's boilerplate at the return and call sites that wouldn't be there in a less 'concise' language!
I think programming languages have a tendency to pick up cute features that give you a little dopamine kick when you use them, but that aren't actually good for the health of a substantial codebase. I think academic and hobby languages, and so functional languages, are particularly prone to this. I think implicit currying is one of these features.
Re: A case against currying
#57 (log configuration identifier level format-string arg0 arg1 ... argN)
After each partial application step you can do more and more work narrowing the scope of what you return from subsequent functions. ;; Preprocessing the configuration is possible
;; Imagine all logging is turned off, now you can return a noop
(partial log conf)
;; You can look up the identifier in the configuration to determine what the logger function should look like
(partial log conf id)
;; You could return a noop function if the level is not enabled for the particular id
(partial log config id level)
;; Pre-parsing the format string is now possible
(partial log conf id level "%time - %id")
In many codebases I've seen a large amount of code is literally just to emulate this process with multiple classes, where you're performing work and then caching it somewhere. In simpler cases you can consolidate all of that in a function call and use partial application. Without some heroic work by the compiler you simply cannot do that in an imperative style.Re: A case against currying
#58Earlier quoted context omitted.
That's a very good point, I never thought really about how this relates to the execution model & graph reduction and such. Do you have an example of a function where this can make a difference? I might add something to the article about it. It's also a question of whether this is exclusive to a curried definition or if such an optimization may also apply to partial application with a special operator like in the arti…
One slightly contrived example would be if you had a function that returned the point of a set closest to another given point. getClosest :: Set Point -> Point -> Point You could imagine getClosest build a quadtree internally and that tree wouldn't depend on the second argument. I say slightly contrived because I would probably prefer to make the tree explicit if this was important. Another example would be if you we…
I was imagining you might achieve this optimization by inlining the function. So if you have
getClosest(points, p) = findInTree(buildTree(points), p)
And call it like myPoints = [...]
map (getClosest(myPoints, $)) myPoints
Then the compiler might unfold the definition of getClosest and give you map (\p -> findInTree(buildTree(myPoints), p)) myPoints
Where it then notices the first part does not depend on p, and rewrite this to let tree = buildTree(myPoints) in map (\p -> findInTree(tree, p)) myPoints
Again, pretty contrived example. But maybe it could work.Re: A case against currying
#59Earlier quoted context omitted.
That's a very good point, I never thought really about how this relates to the execution model & graph reduction and such. Do you have an example of a function where this can make a difference? I might add something to the article about it. It's also a question of whether this is exclusive to a curried definition or if such an optimization may also apply to partial application with a special operator like in the arti…
One slightly contrived example would be if you had a function that returned the point of a set closest to another given point. getClosest :: Set Point -> Point -> Point You could imagine getClosest build a quadtree internally and that tree wouldn't depend on the second argument. I say slightly contrived because I would probably prefer to make the tree explicit if this was important. Another example would be if you we…
The flip side of your example is that people see a function signature like getClosest, and think it's fine to call it many times with a set and a point, and now you're building a fresh quadtree on each call. Making the staging explicit steers them away from this.
Re: A case against currying
#60One language that uses the tuple argument convention described in the article is Standard ML. In Standard ML, like OCaml and Haskell, all functions take exactly one argument. However, while OCaml and Haskell prefer to curry the arguments, Standard ML does not. There is one situation, however, where Standard ML prefers currying: higher-order functions. To take one example, the type signature of `map` (for mapping over…
And yeah I think this is the way to go. For higher-order functions like map it feels too elegant not to write it in a curried style.