frontpage.
newsnewestaskshowjobs

Made with ♥ by @iamnishanth

Open Source @Github

fp.

Open in hackernews

A Case Against Currying

https://emi-h.com/articles/a-case-against-currying.html
40•emih•2h ago

Comments

messe•1h ago
What benefit does drawing a distinction between parameter list and single-parameter tuple style bring?

I'm failing to see how they're not isomorphic.

Pay08•1h ago
The tuple style can't be curried (in Haskell).
messe•1h ago
That's not what I'm talking about.

The article draws a three way distinction between curried style (à la Haskell), tuples and parameter list.

I'm talking about the distinction it claims exists between the latter two.

disconcision•1h ago
all three are isomorphic. but in some languages if you define a function via something like `function myFun(x: Int, y: Bool) = ...` and also have some value `let a: (Int, Bool) = (1, true)` it doesn't mean you can call `myFun(a)`. because a parameter list is treated by the language as a different kind of construct than a tuple.
antonvs•42m ago
A language which truly treats an argument list as a tuple can support this:

    args = (a, b, c)
    f args
…and that will have the effect of binding a, b, and c as arguments in the called function.

In fact many “scripting” languages, like Javascript and Python, support something close to this using their array type. If you squint, you can see them as languages whose functions take a single argument that is equivalent to an array. At an internal implementation level this equivalence can be messy, though.

Lower level languages like C and Rust tend not to support this.

emih•1h ago
That's a fair point, they are all isomorphic.

The distinction is mostly semantic so you could say they are the same. But I thought it makes sense to emphasize that the former is a feature of function types, and the latter is still technically single-parameter.

I suppose one real difference is that you cannot feed a tuple into a parameter list function. Like:

fn do_something(name: &str, age: u32) { ... }

let person = ("Alice", 40);

do_something(person); // doesn't compile

Kambing•1h ago
They are isomorphic in the strong sense that their logical interpretations are identical. Applying Curry-Howard, a function type is an implication, so a curried function with type A -> B -> C is equivalent to an implication that says "If A, then if B, then C." Likewise, a tuple is a conjunction, so a non-curried function with type (A, B) -> C is equivalent to the logic statement (A /\ B) -> C, i.e., "If A and B then C." Both logical statements are equivalent, i.e., have the same truth tables.

However, as the article outlines, there are differences (both positive and negative) to using functions with these types. Curried functions allow for partial application, leading to elegant definitions, e.g., in Haskell, we can define a function that sums over lists as sum = foldl (+) 0 where we leave out foldl's final list argument, giving us a function expecting a list that performs the behavior we expect. However, this style of programming can lead to weird games and unweildy code because of the positional nature of curried functions, e.g., having to use function combinators such as Haskell's flip function (with type (A -> B -> C) -> B -> A -> C) to juggle arguments you do not want to fill to the end of the parameter list.

messe•1h ago
Please see my other comment below, and maybe re-read the article. I'm not asking what the difference is between curried and non-curried. The article draws a three way distinction, while I'm asking why two of them should be considered distinct, and not the pair you're referring to.
recursivecaveat•1h ago
Probably just that having parameter-lists as a specific special feature makes them distinct from tuple types. So you may end up with packing/unpacking features to convert between them, and a function being generic over its number of parameters is distinct from it being generic over its input types. On the other hand you can more easily do stuff like named args or default values.
layer8•37m ago
The parameter list forces the individual arguments to be visible at the call site. You cannot separate the packaging of the argument list from invoking the function (barring special syntactic or library support by the language). It also affects how singleton tuples behave in your language.

The article is about programmer ergonomics of a language. Two languages can have substantially different ergonomics even when there is a straightforward mapping between the two.

naasking•25m ago
Presumably creating a different class for parameter lists allows you to extend it with operations that aren't natural to tuples, like named arguments.
rocqua•17m ago
It's not that they are meaningfully different. It's just acknowledging if you really want currying, you can say 'why not just use a single parameter of tuple type'.

