[HN Gopher] Currying
___________________________________________________________________
Currying
Author : peter_d_sherman
Score : 53 points
Date : 2024-04-07 02:50 UTC (20 hours ago)
(HTM) web link (wiki.haskell.org)
(TXT) w3m dump (wiki.haskell.org)
| solardev wrote:
| In my entire life as a programmer, this is the one thing that
| I've found most confusing. Our old codebase used a lot of curried
| Ramda functions and nobody on the team could read any of it. We
| spent a week ripping every single instance of it out and replaced
| it with regular lodash and never looked back and never had an
| issue with it again.
|
| After like ten or fifteen hours of trying to understand what
| currying is, I still have no idea, and frankly don't care. If I
| see it in a codebase it'd just be a big red flag to avoid that
| job altogether.
| auggierose wrote:
| Ramda, lodash, no idea what currying is, ...
|
| Software is in good hands.
| solardev wrote:
| Heh, so much of the world runs on basic business software. If
| it ran fine after the rewrite, with much less cognitive load,
| why not? These were just bog standard web pages that someone
| overengineered the hell out of, to a point that nobody else
| could understand or maintain it. Sure, that might win them
| l33t points, but wasn't very useful otherwise.
| yakshaving_jgt wrote:
| As a Haskell programmer, I have to agree with you.
|
| When you're writing Haskell, you should write idiomatic
| Haskell. Currying is part of that. It's a trivial concept
| and we use it liberally at work. If you're writing
| JavaScript though, I think it's a different story. I don't
| think JavaScript lends itself to FP in the way that Haskell
| or Elm does, and trying to make it do that only makes a
| project harder to work on.
| worddepress wrote:
| Almost as if an astronaut can come, do their thing in a
| whirlwind and leave for another company 2 years later, while
| the remaining plebs have to figure it all out.
| solardev wrote:
| Yeah, a lot of the codebase was like that. Some lone wolf
| mad genius that occasionally left pieces of pure brilliance
| behind, but more often than not, just wrote code that was
| incredibly idiosyncratic and unnecessarily reinvented. Like
| he'd spend several files making a color gradient
| visualization system with his own scales, with an
| enthusiastic but limited understanding of color theory,
| that ended up producing mostly reasonable but occasionally
| insane color scales. We spent several days of back and
| forth discussions with him before understanding what he was
| actually trying to do (which isn't what his code was
| actually doing) and then replaced it with a one liner out
| of a standard visualization/color lib.
|
| The entire codebase was like that. It stood out to me as
| the work of someone who was very smart but worked alone and
| never had to work with a team, and never considered what
| someone trying to retrace his mental process would have to
| go through. He left zero documentation or comments, built
| the whole thing in a rush, sold it to another company, and
| we were the ones hired later to clean up his mess.
|
| Some of it was very good. He was really good at efficiently
| encoding low level network traffic over packet encodings he
| invented or heavily modified. Seemed like he would've been
| a brilliant signals engineer or cryptologist. But having to
| work with his ordinary business logic code was pretty
| nightmarish, if interesting from a reverse engineering and
| psychology point of view.
|
| It's just not how I would ever want to write code meant for
| mere mortals. But then again, he's a multi millionaire now
| and I'm a rando nobody living paycheck to paycheck, so I'm
| in no place to judge lol.
| MarkSweep wrote:
| > But then again, he's a multi millionaire now and I'm a
| rando nobody living paycheck to paycheck, so I'm in no
| place to judge lol.
|
| If he had not made such a big mess, you might not be
| getting this paycheck to clean it up. I think they call
| this "creating scope for other people" at big companies,
| real staff engineer stuff. =)
| solardev wrote:
| Heh, exactly. Some of us are just software janitors for
| the bigwigs.
| bPspGiJT8Y wrote:
| > After like ten or fifteen hours of trying to understand what
| currying is, I still have no idea
|
| Maybe you could recall which learning materials you used?
| solardev wrote:
| The Ramda docs, primarily, and Google searches. It just
| seemed like a lot of unnecessary complexity. We modified the
| parts we understood (or believed we did), and just rewrote
| the rest from scratch. The tests still passed, nobody else
| complained, the managers and customers never noticed, the
| devs were much happier, QA never said a word, and a whole
| class of bugs disappeared once we were able to reliably
| predict the output of simple helper functions. Shrug. Seemed
| like a win win. Probably the best thing we ever did to that
| codebase.
| worddepress wrote:
| In Haskell it is easy. If you "forget" the last argument to a
| function, you get returned a function where you can provide
| that later on. A bit like saying "you can fill this in later".
| That is a "curried" function.
|
| Example add 1 2 // add is curried, you can
| use it like this, the "normal" way, returns 3 p =
| add 1 // since add is curried I can also provide just the first
| argument p 2 // and then apply the last argument to the
| intermediate results, returns 3
|
| What is the point of using curried functions in JS? I am not
| really sure. It is not very ergonomic and I wouldn't like to
| use them in general. Maybe for some specific things it could be
| useful.
|
| In Haskell curried form is the default and the syntax and
| semantics really suits it. In JS non-curried is the default and
| it just looks odd, and you need libraries to support it. That
| library you mentioned doesn't look nice to use.
| zarzavat wrote:
| It's worth noting that even in Haskell, overuse of the so
| called point-free style is disliked for much the same
| reasons:
|
| https://wiki.haskell.org/Pointfree
|
| There is a sliding scale and even at the Haskellers have a
| limit of how much point-free they can take.
|
| In JavaScript use of the style is problematic in another way:
| unlike Haskell in JS the length of the argument list is
| variable, which means that if someone adds another argument
| to either the caller or callee it can break the code in
| subtle and unexpected ways.
|
| For this reason it's good practice to always wrap functions
| being passed as arguments to another function in a lambda
| expression.
|
| i.e instead of writing: g(f), you should usually write: g(x
| => f(x)) unless you have good reason to believe that g(f) is
| safe. This makes it difficult to use point-free style at all
| in JS.
|
| For example arr.map(f) is generally unsafe in JS because if
| `f` adds an extra default argument of type number then your
| code will break and even TypeScript won't let you know.
| taneq wrote:
| Honestly, a lot of these 'advanced' features aren't a good
| choice in most codebases IMO. The vast majority of
| 'enterprise' coding is CRUD and glue, where execution speed
| doesn't matter and being as simple and explicit as possible
| is the highest virtue. And the significant number of
| professional coders who worked hard to get their heads around
| things like pointers and printf format specifiers are still
| plenty productive in those kinds of codebases.
|
| Things like currying are fun but like anything that
| encourages gratuitously deep call trees, they wreck your
| locality of reference (as a developer) and force you to
| 'decompile' the code in your head in order to understand it.
| I'm sure that a top level developer would be able to write
| curried JS in such a way that it was clear and readable to
| another top level developer, but that's not the point. The
| code's not _for_ you, it 's for newbie who gets stuck with it
| when you move on.
| kerkeslager wrote:
| I've never liked this "feature".
|
| What if you forget, not in quotes, to give the second
| argument? Why on earth would you want to get a type error in
| a completely different part of the code because you got a
| function instead of an integer? Wouldn't it be desirable to
| have the compiler just tell you that you forgot the second
| argument where you forgot the second argument?
|
| Is it _really_ valuable to be able to do: p
| = add 1
|
| ...instead of: inc = (\x -> add 1 x)
|
| ...or, heaven forbid: inc x = add 1 x
|
| ...?
|
| I mean, I get the idea, they're following the lambda
| calculus, but this is one of the things that should have been
| dropped when they started to expand the lambda calculus to a
| general purpose programming language.
| bPspGiJT8Y wrote:
| > What if you forget, not in quotes, to give the second
| argument?
|
| I will get a type error and it will take me 2-3 seconds to
| figure out what it is about.
|
| > Why on earth would you want to get a type error in a
| completely different part of the code because you got a
| function instead of an integer?
|
| Why would it be in a completely different part of the code?
| At most it would be 2 lines away, but usually on the same
| line.
| kerkeslager wrote:
| > I will get a type error and it will take me 2-3 seconds
| to figure out what it is about.
|
| You should really screen capture yourself coding
| sometime. On a large codebase you're lucky if the
| compiler even runs in 10 seconds.
|
| > Why would it be in a completely different part of the
| code? At most it would be 2 lines away, but usually on
| the same line.
|
| Because possibly you return the partial assuming it's a
| number, and try to use it somewhere else, possibly even
| in another file.
| bPspGiJT8Y wrote:
| > Because possibly you return the partial assuming it's a
| number, and try to use it somewhere else, possibly even
| in another file.
|
| Sorry, I must've been more explicit instead of implying
| certain usage patterns. What I meant here is that I have
| a hard time imagining this happening because I would
| start working on a function by writing its type
| signature. Unless my types check out, I won't be able to
| mark this function as "done" and jump to another part of
| code. So the situation "you return the partial assuming
| it's a number" simply _can not_ happen, that 's exactly
| what type checking is for. By the time I use it in
| another place, it has to already have been type checked.
| 082349872349872 wrote:
| Function arguments work like the power rule for exponents in
| elementary school math: f a b = g(a,b) = f' b a
| (x^2)^3 = x^(2*3) = (x^3)^2
| solardev wrote:
| You must've gone to a pretty kick ass elementary school. In
| mine, we'd play Connect Four and make paintings out of fake
| hieroglyphs...
| 082349872349872 wrote:
| I'm fortunate my parents didn't stick with free-to-play,
| but were willing to spend 5-6 digits more to buy a house in
| the "good" school district:
| https://news.ycombinator.com/item?id=39946026
| dxdm wrote:
| Willing _and_ able.
| 082349872349872 wrote:
| I grew up during the Cold War, so I don't know what the
| Young Pioneers were learning*, but we were good little
| capitalists, and we learned the Golden Rule:
|
| "Whoever has the gold makes the rules"
|
| (*maybe the ones who spent their summers at Artek were
| learning "whoever makes the rules has the gold"?)
| inopinatus wrote:
| When I were a lad, elementary school meant four hours of
| beatings in a muddy field, and we were glad of it
| 082349872349872 wrote:
| On that note, I found Orwell's posthumously-published
| _Such, Such Were the Joys_ (about his boarding school
| days) suggests the inspirations behind large swaths of
| _1984_.
| inopinatus wrote:
| _Coming Up for Air_ is practically my childhood in a
| nutshell
| akdor1154 wrote:
| Was the codebase in strict Typescript? I find currying to be
| sometimes useful in the right language / paradigm, as long as
| you can rely on the compiler to yell at you until you get it
| right.
|
| Dynamically typed functional retrofit on with no type checking
| though? Hell no, that's a write-once read-never mess of
| unmaintainable shite.
|
| (I also have been on a team with The Guy Who Wanted To Ramda,
| and I'm someone who writes build scripts in ocaml)
| solardev wrote:
| It was a mishmash. The parts written by later employees were
| strongly typed and easy to follow. The parts he wrote weren't
| typed at all and frequently relied on implicit conversions
| and bitwise operators to do magic operations that would take
| us mere mortals several days to even begin to understand, all
| just to save what would've been like 3 lines of code and a
| single comment.
|
| Inheriting that codebase felt like watching one of those
| serial killer dramas... obviously a really smart guy, but
| with a psychology unlike most people I've ever met, and
| really hard to follow as a normal person. I still see him
| with a mixture of respect, awe, curiosity, and wonder.
|
| The thing is, none of the stuff he gave is was actually
| complicated. It's just a basic business dashboard. But he
| reinvented almost every single part of it in totally
| nonstandard ways that the whole project was less about coding
| and more about anthropology and forensic psychology.
| OscarTheGrinch wrote:
| CSI: Code Shite Investigation
| kccqzy wrote:
| > After like ten or fifteen hours of trying to understand what
| currying is
|
| I have doubts on whether this is a sincere attempt at
| understanding the concept. The linked wiki page is like a five
| minute read and that's enough to understand the concept at a
| user's level.
|
| What I suspect is that something else is tripping you up and
| causing code readability issues; but since you didn't know what
| currying is you incorrectly ascribed currying as the problem.
| threatofrain wrote:
| Yeah 10-15 hours is kinda crazy for an introductory topic.
| curry_is_easy wrote:
| Since you know JavaScript, this should help.
|
| Uncurried:
|
| ((x, y) => x * y)(3, 5) === 15
|
| Curried:
|
| (x => y => x * y)(3)(5) === 15
|
| If you don't understand that, your problem is that you don't
| understand anonymous functions (lambdas), not that you don't
| understand currying.
| fxj wrote:
| In my experience, functional programming really shines in rapid
| prototyping. However, in production code I would try to avoid
| it, because it can confuse developers that are not used to it
| and also sometimes I struggle myself when decoding the short
| "clever" one-liner that I wrote 5 years ago. But if you want to
| write very powerful code withing a single line it is really
| great and makes you very productive for finding solutions fast.
| It is a bit like the Perl throw-away code we were writing in
| the 1990s. ;-)
|
| just my 2 ct
| KingOfCoders wrote:
| It should be called Schonfinkel. But I guess, curry is easier to
| pronounce.
| IshKebab wrote:
| I always thought currying was a bad idea.
|
| It makes the code way less readable:
|
| * more difficult to distinguish arguments and return values
| (there's a reason most languages have distinct syntax for them)
|
| * it encourages you to put your arguments in an order that might
| not be the most logical
|
| And on top of that it only works for the _last_ argument(s).
|
| Feels like a lot of disadvantages to allow an overly clever trick
| that you can do in other languages much more readable using
| lambda functions. And those work for any arguments, not just the
| last one(s).
| 082349872349872 wrote:
| > _using lambda functions_
|
| or (where available) sections
| yen223 wrote:
| I much prefer partial application. It just makes more intuitive
| sense - you take a function with many arguments, "fix" some of
| those arguments, and get a new function out. No need to mess
| with argument ordering, or all that.
|
| It's a shame the only language that did partial application
| well is, weirdly, Python.
| bPspGiJT8Y wrote:
| In Gleam it also seems to be done well:
| https://tour.gleam.run/functions/function-captures/
| yen223 wrote:
| That looks really cool, thanks for sharing
| IshKebab wrote:
| Partial application just means fixing some arguments. My
| point was you can do that easily using lambda functions.
|
| I guess you were talking about `functools.partial`, which is
| basically the same as currying and also only works on the
| last arguments. It's better to use lambda functions.
|
| Also Python is far from the only language to have a function
| like that, e.g. see C++'s `std::bind` which nobody* uses
| since lambdas were supported.
|
| *standard HN disclaimer
| skybrian wrote:
| When you pass some arguments to a constructor and then later, you
| pass some more arguments to a method, that's equivalent to
| currying.
|
| Method calls are easier to read and work better with
| autocomplete.
| whilenot-dev wrote:
| I think you confuse currying with partial application.
| kerkeslager wrote:
| Are you saying this to be helpful, or just to show you're
| smarter? If you're saying this to be helpful, you should
| probably explain more.
|
| Currying is a subset of partial application, so I suspect the
| person you're responding to is just being a bit loose with
| their wording, as opposed to not understanding the topic.
| whilenot-dev wrote:
| I don't read it that way, no. Currying is a process to
| transform a function that takes multiple arguments into
| higher order functions that take one argument at a time to
| _enable_ partial application (as written in the article).
| The partial application of a function is a different
| transformation of a function.
|
| Some languages provide partial application without currying
| a function - JavaScript has _bind_ , Python has
| _functools.partial_ etc.
| kerkeslager wrote:
| Okay, I understand what you're saying, and you're right,
| but you're missing my point and doubling down on the same
| mistake again.
|
| Language is inherently imprecise and communication is
| never perfect. When people say something, it's rude to
| assume they are wrong or don't understand something
| because they don't say it exactly the way you would have
| said it. This is extremely common in technical
| communication and it's extremely counterproductive.
|
| I'm suggesting that you could be a better reader by
| trying to understand what people are trying to say even
| if they don't communicate it exactly the way you want
| them to.
|
| For example, _no one in this comment chain is confused
| about what currying or partial application are_ , so it's
| a bit rude that you've assumed people are confused
| because you didn't take the time to figure out what
| people were trying to communicate. Instead, because
| people didn't say things exactly the way you would have
| said it, you jumped in with corrections.
|
| If you take a step back and resist the urge to correct
| people, can you understand how "Currying is a subset of
| partial application" is true?
| whilenot-dev wrote:
| > ...can you understand how "Currying is a subset of
| partial application" is true?
|
| It isn't true and i can understand why someone would make
| that statement. Currying and partial application both
| come from a functional mindset that treats functions as
| data, and like data also functions can be transformed.
|
| Currying and partial application both satisfy a
| transformation depending on a desired context:
| - Currying "widens" a function "up" (as in you don't need
| to know all arguments beforehand in the same context
| anymore) - Partial application "narrows" a function
| "down" (as in arguments are contained within the context
| of the partially applied function)
|
| "Currying is a subset of partial application" is true in
| the same sense as if "subtraction is a subset of
| addition". Sure, you can subtract negative values to have
| addition and add negative values to have subtraction, but
| that can't be your point, is it?
|
| > If you take a step back and resist the urge to correct
| people...
|
| Please keep it on topic.
| kerkeslager wrote:
| > It isn't true and i can understand why someone would
| make that statement
|
| It is true, and you are smart enough to understand it if
| you stop assuming you're the only one who understands the
| topic. I'm specifically sticking with the wording
| "Currying is a subset of partial application" to make the
| point that you can understand what someone is trying to
| say even if they don't say it exactly the way you would
| like them to say it.
|
| > - Currying "widens" a function "up" (as in you don't
| need to know all arguments beforehand in the same context
| anymore) > - Partial application "narrows" a function
| "down" (as in arguments are contained within the context
| of the partially applied function)
|
| So you're saying that I'm "widening" the function "up"
| when I do: increment = add 1
| invert = div 1
|
| ...? But I'm "narrowing" the function "down" when I do:
| increment = (\x -> add 1 x) halve = (\x -> div x
| 2)
|
| ...?
|
| When I curry `add` above, isn't the argument 1 contained
| within the context of the partially applied function?
|
| When I partially apply `div` above, don't I no longer
| have to know all the arguments (i.e. I no longer need to
| know the denominator 2)?
|
| It sure seems like this up/down wide/narrow wording
| you're fixated on isn't a particularly better
| descriptions of what's going on with currying and partial
| application.
|
| It mean, maybe it's just my font, but the partial
| applications are a bit wider on my screen. ;P
|
| > "Currying is a subset of partial application" is true
| in the same sense as if "subtraction is a subset of
| addition". Sure, you can subtract negative values to have
| addition and add negative values to have subtraction, but
| that can't be your point, is it?
|
| I'm not sure I understand that analogy to say it's my
| point.
|
| Are you saying you would behave condescendingly toward
| someone who said "subtraction is a subset of addition"
| too?
|
| > Please keep it on topic.
|
| The topic skybrian started was derailed when you decided
| to "correct" him because you didn't make any effort to
| understand what he was trying to say. Your behavior is
| the topic now because your behavior became a problem.
| whilenot-dev wrote:
| Neither of your examples does curry a function. The first
| one partially applies it (the Haskell way), the second
| one wraps the partial application in a redundant lambda
| (Haskell again). I think that's where the confusion is,
| let me also try to use the Haskell syntax...
|
| This is an _add_ function with two arguments:
| add :: (Int, Int) -> Int add (x, y) = x + y
|
| At the moment it can not be partially applied, so let's
| curry it: -- addCurr :: Int -> Int -> Int
| addCurr = curry add
|
| Now let's partially apply it for an _increment_ function:
| -- increment :: Int -> Int increment = addCurr 1
|
| > When I curry `add` above, isn't the argument 1
| contained within the context of the partially applied
| function?
|
| You didn't curry it, you partially applied it. This was
| possible, because your function _add_ was already
| curried.
| kerkeslager wrote:
| Okay, that's a fair critique, let it not be said I can't
| admit when I'm wrong. I shouldn't have used `curry` as a
| verb there, as my point was about the concept of currying
| rather than the specific curry function.
|
| My mistake there doesn't negate my larger point, but I've
| gotta go touch grass.
| whilenot-dev wrote:
| > _no one in this comment chain is confused about what
| currying or partial application are_
|
| I can't agree here either... I know it from my own
| experience when i first learned about currying and
| partial application.
| kerkeslager wrote:
| It does not appear to me that skybrian is first learning
| about currying and partial application, and I'm not first
| learning about currying or partial application in this
| thread either.
|
| Your play at humility that you didn't understand currying
| and partial application in the past kinda falls flat if
| you then go on to less-humbly assume that everyone else
| in the conversation is stuck where you once were.
| threatofrain wrote:
| I thought it was somewhat the reverse -- that the
| original motivation was to simulate multi-arity functions
| in a setting that only allows unary functions.
| bPspGiJT8Y wrote:
| I don't really understand the distinction between "multi-
| arity" and "unary" functions. In my mental model, all
| functions are unary, it's just that in "traditional",
| non-FP languages it's more common to pack arguments into
| tuples. That is `(a, b, c) => ...` in JS is a _unary_
| function which takes a tuple of 3 items. But the thing
| is, FP has tuples too (and in Haskell, with pretty much
| the same syntax), it 's just that most of the time
| arguments aren't packed into tuples.
|
| So could you elaborate to me where the distinction lies
| here and why is there a need to "simulate" things?
| jraph wrote:
| Say you are dealing with a callback mechanism that takes
| a function and (only) one value to pass:
| function c(f, v) { ... f(v) ... }
|
| But what you actually need this callback mechanism to
| call is a function that takes two arguments. No default
| values. function f2(a, b) { ... }
|
| There's no way in js that c is going to call f2 and set
| its b parameter to something. You can't set v to be a
| tuple so b will be filled: c(f2,
| "value") => b will be undefined. c(f2, [1,
| 2]) => a will be set to [1, 2].
|
| you need to wrap f2 for this when calling c:
| function wrapf2(b) { return f2("fixed a", b) }
|
| Or you can make c deconstruct v, or use apply:
| function c(f, v) { ... f(...v) ... } function
| c(f, v) { ... f.apply(null, v) ... }
|
| but then v always needs to be an array:
| c(f, [1]) c(f, [1, 2]) c(f, 1) //
| fails
|
| This is why in Javascript, multi-arity is different from
| unary with tuples. JS doesn't have tuples as a primitive
| / first-class type anyway. In some math notations we
| don't make the difference, but in most programming
| languages there's a distinction. Your model where
| functions are always unary but can take tuples, and (v)
| is the same as v, is correct (and useful when the
| distinction doesn't matter and is just annoying to deal
| with), but doesn't match the way many programming
| languages actually work. I believe even in most
| functional programming languages, (v) is different from
| v, you also said it.
|
| In assembly, different parameters are in fully separate
| registers or stack entries. Interestingly, you could
| imagine representing tuples as C structs (that can have
| only one member) and always pass structs, and passing a
| one-member struct will actually have the exact same
| effect as passing a value of a primitive type.
| Hirrolot wrote:
| Currying and partial application are dualities (think of
| them as function introduction/function elimination), so
| neither subsumes the other one.
|
| However, I agree with the wording of the original comment:
| when you _pass_ some arguments to a constructor and then to
| a method, this is (at least conceptually) partial
| application.
| kerkeslager wrote:
| > Currying and partial application are dualities (think
| of them as function introduction/function elimination),
| so neither subsumes the other one.
|
| Could you say more? I'm not sure I understand what you're
| saying here.
| skybrian wrote:
| You can think of them as equivalent ways to write the
| same code. There are mechanical rewrites to go from one
| to the other, and these rewrites work in any direction.
| Which way is more readable depends on circumstances.
|
| When a function needs a lot of parameters to do a
| calculation, these parameters can be arbitrarily grouped
| into records and supplied in any order. Languages have
| convenient syntaxes for certain ways of grouping
| parameters together.
|
| For example, a closure creates a group of parameters in
| the form of a function.
| xeonmc wrote:
| Currying wraps, partial application unwraps.
| ReleaseCandidat wrote:
| No, not exactly. And partial application of any function is
| nowadays possible using anonymous functions to wrap the
| original one, no need for constructors and special methods.
|
| What currying in Haskell and any other ML does, is the
| following as Javascript. Let's say we have a function with four
| parameters `func(a, b, c, d) {return a + b + c + d;}`, then the
| curried version is the following: func = (a)
| => { return (b) => { return (c) => {
| return (d) => a + b + c + d; } } }
| kerkeslager wrote:
| You're assuming that the poster doesn't understand what
| currying is and you're jumping in with a correction which is
| kinda rude.
|
| Instead of assuming you know better and jumping in with a
| correction, could you reread that post and assume that the
| person knows what they're talking about but isn't
| communicating exactly the way you would have communicated it?
| I.e., read with the intent of understanding intent, rather
| than read with the intent of correcting?
| ReleaseCandidat wrote:
| > but isn't communicating exactly the way you would have
| communicated it?
|
| My answers are almost always less intented for the person
| who's post I'm responding to, but more for a general
| audience. As others have already posted the part about
| "currying isn't partial application", and I mainly wanted
| to add a (hopefully) understanable example.
|
| And I would say that taking the OP's post as if he doesn't
| exactly know, what currying means in comparison to partia
| application _is_ the more favourable interpretation of
| their post.
| kerkeslager wrote:
| > My answers are almost always less intented for the
| person who's post I'm responding to, but more for a
| general audience
|
| Same. I don't expect you're going to admit your mistake,
| but I'm pointing it out because the general audience is
| the sort of audience who is prone to this mistake (which
| I only know because I myself have been corrected for
| making this mistake).
|
| > As others have already posted the part about "currying
| isn't partial application", and I mainly wanted to add a
| (hopefully) understanable example.
|
| And in doing so, you're making the same mistake as those
| other posters, by assuming skybrian doesn't know what
| currying and partial application are. You're not doing
| your audience any favors by painting skybrian's post as
| if he said something wrong.
|
| > And I would say that taking the OP's post as if he
| doesn't exactly know, what currying means in comparison
| to partia application _is_ the more favourable
| interpretation of their post.
|
| How so?
| luc4 wrote:
| It's ironic that your responses come off as much more
| rude and condescending than the comment in question.
| skybrian wrote:
| I'm not offended. I was too brief and I can see how my
| post could be misleading.
| ghusbands wrote:
| It is clear that that poster does not understand what
| currying is, as defined in the first line of the article
| and easily found online; also, downthread [1], nor do you.
| Currying is when you turn a function of multiple parameters
| into nested functions of single parameters. Partially
| applying a curried function is not called currying.
|
| Though if enough people share the confusion, I guess it
| becomes an alternative definition. Language evolves.
|
| Also, in any case, a constructed object is nothing like a
| partially applied function, so the parallel drawn is not
| useful. It seems entirely reasonable in this case to assume
| that the poster does not have sufficient experience or
| knowledge.
|
| [1] https://news.ycombinator.com/item?id=39959651
| skybrian wrote:
| I can see how someone might think I don't know the
| difference, but it's rather that I don't think the
| details are important. They are both ways of partially
| applying a function. I wanted to demystify currying and
| show the equivalence, but I was too brief about it.
|
| A method is a function with convenient syntax. Some of
| its arguments come from the "this" parameter. If the
| object is immutable and the method has no side-effects,
| it's a pure function.
|
| Converting a function to an immutable class with a method
| is a mechanical refactoring that I sometimes do, when the
| class has an intuitive name. The code does the same
| thing.
| Hirrolot wrote:
| The technique you describe requires us to predict what data
| should be partially applied (before actually using it), and
| what data should be applied and used immediately. In contrast,
| currying/partial application doesn't require this decision from
| us -- for this reason I consider the functional approach more
| flexible than the object-oriented one.
|
| In addition to that, I don't always find the constructor/method
| approach more readable. It _can_ be readable, if you design
| your API with care; however, it is common to group related data
| into records in the FP world as well, which mimicks OOP
| constructors, in a sense.
| skybrian wrote:
| Yes, like many refactorings, rewriting a function as an
| immutable class with a method is not always a win. It depends
| on how you want to group a function's parameters, whether
| that grouping has an intuitive name, and whether you might
| want to write other methods that take the same initial
| parameters. Sometimes grouping parameters using a record type
| is better, though you don't get the nice syntax.
|
| If currying isn't built-in, you can emulate it. In TypeScript
| you need to do it explicitly, which means anticipating that
| it will be used, and whenever I try that, I later decide that
| it's needlessly obscure and rewrite the code some other way.
|
| Even with built-in currying, you still need to anticipate
| usage by changing the order of the parameters, based on which
| ones you expect the callers to have first. It's less general
| than writing an inline function with exactly the parameters
| you need. TypeScript has nice syntax for defining tiny, one-
| off functions and I think that's better than having currying.
|
| One side-effect of having currying in a language is that it
| discourages naming things, and I think that's often bad for
| readability. A chain of method calls gives you a lot of names
| to look up and a place to put documentation.
| kerkeslager wrote:
| Currying is one of those things in Haskell that always makes me
| think that Haskellers were so preoccupied with whether or not
| they _could_ , that they didn't stop to think if they _should_.
|
| In my limited Haskell experience, you can mostly ignore that
| functions are curried, but every once in a while, someone uses
| it, and it has never, in my experience, made the code easier to
| understand, because the currying happens implicitly.
|
| The more general case of currying is partial application, where
| instead of a function taking only its first argument and
| returning a function that takes its next argument, you can apply
| a function any of it's arguments and return a function that takes
| the remaining arguments. So if you have: div x y
| = x / y
|
| Then: invert = (\x -> div 1 x) -- invert is
| a partial application of div to the 1 as first argument
| halve = (\x -> div x 2) -- halve is a partial application
| of div to 2 as the second argument
|
| Obviously, the first one could be done with currying. What makes
| partial application better in my opinion is that it's explicit
| about what's happening, and doesn't happen without you asking it
| to.
| ReleaseCandidat wrote:
| Just a remark, currying is nothing that is special to Haskell,
| it's a general ML "speciality" including the syntax for
| function application of not using parens.
|
| Which is because lambda calculus does not have functions with
| more than one parameter, I guess.
| kerkeslager wrote:
| Yeah, I'd attribute this to the lambda calculus, for sure.
|
| And in the lambda calculus, it makes a ton of sense, because
| it allows you to break proofs into smaller chunks more
| easily.
|
| But in a general-purpose programming language where the
| proofs are automated, I think this just makes the code less
| communicative of programmer intent.
| kamray23 wrote:
| I don't think currying happens without you asking to, though.
| It happens because it happens, it's part of the language, and
| it's something you implicitly keep in the back of your mind
| every time you see a function call. I don't program a lot in
| Haskell, only some maths things I sometimes might need since it
| is rather useful for that, but the concept of currying is so
| natural that it's constantly expressing itself in the code.
| Very rarely do you apply arguments and consider that to be a
| function call in itself instead of like, three function calls.
| And since partial application is so incredibly important to
| Haskell and other similar languages, without currying writing
| would be very difficult. Consider the actual simple example of
| gears = filter ((==2) . length) . map
| (neighbouringNumbers numbers) $ filter ((=='*')
| . fst) symbols
|
| which without currying would have to look like
| gears = (\xs -> filter ((\x -> x == 2) . length) xs)
| . (\xs -> map (\x -> neighbouringNumbers numbers x) xs)
| $ filter (\(c,_) -> c == '*') symbols
|
| It just makes partial application a lot easier, especially when
| this kind of code pops up all over the place.
| kerkeslager wrote:
| > I don't think currying happens without you asking to,
| though. It happens because it happens, it's part of the
| language, and it's something you implicitly keep in the back
| of your mind every time you see a function call.
|
| Eh, but that's my point: I want less cognitive load. Currying
| is another thing that I have to keep in the back of my mind,
| and Haskell already has way too many of those, and it's not a
| particularly _useful_ thing. I 've got limited space in the
| back of my mind for things and if I'm going to keep things in
| the back of my mind I want them to be useful. I mean, if your
| argument in favor of currying is that it saved you a few
| keystrokes in that example, color me unimpressed.
|
| Maybe I'm just too stupid to understand easily, but the
| "simple" example you're giving is taking me a while to
| understand. If a junior dev on my team submitted that in a PR
| I'd send it back asking them to break it up into a few
| smaller named functions and probably not use a partial
| application at all. Something like "I know it's fun to be
| clever but let's make this easier for the next person who has
| to figure out what it does".
|
| I guess what I'm saying is that for that example it seems
| like you're going for tersity rather than clarity for future
| readers of the code. If you were going for clarity you
| probably wouldn't write it either of the ways you've given.
|
| And in big projects clarity _is the number 1 concern_ [1]. In
| toy examples like this I can slog through and figure
| something like this out, but when it's 30 functions written
| like this, all calling each other in the most clever ways
| possible, _nobody_ can figure it out.
|
| [1] EDIT: Okay #2 after correctness perhaps. But it becomes
| hard to achieve correctness without clarity as a project
| grows.
| nightlyherb wrote:
| Hi, as someone who has been interested in functional
| programming for a while but who struggled to read point-
| free Haskell code until recently, I think it might be
| useful to share my perspective. gears =
| filter ((==2) . length) . map
| (neighbouringNumbers numbers) $ filter
| ((=='*') . fst) symbols
|
| If this Haskell code does not look clear, it's because
| people are unfamiliar with point-free style, not because
| the this code is badly written, and especially not because
| the reader is stupid.
|
| The given Haskell code reads naturally to me now. (I'm only
| a little uncertain because I can't write Haskell.) It would
| have been line noise to me before before point-free style
| clicked.
|
| I tend to read Haskell code in articles from right to left,
| and it reads: "Choose the symbols whose first letter is
| '*', get their respective (neighboringNumbers numbers), and
| choose the results whose length is 2". I read
| (neighboringNumbers numbers) as if it were a noun.
|
| I don't know how this clicked, but I'm pretty sure it has
| nothing to do with stupidity. Perhaps it was by chance, or
| perhaps it was by banging my head against the wall enough
| times.
|
| Would I introduce point-free style in a JS codebase?
| Probably not. People are unfamiliar with this kind of
| style. Would I introduce this style in a Haskell codebase?
| Almost certainly, because it's clear and I think it
| reflects how Haskell programmers think.
| skybrian wrote:
| They say that array languages are pretty readable once
| you get used to them, too? But you're drastically
| limiting your audience. Part of readability is writing
| for people who aren't as fluent as you are.
|
| Expert jargon can _sometimes_ be useful, but it often
| obscures things that would be pretty simple if written
| some other way.
|
| With Haskell there's a tension between saying "I only
| care about writing for other expert programmers" and
| "more people should learn Haskell." The idioms are part
| of the turnoff.
| Simon_ORourke wrote:
| > Currying is one of those things in Haskell that always makes
| me think that Haskellers were so preoccupied with whether or
| not they could, that they didn't stop to think if they should.
|
| This had to be said! Sure you can make the argument that this
| improves your productivity as a coder, or makes the code more
| concise, but to someone coming along cold and trying to pick
| apart your code to fix something it's going to be a nightmare.
| boxed wrote:
| Currying makes positional only parameters look cooler and
| fancier. It's a trap. Labeled arguments is the way to go for 99%
| of all parameters. Accidental currying is horrible.
| fxj wrote:
| Currying is only a part of the functional programming stack. It
| can make your life easier when you combine it with other
| functional programming techniques like tacit programming. e.g.
| you have a function with a very long arguments list and dont want
| to write the arguments over and over again, but still dont want
| to define an additional function.
|
| https://en.wikipedia.org/wiki/Tacit_programming
|
| https://medium.com/@jesterxl/real-world-uses-of-tacit-progra...
|
| when you use tacit programming and currying together you can
| write code in a "bash-like" pipe style which not only makes the
| program more readable but also is less error prone.
|
| https://wiki.haskell.org/Pointfree
| boxed wrote:
| You can have partial function application without currying. The
| problem with currying is that it's implicit, and positional.
| Two very bad things in programming.
| fxj wrote:
| As I said in another comment down below. Currying is great
| for rapid prototyping because you dont have to define a new
| function. eg. add1 and add(1) which you then can use for
| map/reduce or pointfree programming. In production code it
| often makes more sense to be a bit more detailed and verbose
| to make it readable for devs who are not familiar with
| currying. But abstraction in general is not something that is
| "bad".
|
| high level abstraction can make you very productive, but it
| can also be rather unreadable. e.g. two code snippets in
| python (yes this is valid python code with some operator
| overloading):
|
| # compute pi by drawing random numbers in a circle
| 1000000 >> ps(
| ps(kh>>op("(x**2+y**2)**0.5<1")@rlu<<kh)>>S*4>> _/_)
|
| or this: # 10 fibonacci numbers
| [x:=[1,1]] + [x := [x[1], sum(x)] for i in range(10)]
|
| I would of course not use that in production code, but for a
| rapid prototype it is priceless to do something like this in
| python.
|
| just my 2 ct
| skybrian wrote:
| I think a lot of people would disagree that they make code more
| readable. These are all ways of removing the names of
| intermediate results. Some names are noise, but more often,
| they're useful documentation.
|
| You can make code much shorter by removing all the application-
| specific names and only using very abstract, domain-independent
| names or symbols. The result can be seen in complicated regular
| expressions, array languages, bash scripts, and so on. In the
| wrong hands, these are all notoriously unreadable.
|
| But you can also go to the other extreme and give every little
| thing a very long name, and that's less readable in its own
| way. Bob Nystrom wrote a nice article about how to combat that
| tendency. [1]
|
| Naming things is an art. In functional languages, let
| expressions are convenient and useful. When you feel like you
| don't want to define another function, sometimes it's better to
| resist that tendency and think about a better name.
|
| [1] https://journal.stuffwithstuff.com/2016/06/16/long-names-
| are...
| whateveracct wrote:
| I read Haskell code structurally and don't pronounce symbols
| in my head.
|
| When it comes to names, the binding and lexical scope and
| position in the AST helps me read the code more than the
| English pronunciation.
|
| Tbh if I have a stream of English words in my head reading
| Haskell, it's probably already meh code at best :S
|
| I'd say I see more Haskell code made unreadable due to over-
| naming than under-naming in the wild (i.e. industry)
| danmur wrote:
| Haskell always makes me hungry
| jraph wrote:
| With a bit of ginger, some lemon and perhaps some coco, it's
| excellent.
| indigosun wrote:
| I recently saw the title, Learn Physics with Functional
| Programming, and thought to myself... I know physics and
| functional programming but I want to learn Haskell.
|
| Having gone through it, I highly recommend the book; especially
| to anyone knowledgeable about any 2 and interested in the third.
|
| I love Haskell. I love writing it, and reading it. Haskell is a
| beautiful programming language. One where I felt immediately at
| home in and comforted by.
|
| Currying is one of the foundational concepts of the language, but
| I do think it is one aspect of the language the community treats
| excessively sacredly.
|
| In Haskell, function composition has right associativity. What
| this means, in the event of a function that takes in 3 Integers
| as arguments and returns a Double would have a type definition
| of: FN_NAME :: Int -> Int -> Int -> Double
|
| The right associativity then also means this is equivalent to
| these type definition: fn0 :: Int -> Int -> Int
| -> Double fn0 x y z = (fromIntegral (x + y)) /
| (fromIntegral z) fn1 :: Int -> (Int -> Int ->
| Double) fn1 x y z = (fromIntegral (x + y)) /
| (fromIntegral z) fn2 :: Int -> (Int -> (Int ->
| Double)) fn2 x y z = (fromIntegral (x + y)) /
| (fromIntegral z) ghci> fn0 1 2 3 -- 1.0
| ghci> fn1 1 2 3 -- 1.0 ghci> fn2 1 2 3 --1.0
|
| This is currying in action. A function that takes three arguments
| is the same as a function that takes one argument and passes it
| to function that takes two arguments.
|
| As I understand it, the compiler sees ALL functions as a
| composition of functions of one input.
|
| This is pretty cool, and a powerful foundation to build on. Where
| I take a bit of issue is when the Haskell community allows the
| rigor of the type definition to carry some of the weight of the
| expressiveness of the function definition.
|
| Each function definition ('=') pairs with a type definition
| ('::'), as the ones above, ie. an example from the promoted book:
| type R = Double type VecDerivative = (R -> Vec)
| -> R -> Vec vecDerivative :: R -> VecDerivative
| vecDerivative dt v t = (((v (t + dt/2)) ^-^ (v (t - dt/2))) ^/
| dt) velFromPos :: R -- dt
| -> (R -> Vec) -- position function -> (R ->
| Vec) -- velocity function velFromPos = vecDerivative
|
| The second function definition: "velFromPos = vecDerivative" ; is
| equal to saying, "velFromPos dt v t = vecDerivative dt v t".
| Haskell seems to allows this syntactical ~implicit argument
| passing because of it's reverence for currying.
| -- random example of 'vector-valued function' v1 :: R ->
| Vec v1 t = ((2 *^ (t**2 *^ iHat)) ^+^ (3 *^ (t**3 *^
| jHat)) ^+^ (t**4 *^ kHat)) ghci> vecDerivative
| 0.01 v1 1 -- vec 3.999999999999937 9.000074999999885
| 4.000099999999962 ghci> velFromPos 0.01 v1 1 -- vec
| 3.999999999999937 9.000074999999885 4.000099999999962
|
| This has some interesting effects, such as displayed above where
| the use of currying makes it explicitly clear that calculating
| the velocity is the exact same thing as calculating the
| derivative of position.
|
| But I always felt this ~implicit syntax is needlessly aggressive
| to newcomers.
|
| I also pathologically write my Haskell code like a Lisp because
| the Haskell community's celebration of the complex precedence
| structure of its order of operations causes practitioners to
| appear, to my eyes, over confident in their ability to sight read
| their own work's order of operations. ;P ghci>
| sqrt 2 + 1 * 3 + 3 * 2 + 1 / 7 -- 10.557070705230238
| ghci> ((sqrt 2) + ((1 * 3) + ((3*2) + (1 / 7)))) --
| 10.557070705230238
|
| I will forever think the second is easier to read, but if you
| look at many Haskell code bases you will see stuff like the first
| all over the place, and I have been unable to find a reason why
| the implicit function arguments and lack of parenthesis syntax is
| considered idiomatic.
|
| In the face of the ability to write the functions explicitly and
| with expressive parentheses, it seems the answer is "because you
| can".
|
| https://nostarch.com/learn-physics-functional-programming
___________________________________________________________________
(page generated 2024-04-07 23:02 UTC)