Then there's an implication of 'sure, but that doesn't actually help much if it's not standar' and then it's not addressed further.

recursivecaveat•1h ago
Currying was recently removed from Coalton: https://coalton-lang.github.io/20260312-coalton0p2/#fixed-ar...
emih•1h ago
Thanks for sharing, interesting to see that people writing functional languages also experience the same issues in practice. And they give some reasons I didn't think about.
leoc•48m ago
> 3. Better type errors. With currying, writing (f 1 2) instead of (f 1 2 3) silently produces a partial application. The compiler happily infers a function type like :s -> :t and moves on. The real error only surfaces later, when that unexpected function value finally clashes with an incompatible type, often far from the actual mistake. With fixed arity, a missing argument is caught right where it happens.

'Putting things' (multi-argument function calls, in this case) 'in-band doesn't make them go away, but it does successfully hide them from your tooling', part 422.

Pay08•1h ago
I'm biased here since the easy currying is by far my favourite feature in Haskell (it always bothers me that I have to explicitly create a lamba in Lisps) but the arguments in the article don't convince me, what with the synctactic overhead for the "tuple style".
lukev•1h ago
I'd got a step further and say that in business software, named parameters are preferable for all but the smallest functions.

Using curried OR tuple arg lists requires remembering the name of an argument by its position. This saves room on the screen but is mental overhead.

The fact is that arguments do always have names anyway and you always have to know what they are.

layer8•1h ago
I want to agree, but there is the tension that in business code, what you pass as arguments is very often already named like the parameter, so having to indicate the parameter name in the call leads to a lot of redundancy. And if you’re using domain types judiciously, the types are typically also different, hence (in a statically-typed language) there is already a reduced risk of passing the wrong parameter.

Maybe there could be a rule that parameters have to be named only if their type doesn’t already disambiguate them and if there isn’t some concordance between the naming in the argument expression and the parameter, or something along those lines. But the ergonomics of that might be annoying as well.

sestep•1h ago
This is an issue in Python but less so in languages like JavaScript that support "field name punning", where you pass named arguments via lightweight record construction syntax, and you don't need to duplicate a field name if it's the same as the local variable name you're using for that field's value.
layer8•54m ago
That forces you to name the variable identically to the parameter. For example, you may want to call your variable `loggedInUser` when the fact that the user is logged in is important for the code’s logic, but then you can’t pass it as-is for a field that is only called `user`. Having to name the parameter leads to routinely having to write `foo: blaFoo` because just `blaFoo` wouldn’t match, or else to drop the informative `bla`. That’s part of the tension I was referring to.
twic•11m ago
OCaml has a neat little feature where it elides the parameter and variable name if they're the same:

  let warn_user ~message = ... (* the ~ makes this a named parameter *)

  let error = "fatal error!!" in
  warn_user ~message:error; (* different names, have to specify both *)

  let message = "fatal error!!" in
  warn_user ~message; (* same names, elided *)
The elision doesn't always kick in, because sometimes you want the variable to have a different name, but in practice it kicks in a lot, and makes a real difference. In a way, cases when it doesn't kick in are also telling you something, because you're crossing some sort of context boundary where some value is called different things on either side.
leoc•1h ago
Right. Currying as the default means of passing arguments in functional languages is a gimmick, a hack in the derogatory sense. It's low-level and anti-declarative.
layer8•1h ago
I completely agree. Giving the first parameter of a function special treatment only makes sense in a limited subset of cases, while forcing an artificial asymmetry in the general case that I find unergonomic.
paldepind2•1h ago
I completely agree with the points in this article and have come to the same conclusion after using languages that default to unary curried functions.

> I'd also love to hear if you know any (dis)advantages of curried functions other than the ones mentioned.

I think it fundamentally boils down to the curried style being _implicit_ partial application, whereas a syntax for partial application is _explicit_. And as if often the case, being explicit is clearer. If you see something like

    let f = foobinade a b
in a curried language then you don't immediately know if `f` is the result of foobinading `a` and `b` or if `f` is `foobinade` partially applied to some of its arguments. Without currying you'd either write

    let f = foobinade(a, b)
or

    let f = foobinade(a, b, $) // (using the syntax in the blog post)
and now it's immediately explicitly clear which of the two cases we're in.

This clarity not only helps humans, it also help compilers give better error messages. In a curried languages, if a function is mistakenly applied to too few arguments then the compiler can't always immediately detect the error. For instance, if `foobinate` takes 3 arguments, then `let f = foobinade a b` doesn't give rise to any errors, whereas a compiler can immediately detect the error in `let f = foobinade(a, b)`.

A syntax for partial application offers the same practical benefits of currying without the downsides (albeit loosing some of the theoretical simplicity).

munchler•45m ago
Well, I totally disagree with this. One of the main benefits of currying is the ability to chain function calls together. For example, in F# this is typically done with the |> operator:

    let result =
        input
            |> foobinade a b
            |> barbalyze c d
Or, if we really want to name our partial function before applying it, we can use the >> operator instead:

    let f = foobinade a b >> barbalyze c d
    let result = f input
Requiring an explicit "hole" for this defeats the purpose:

    let f = barbalyze(c, d, foobinade(a, b, $))
    let result = f(input)
Or, just as bad, you could give up on partial function application entirely and go with:

    let result = barbalyze(c, d, foobinade(a, b, input))
Either way, I hope that gives everyone the same "ick" it gives me.
emih•39m ago
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.
raincole•27m ago
Wow, this convinced me. It's so obviously the right approach when you put it this way.
twic•18m ago
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 '", $, "'!")
raincole•10m ago
Yeah, especially in F#, a language that means to interpolate with .Net libraries (most not written with "data input at last" mindset.) now I'm quite surprised that F# doesn't have this feature.
skybrian•28m ago
For pipelines in any language, putting one function call per line often works well. Naming the variables can help readability. It also makes using a debugger easier:

  let foos = foobinate(a, b, input)
  let bars = barbakize(c, d, foos)
Other languages have method call syntax, which allows some chaining in a way that works well with autocomplete.
jwarden•1h ago
Here’s an article I wrote a while ago about a hypothetical language feature I call “folded application”, that makes parameter-list style and folded style equivalent.

https://jonathanwarden.com/implicit-currying-and-folded-appl...

01HNNWZ0MV43FF•1h ago
I've never ever run into this. I haven't seen currying or partial application since college. Am I the imperative Blub programmer, lol?
mkprc•55m ago
Prior to this article, I didn't think of currying as being something a person could be "for" or "against." It just is. The fact that a function of multiple inputs can be equivalently thought of as a function of a tuple can be equivalently thought of as a composite of single-input functions that return functions is about cognition, and understanding structure, not code syntax.
kevincox•41m ago
But it is about code syntax. Languages like Haskell make it part of the language by only supporting single-argument functions. So currying is the default behaviour for programmers.

I think you are focusing on the theoretical aspect of partial application and missing the actual argument of the article which having it be the default, implicit way of defining and calling functions isn't a good programming interface.

bbkane•32m ago
Similar to how lambda calculus "just is" (and it's very elegant and useful for math proofs), but nobody writes non-trivial programs in it...
titzer•49m ago
I agree with this article. Tuples nicely unified multiple return values and multiple parameters. FWIW Scala and Virgil both support the _ syntax for the placeholder in a partial application.

    def add(x: int, y: int) -> int { return x + y; }
    def add3 = add(_, 3);
Or more simply, reusing some built-in functions:

    def add3 = int.+(_, 3);
talkingtab•41m ago
Yesterday there was an obscure thing on ant mills. (see wikipedia for a visual). I had an instant of recognition. Hacker News!

Today I read this article and after plowing through the article came to where the meat was supposed to be and the points made to my mind are weak. The conclusions. I just "followed" this whole article to lead to .... nothing.

I had another instance of recognition. It is like one of those recipe sites where you page down and down, reading the persons life story and get to a recipe that is .... ineffective.

Then it occurred to me that the problem is not AI generated content. It is ant mill media. Like Hacker News is now, like the Washington Post (thanks there Jeff Bezos), the greed-centric New York Times.

If you have read this far, here is the punch line:

The government generated by an ant mill is completely inept and ineffective. So if you look up and around at the absolute mess, does it look like an ant mill to you?

emih•32m ago
It's not that serious :)
raincole•15m ago
Could you explain how this comment is relevant?
bbkane•41m ago
The Roc devs came to a similar conclusion: https://www.roc-lang.org/faq#curried-functions

(Side note: if you're reading this Roc devs, could you add a table of contents?)

jstrieb•40m ago
I like currying because it's fun and cool, but found myself nodding along throughout the whole article. I've taken for granted that declaring and using curried functions with nice associativity (i.e., avoiding lots of parentheses) is as ergonomic as partial application syntax gets, but I'm glad to have that assumption challenged.

The "hole" syntax for partial application with dollar signs is a really creative alternative that seems much nicer. Does anyone know of any languages that actually do it that way? I'd love to try it out and see if it's actually nicer in practice.

emih•28m ago
Glad to hear the article did what I meant for it to do :)

And yes, another comment mentioned that Scala supports this syntax!

rocqua•25m ago
Someone else in the comments mentioned that scala does this with _ as the placeholder.
zyxzevn•40m ago
With a language like Forth, you know that you can use a stack for data and apply functions on that data. With currying it you put functions on a stack instead. This makes it weird. But you also obscure the dataflow.

With the most successful functional programing language Excel, the dataflow is fully exposed. Which makes it easy.

Certain functional programming languages prefer the passing of just one data-item from one function to the next. One parameter in and one parameter out. And for this to work with more values, it needs to use functions as an output. It is unnecessary cognitive burden. And APL programmers would love it.

Let's make an apple pie as an example. You give the apple and butter and flour to the cook. The cursed curry version would be "use knife for cutting, add cutting board, add apple, stand near table, use hand. Bowl, add table, put, flour, mix, cut, knife butter, mixer, put, press, shape, cut_apple." etc..

codethief•29m ago
I've long been thinking the same thing. In many fields of mathematics the placeholder $ from the OP is often written •, i.e. partial function application is written as f(a, b, •). I've always found it weird that most functional languages, particularly heavily math-inspired ones like Haskell, deviate from that. Yes, there are isomorphisms left and right but at the end of the day you have to settle on one category and one syntax. A function f: A × B -> C is simply not the same as a function f: A -> B -> C. Stop treating it like it is.
vq•25m ago
One "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 compiler optimisation.

emih•9m ago
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 article. I think it could, but the compiler might need to do some extra work?

kubb•22m ago
The article lists two arguments against Currying:

  1) "performance is a bit of a concern"
  2) "curried function types have a weird shape"
2 is followed by single example of how it doesn't work the way the author would expect it to in Haskell.

It's not a strong case in my opinion. Dismissed.

skybrian•18m ago
There 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.
raincole•4m ago
I believe one of the main reasons that F# hasn't never really taken off is that Microsoft isn't afraid to borrow the good parts of F# to C#. (They really should've ported discriminated unions though)
kajaktum•12m ago
I feel like not having currying means your language becomes semantically more complicated because where does lambdas come from?

Bored of eating your own dogfood? Try smelling your own farts

https://shkspr.mobi/blog/2026/03/bored-of-eating-your-own-dogfood-try-smelling-your-own-farts/
154•ColinWright•2h ago•64 comments

Flash-Moe: Running a 397B Parameter Model on a Mac with 48GB RAM

https://github.com/danveloper/flash-moe
156•mft_•3h ago•54 comments

Building an FPGA 3dfx Voodoo with Modern RTL Tools

https://noquiche.fyi/voodoo
51•fayalalebrun•2h ago•4 comments

iBook Clamshell

https://www.ibook-clamshell.com/index.php/en/
19•polishdude20•52m ago•7 comments

Project Nomad – Knowledge That Never Goes Offline

https://www.projectnomad.us
77•jensgk•2h ago•11 comments

Apple's intentional crippling of Mobile Safari continues

https://pwa.gripe/
46•xd1936•2h ago•31 comments

Convincing Is Not Persuading

https://blog.alaindichiappari.dev/p/convincing-is-not-persuading
24•alainrk•2h ago•15 comments

Brute-Forcing My Algorithmic Ignorance with an LLM in 7 Days

http://blog.dominikrudnik.pl/my-google-recruitment-journey-part-1
35•qikcik•3h ago•11 comments

More common mistakes to avoid when creating system architecture diagrams

https://www.ilograph.com/blog/posts/more-common-diagram-mistakes/
43•billyp-rva•3h ago•18 comments

25 Years of Eggs

https://www.john-rush.com/posts/eggs-25-years-20260219.html
140•avyfain•3d ago•45 comments

A Review of Dice That Came with the White Castle

https://boardgamegeek.com/thread/3533812/a-review-of-dice-that-came-with-the-white-castle
39•doener•3d ago•4 comments

$ teebot.dev – from terminal to tee in 6 seconds

https://teebot.dev
19•foxpress•3h ago•20 comments

Node.js worker threads are problematic, but they work great for us

https://www.inngest.com/blog/node-worker-threads
34•goodoldneon•3d ago•13 comments

The IBM scientist who rewrote the rules of information just won a Turing Award

https://www.ibm.com/think/news/ibm-scientist-charles-bennett-turing-award
15•rbanffy•3h ago•2 comments

Hormuz Minesweeper – Are you tired of winning?

https://hormuz.pythonic.ninja/
486•PythonicNinja•6h ago•330 comments

My first patch to the Linux kernel

https://pooladkhay.com/posts/first-kernel-patch/
160•pooladkhay•2d ago•31 comments

Monuses and Heaps

https://doisinkidney.com/posts/2026-03-03-monus-heaps.html
31•aebtebeten•3d ago•1 comments

Why Lab Coats Turned White

https://www.asimov.press/p/lab-coat
16•mailyk•3d ago•4 comments

Tinybox – A powerful computer for deep learning

https://tinygrad.org/#tinybox
542•albelfio•19h ago•312 comments

Some things just take time

https://lucumr.pocoo.org/2026/3/20/some-things-just-take-time/
776•vaylian•1d ago•249 comments

How We Synchronized Editing for Rec Room's Multiplayer Scripting System

https://www.tyleo.com/blog/how-we-synchronized-editing-for-rec-rooms-multiplayer-scripting-system
11•tyleo•3h ago•8 comments

The three pillars of JavaScript bloat

https://43081j.com/2026/03/three-pillars-of-javascript-bloat
400•onlyspaceghost•13h ago•235 comments

Professional video editing, right in the browser with WebGPU and WASM

https://tooscut.app/
317•mohebifar•17h ago•115 comments

Chest Fridge (2009)

https://mtbest.net/chest-fridge/
153•wolfi1•14h ago•80 comments

HopTab–free,open source macOS app switcher and tiler that replaces Cmd+Tab

https://www.royalbhati.com/hoptab
76•robhati•9h ago•23 comments

Turns out your coffee addiction may be doing your brain a favor

https://www.theregister.com/2026/03/21/turns_out_your_coffee_addiction/
35•Bender•2h ago•16 comments

Floci – A free, open-source local AWS emulator

https://github.com/hectorvent/floci
237•shaicoleman•17h ago•78 comments

Vatican Rebukes Peter Thiel's Antichrist Lectures in Rome

https://www.thenerdreich.com/peter-thiels-antichrist-circus-smacked-down-in-rome/
117•vrganj•5h ago•87 comments

Electronics for Kids, 2nd Edition

https://nostarch.com/electronics-for-kids-2e
232•0x54MUR41•3d ago•48 comments

'Miracle': Europe reconnects with lost spacecraft

https://phys.org/news/2026-03-miracle-europe-reconnects-lost-spacecraft.html
76•vrganj•5h ago•30 comments