135 comments

[ 3.4 ms ] story [ 56.7 ms ] thread
It seems to me that the author understands what lambdas are used for and why they don’t really act as lambdas. Still, they’re useful, so it seems like the actual point is that the keyword just exists to punish people who use it with syntactic overhead.
Curious what did more to reframe the lambda keyword into a generic term for simple functions, AWS lambda or python lambda
I think the colloquial usage has roots in Python's use of it, I've been hearing about anonymous functions being called lambdas for a long time. The Lambda service's name seems to play on that prior colloquial usage.
Scheme has the lambda special form, predating Python by decades, and its version also allows for multiple parameters in a non-curried way. SICP served as a classical textbook in computer science for multiple generations of students, so I think that a lot of people's first introduction to lambdas as anonymous functions came from Scheme:

https://sarabander.github.io/sicp/html/1_002e3.xhtml#g_t1_00...

And the use of the LAMBDA keyword goes all the way back to John McCarthy's original paper on Lisp from 1960:

https://archive.org/details/recursive-functions-symbolic-exp...

Wow wasn't aware of this long history.

My hot take for which I'm sure I'll get some hate is all languages should just include js-style arrow functions

Probably little hate, it's a long existing style. Haskell, Erlang, the entire ML line of languages, and probably many more have all used it since last century. It came from mathematical notation, and it is concise.
Aside from the scheme example in the sibling reply, ruby has had a lambda, as a special kind of proc, for a very very long time and it is similarly not a "real lambda."

Honestly it's likely that for most of the history of the term it's been more used for "incorrect" meanings than "correct" ones.

I wish python would support multi line lambdas. Having to define a separate function often makes thing considerably less readable in my experience.
Python supports multi-line lambdas (use parentheses). However, it does not support lambda functions. Python lambdas can only be expressions.
I am not fan of Ruby but this is a thing that Ruby nailed on (blocks).
> I wish python would support multi line lambdas

It does, it just doesn’t support statements (not even one) in lambdas, since a lambda (however many lines it takes up) contains a single expression.

It does. Just add parentheses.
"there should be no lambda in Python"

According to this blog post there isn't, and yet there is? I'm more confused by the way this was written than Python's implementation of the "lambda" keyword.

My paraphrase: There should be no lambda keyword in Python because the lambda keyword implies idiomatic support for lambda calculus, which Python does not have. (I infer that the author supports renaming the keyword to something like inline instead.)
The author says as much in the post. There's some discussion of inline towards the end.
The original lambda calculus also didn't have implicit currying for multi-variable functions. If you wanted to curry a function you had to write out

    λx.λy.λz. x (y z)
If you wrote the much more user friendly

    λ x y z . x (y z)
And tried currying it then it would fail.

The author is complaining that Python doesn't use a dialect of lambda functions used in Haskell. Which is true. Python isn't Haskell and shouldn't be.

Clickbait title. Author wants Python to have lambdas. They just want them to not be called lambdas because they don't support currying and are niche compared to lambdas in functional languages.
Gender doesn't really add anything material to this conversation. This is useless nit-picking.

  > datetime.date(2018, 3, 4)
  > datetime.date(2018)(3)(4)
It's very obvious that the first of these date() function must be a function of three arguments (year, month, day) and the second, if the two calls are meant to return the same result, must be a function of one argument (year) that returns a function of one argument (month) that returns a function of one argument (day) that returns, presumably, a date.

Syntactic sugar to call one date() as if it was the completely different other one would be recklessly dangerous. In particular, there are default parameters: date(1848) might legitimately mean something like 1/1/1848 (defaulting day and month to 1 and 1), not "give me a curried factory of dates in 1848".

> In Python our functions are not curried

Sure you can do that...

  def add(a, b): return a + b

  incr = lambda b: add(1, b) # like incr = l_add 1

  incr(3) # ouputs 4
Is this whole article a complaint that Python isn't Haskell? What is the point?
The point is we should start the 3to5 migration earlier (Haskell is just a codename).
There’s also functools.partial as another option.

  incr = partial(add, 1)
  incr(3)
Both you and the parent comment have mistook partial function application for currying.

Partial function application:

    incr = partial(add, 1)
    # or
    def incr(x):
        return add(x, 1)
    z = incr(4)
(Side note: parent comment made a classic mistake, you shouldn't use a lambda if you're just going to assign it to a local variable, you should use a nested function definition.)

Currying is function that returns a function:

    def curried_add(x):
        def inner(y):
            return add(x, y)
        return inner
    z = curried_add(1)(4)
On top of that I figured the article meant automatic currying.
No, not going to do any of that.
In my defense, I did not say partial is currying, just that it is an alternative to a lambda in Python.

I agree with your points, and with Izkata that the more relevant nuance is not that Python cannot curry, but that it doesn't do currying as fluently as Haskell, which I think was GP's point about the article. The crux of the article seems to have very little support for the argument of the title that lambdas shouldn't be in Python, but instead only seeks to make clear that "we do not use lambda with the intention of using lambda calculus." It's strongest contribution is to suggest that inline anonymous functions would be a helpful addition to the addition for Python to be able to do lambda calculus.

The author makes a bunch of tenuous assumptions; that lambda calculus is all about currying; and that the “lambda” keyword brings lambda calculus to Python; that lambda is undesirable because Python doesn’t have nice currying syntax.

Anyway I think Guido hates lambda because it feels like an appendage in Python, due to list comprehensions and due to expression-only bodies, restriction which was put in place probably because passing multi-line nameless functions as arguments is awkward in a whitespace-sensitive language/it just didn’t feel Pythonic to Guido to be passing large inline functions to functions.

Imo the whole whitespace sensitivity thing was a mistake to begin with. It makes code harder to read, harder to write and harder to parse with only the benefit of being a little easier to understand to absolute beginners (and I'm not even sure about that last one).
And harder to copy-paste!
Technically, it has the benefit that code cannot be incorrectly indented - it can't be formatted to look like the scope is one way when in reality it's another way.
Code can absolutely be incorrectly indented in python and to make matters worse it changes the functionality of the code when it is.
Code can be indented in a way you didn't mean for it to be. But it cannot be incorrectly indented in the sense I just described, because the indentation changes the functionality of the code. The indentation can never mislead you about the structure of the code.
I've seen one case where it has, where the outdent at the end of a nested loop/loop/if was wrong: it was only 3 spaces instead of matching the 4 indent, so it looked right at a glance but wasn't. Needed to use pdb to step through it and see it do the wrong thing before that stuck out.
I am very much not a beginner and I don't find whitespace sensitive languages hard to read. I don't find Lisp hard to read either. Or C-style languages for that matter. The sheer passion people have towards whitespace vs brackets vs parenthesis makes me question their sanity.
Congrats: you are an outlier. That's pretty cool, and it means you can read a lot of code quite easily, which I'm sure lets you be more productive than most, but you are not like most other people.
Are you sure this an outlier opinion amongst regular python users?
Well no, I imagine that that group would somewhat self select for developers who are okay with reading whitespace based syntax. Someone who finds it hard to read is probably not going to become a regular user of the language unless they have to.
Python is lingua franca in data science / ML, i.e. you pretty much do have to to work in the field. In my experience the people who complain about this aspect of python are those just starting out.

Dependency management would be an example of a real Python issue, not a "everyone gets used to it" one IMO.

I use python extensively, and whitespace very much is an actual issue. Not a big one, but it is annoying every once in a while e.g. when copying code. Especially as autoindenting is very much hampered in that case, because the editor can't tell what the indent is supposed to be if multiple options are valid python. Dealing with braces instead is much easier. Not a deal-breaker or major issue, but an annoyance nonetheless.
(comment deleted)
Why would a python programmer have any understanding of code that's full of pointers and memory addresses? You show someone who started on Python, and still uses it today, real product C code and their eyes glaze over real fast.
I think you replied to the wrong person.
I think this is one of those silent majority situations.

Almost all programmers can read a C-like language because well, that covers like 95% of languages by use. You have C/C++/Java/Go/JS/Kotlin/Rust/zig/C# etc.

BUT second to that in sheer use is probably Python. Almost everyone (that I know) has touched it atleast a bit whether its dev scripts, tooling, scientific work (bio, cv, etc.), robotics, mech engineers, ml/ai space etc.

Why would anyone nitpick to death tools that have so much ubiquity? Well they'd have to have a narrow focus to even consider it. The majority of algebra users never second guess +, *, ^, (), etc.

I'd say lisp is probably the hardest to get used to combine with the least frequent to cross your path. And then there's a long tail of bash/ruby styles, tcl, ocaml, etc etc.

I think this is a dangerous assumptions to make: the last time I looked at C was 30 years ago, you show me real C code with pointers and references and namespaces and I couldn't tell you wtf was going on if my life depended on it.

With a few weeks of study to "re"learn C and/or C++? Sure, they're just programming languages. But right off the bat? No idea what the hell is going on in real, production C code.

This is my point though. People just complain about whatever syntax they aren't used to using but none of them are inherently more difficult to read.
None of them are inherently more difficult to _learn_ but they sure as hell are more difficult to read when everything's peppered with :: and * and &.
That's a matter of opinion and one I disagree with.
(comment deleted)
(comment deleted)
The problem of working C code isn't the syntax, it's the semantics.

Casting pointers isn't difficult to read because dereference and multiplication share *, it's difficult to understand because it's not something you do outside of C.

In my experience, people who struggle with different syntax conventions are the outliers; if you can handle the idea of brackets generally (as we all do regularly), then you can deal with one set of conventions.

Similarly, if you can grasp the concept of a paragraph, or of bullet points, or a bunch of other things in normal text that convey meaning through whitespace, then you can deal with another.

It's only code that (some) people complain about this with. In every other context, we all know that we can work with syntax based either on brackets or spacing. Just in normal communication, all humans deal with a dizzying array of complex conventions without even thinking about it.

The reluctance to use whitespace-sensitive languages (or the other way around) is learned helplessness, rather than a widespread inadequacy.

I find it odd that this isn’t a configuration option in any/all language compilers. I prefer indents to braces, but understand the hate others have. Turns out AST engines could convert them relatively easily.
A lot of languages do have auto formatters with opinionated defaults for this.

For go, gofmt is so opinionated that you can't even configure it, which is kinda cool because it means all go code that uses it will look the same.

There's also rustfmt for rust and prettier for JS/TS (and I assume similar for lots of other languages I'm not familiar with), both of which are configurable, which I prefer - but I can see the upsides of a one-size-fits-all code formatting tool (especially for a language like go where a reasonably constrained feature set/syntax is an explicit goal of the language).

There's some evidence that indentation-based block delimiting is intuitive: People indent their blocks even in whitespace-insensitive languages.
Indentation-based delimiting is good for reading because it provides visual structure. For writing, it's much more effective to rely on braces or parentheses instead and have the indentation be inferred by an autoformatter. For example, with braces, I can just copy-paste a statement or block of statements from somewhere else and it will have the correct semantics regardless of what indentation the source and destination end up having, and the autoformatter will immediately fix it for me.
With indentation-only based delimiting, you don't know whether you're seeing the whole thing:

  def check(situation):
    if situation.under_nuclear_attack:
       situation.set_alert_level(RED)
  -------------- bottom of window -----------------
Is that the whole function? Does it forget to launch the counterstrike? Or is there more? We don't know without scrolling down.

You can change the meaning of code, without changing its whitespace sensitive diff (git diff { -w | -b }).

Call python "harder to read" is like call rain "harder to get wet".
> with only the benefit of being a little easier to understand to absolute beginners (and I'm not even sure about that last one).

This one is backwards: the benefit is forcing beginners to write well-formatted code. You would not believe the insane things people just learning can come up with, when in a C-style language where indentation doesn't matter.

> The author makes a bunch of tenuous assumptions; that lambda calculus is all about currying

My first thought exactly. Lisp is based on lambda calculus and it doesn't do automatic currying. Multiline lambdas require block delimiters more sophisticated than whitespace; hence they don't work in Python. It's no more complicated than that.

I think if you really tried you could make it work but there are a lot of ugly and unique tradeoffs to work through. Something like this could work:

  foo((lambda x, y:
         for i in range(x):
           y += i
         return y),
      20
  )
It could work like this:

   foo (lambda x, y::, 20)    # :: means body of lambda follows
     ::                       # optional, if there is only one lambda and line has no argument lines
       for i in range (x):
         y += i
       return y
The lambda bodies immediately follow the line. If the line has arguments, those follow the lambdas:

   if foo(lambda x::, list) < bar(lambda y::, list):
     ::                  # lambda x
       return x + x
     ::                  # lambda y
       return 2 * y
     return list         # then statement of if
The def keyword followed by a colon could be used as the marker for the continuation material that supplies the multi-line lambada bodies.

Perhaps the double colon would be unnecessary: just make the expression optional, and if it's missing then there has to be an "outboard" body.

   if foo(lambda x:, list) < bar(lambda y:, list):
     def:                # lambda x
       return x + x
     def:                # lambda y
       return 2 * y
     return list         # then statement of if
What Lisp doesn't do is partial application: returning a function of N-M arguments when you only apply M arguments to one that needs N.

Languages which do that have issues representing very useful software engineering tools: optional arguments, variadic arguments and keyword arguments.

Currying is related to partial application; it refers to a technique for representing multivariate functions using only functions of one argument.

E.g. to obtain some f(x, y, z) we instead make it f(x)(y)(z), where f is a one-argument function applied to (x). That yields an anonymous one-argument function that is applied to (y), and that again yields a one-argument function applied to (z), which finally calculates f(x, y, z).

Whereas partial application is this: we already have a bona fide three-argument f(x, y, z), and the ability to call it with all three arguments at once. But for some reason, we call it as f(x)(y)(z). Now f needs 3 arguments but is called with just one, (x). So an anonymous two-argument function is produced which, if given two arguments will combine those with the one that was given and call f. Alas, that function is given only one argument, so it returns another one-argument function. When that one is given an argument, it all unwraps, and f ends up being called. These anonymous functions are understood to be produced because our notation is understood to have implicit partial application semantics. Under currying, things aren't implicit; f(x) is written as a one-argument function, which will have a lambda term in it. There is nothing to be implicit about in terms of application: all functions have one argument.

Thanks for the clarification. I'm guilty of saying things like "Haskell does automatic currying" when in fact it does automatic partial application. But then again I don't actually know what goes on under the sheets in Haskell. It might in fact convert every multi-argument function to a nested single-argument curry-like lambda form internally, whether it needs to or not. I expect that would be inefficient (because it would cons up a lot of unnecessary functions), but it's possible. Does Haskell only do partial application when it actually needs to, or does it do single-argument curry-ish partial application of every function regardless?
Btw I’m not an expert but I believe Haskell treats all functions as unary, but they’re translated to N-ary in an optimizing compiler.
That sounds like semantic wordplay. Functionally, how is currying and partial application different? Given two otherwise identical languages which use either currying or partial application, both languages would run every single program exactly the same way.
F# manages it. I think this really holds Python back as a more powerful scripting language.
Er no, if you make it to the end he straight up says lambda isn't lambda calculus, shouldn't be treated as such, and is useful on its own. Basically it should be kept but named something else.
> l_add needs the arguments passed one at a time. We never see this syntax with multiple parenthesis in Python. It works and it has many benefits built in, but it would be a paradigm shift to expect pythonistas to start writing their programs this way, it’s just not Pythonic.

He makes a big point about currying in his particular manner, but what's wrong with

    f = lambda x,y: x+y
    g = lambda y: f(3,y)
That's also currying right? I've always been a little confused as to why the style he advocates is necessarily better. I'm not a functional guy so I'm maybe I'm missing something, what is the benefit of syntactic sugar for partial application to the first (or last) argument of a function? What if I want partial application of a function with the middle argument specified? Now you're out of luck and have to do the above anyway.

ETA: on the main point

> In Python, we do not use lambda with the intention of using lambda calculus. Our syntax needs to encode our intention in the places we have used it, and where we will use it. These use cases are inline, anonymous functions.

That seems needlessly nitpicking. Wikipedia has "lambda function" as a synonym for anonymous function so it seems pretty widespread in our language at this point. https://en.wikipedia.org/wiki/Anonymous_function

Not quite, that is partial application. Actual currying would allow you to do partial application this way, making it less explicit:

  > g = f(3) # no second parameter provided
  g = <unary function> # however it would be indicated in the python shell
To get it in Python you'd need to do this:

  f = lambda x: lambda y: x + y
  g = f(3) # result is a function, or funcallable, or whatever Python calls the resultant type
This makes the currying explicit. In general, currying is taking an n-ary function and reducing it to a series of n unary functions. These three are equivalent, in Haskell(ish, rusty):

  f x y = x + y
  g x = \y -> x + y
  h = \x -> \y -> x + y
In Haskell, you don't have to use the explicit form of `h` (or the explicit form in the Python example) if you don't want to (and it would probably be weird if you used it extensively).
> Actual currying would allow you to do partial application this way, making it less explicit

> To get it in Python you'd need to do this:

> f = lambda x: lambda y: x + y

> This makes the currying explicit. In general, currying is taking an n-ary function and reducing it to a series of n unary functions.

Something's gone wrong; your example of explicit currying conflicts with your definition of currying. `f` is a unary function.

The original `f` was a binary function:

  f = lambda x, y: x + y
My updated curried `f` is a unary function, but it returns a unary function itself:

  f = lambda x: lambda y: x + y
That's what currying is. I just reused the name, if you prefer:

  curried_f = lambda x: lambda y: x + y
EDIT:

And note, it makes the partial application less explicit/verbose than the original, but the currying is very explicit because of Python's syntax.

  g = curried_f(3)
Is "just" a function call, while:

  g = lambda y: f(3, x)
does the same thing (functionally, at least) but the partial application is made explicit.
I'm no FP expert, but I would call that partial application and not currying.
Currying is partial application for free on all function parameters in the order they were declared. It makes partial application no-hassle if you are supplying successive arguments in each application.
I know this is a very late reply, but I'm curious. How often does it align that the functional argument that you want to specify is in the correct position of the argument list that you get to make use of this feature? The times that I need to partially apply are slim, and I don't believe it's consistent that they're on the same side all the time.

The lambda keyword is long which makes it a pain, but currying really is equivalent to specialised partial application right? People seem to love currying so much I feel like I don't understand something here.

For context I work a lot with java, R, and Python which all have anonymous functions and higher order functions, but are all decidedly _not_ "functional languages".

> That's also currying right?

No, currying is the transformation that takes a function f such that f(x0, x1, ... xn) = r and returns a function f' such that f'(x0)(x1)... (xn) = r.

Haskell doesn’t really have currying so much as having only one argument functions and a syntax for defining them that means one way to define a function that works like the result of currying a multiargument function looks a lot like defining a multiargument function in other languages.

From another (probably less helpful but neat!) perspective, currying is turning x^(y*z) into (x^y)^z
Lambda has to be the least intuitive part of Python. even the name of it "Lambda" makes it sound like some deep computer science complex thing, inaccessible to beginners.

In JavaScript I constantly use anonymous functions everywhere and they are easy to understand and use.

The same concept in Python is clunky, hard to understand and definitely not easy to use.

JavaScript absolutely trounces Python when it comes to compact, minimal, highly expressive anonymous functions.

JavaScripts arrow functions are so elegant and so compact and so effective that they completely change the way I program JavaScript. No such equivalent exists in Python.

lambdas are very useful for example for sorting data using a key function. Maybe the auther is saying that it should be called differently, or have a different syntax and the title is just clickbait.
tldr;

'etymology/provenance' is not inline with current use; hence don't use.

rather silly tbh. you'd have to stop using most words in english (let alone entire systems plagiarized from other cultures) if you take such a stance.

as for the debate, actually, one could argue python would benefit greatly by going the other direction: make multi-line definitions of lambda possible (ala all the lisps).

"however there is nothing that lambda can do that can’t be done with the usual def foo(args, kwargs): ... syntax"

Defining a function inline with no name?

No offense to people who are good people and put in great effort, but that alone does not mean one is infallible.

Python is a very poorly designed language, and when I would sometimes dig to find out why things here how they were, Guido was behind many of the decisions which I believe were bad.

Python is a very poorly designed language...from your perspective...for a subset of tasks that you deem to have higher importance than others.

I'm definitely not saying python is anywhere near perfect, and yes it certainly has design flaws (as does any complex system). But obviously it's done a lot of things right to gain the reach that it has.

Being actively updated and maintained when Perl wasn’t helped Python dramatically.
> But obviously it's done a lot of things right to gain the reach that it has.

Indeed it has. It is a very accessible (and even moreso "available") language.

But it has so many odd (and frankly wrong) peculiarities that big projects tend to become more difficult more quickly because of the mess that is most Python code. Some of the footguns are so rediculous that you cannot believe they exist; and even more astounding is the apologist arguments for why they should be the way they are. This one is my favorite: https://stackoverflow.com/questions/1132941/least-astonishme...

Since it is so accessible and has such a rich ecosystem, someone with little skill can cobble together a system which works well enough to get to production. But that junior developer pay rate cost (and ease of hiring) will mean that as the project matures and more and more features are added, the code more rapidly becomes incomprehensible. Everything being mutable, no types (except for bolted on, visually noisy, non-runtime linters), OOP missing some of the key information hiding features, awkward or half-baked functional features, and time/space performance problems all combine to make Python a terrible choice for non-prototype level projects.

> This one is my favorite:

That’s not a confusing language design. It’s a very simple case of mutability that new programmers stumble over because they don’t yet know how to reason about mutability.

But go ahead, tell us what should have been done instead here? Make it impossible to pass mutable arguments to functions? Or make it impossible to use mutable variables as default?

It seems you are misunderstanding something fundamental about this issue. This is not a problem of mutability or immutability of function parameters, but rather that parameter default values are immutable. (But even to your point about making it impossible to pass mutable arguments to functions... that's a very common thing in many languages, even though it is irrelevant to this topic.)

This should illustrate the problem:

-------------------------------------

Python 3.10.6 (main, Aug 11 2022, 13:36:31) [Clang 13.1.6 (clang-1316.0.21.2.5)] on darwin

Type "help", "copyright", "credits" or "license" for more information.

>>> def visit_city(city_name, visited=[]):

... visited.append(city_name)

... return visited

...

>>> a = visit_city("Boston")

>>> a

['Boston']

>>> a = visit_city("New York", a)

>>> a

['Boston', 'New York']

>>> b = visit_city("Dallas")

>>> b

['Boston', 'New York', 'Dallas']

>>>

--------------------------------

Any reasonable developer would expect b to => ['Dallas']. You absolutely would not expect the value of b to have been influenced by previous calls to visit_city().

Python apologists only offer one positive for this: "It is useful for memoization". That is a very edge case use, and it was not the intention when default arguments were implemented. It was an accidental side use of a misfeature.

> but rather that parameter default values are immutable.

No they are not, I’d offer you an example, but you gave one yourself. Are you confused as to what mutable and immutable means?

> You absolutely would not expect the value of b to have been influenced by previous calls to visit_city().

Yes they would. Python is not a functional language. If you have A=[1,2,3] and call A.append(4), what do you expect A to be? Same if you have a function with argument “mylist” and a default set to A, what do you expect to happen to A if you call mylist.append(4) inside that function body on a call that didn’t pass another list?

> Python apologists only offer one positive for this: "It is useful for memoization".

No, that is not the only use, if you couldn’t pass mutable arguments you would cripple the language. And if you couldn’t pass mutable defaults you’d have an odd inconsistent handling of them.

You are flat out wrong, and I cannot see any way to make this clear to you. Perhaps this is a language barrier problem?

> if you couldn’t pass mutable arguments you would cripple the language

This is not about passing mutable arguments. This is about having a default value for a parameter be mutable (unreliable).

Yes the arguments in Python functions are mutable. That's not unexpected. What is unexpected is that the default value assigned to a non-provided argument is not consistent. In my example I show that the parameter "visited" has a default value of [] if no argument is passed in.

Off the top of my head, Ruby, Javascript, and Swift offer the ability to have default parameter values, and they are all predictably immutable (defaults). Python is the odd one here.

I will say no more about this here, because I'm beating a dead horse.

You’re missing the essential thing which isn’t mutability but that languages like Ruby have default expressions instead of default values. The default “value” is a factory. You can have mutable default values in Ruby just fine if you make the default expression return a reference to the same object.

    $a = []

    def func(b=$a)
        b.append 5
    end

    func
    func

    # $a is [5, 5]
They are absolutely not immutable, they just feel immutable. And even then immutable is technically the wrong word — you can obviously mutate params default or not. What you mean is that each invocation gets its own copy of the default but unless you can deep clone arbitrary objects and globals/singletons (i.e. classes) don’t exist this is impossible.

Python has actual default values which while sometimes surprising when those values are mutable it is just as much a valid design choice as expressions and both permit mutation.

Default expressions have a different equally crazy footgun side effects.

    $a = []
    def func(b=$a.append(1))
      b.append 2
    end

    func
    func

    # $a is [1, 2, 1, 2]
Ruby default "values" may indeed be expressions, but the typical use is to provide a value which does not depend on state each time it is provided (and a value which does not share memory with previous or future generations of said value). That quacks like "immutable", or "static".

Yes, you can stuff anything, even a reference, into the right hand side of a default parameter definition. But in 17 years of using Ruby, I've never done this, never needed to, and never seen anyone else do it. Granted I don't read source of libraries or Rails (except occasionally), so maybe _somewhere_ someone does this. I've read a few Ruby books over the years and never seen this illustrated as a thing to do, even in the Ruby Cookbook book.

(added) To be fair, one thing I have seen which is confusing to people new to programming and applies in Ruby, is if you define a constant EMPTY = [] elsewhere, and you use EMPTY as your default value for a parameter, you will get the same behavior as Python had with just []. That's because EMPTY is a constant reference to an array, but the array is mutable. This would be true for any language which is not immutable. Change the reference? No, that's immutable. Change the contents of the thing it references? Yes.

However, [] would appear to any sane person as an immediate new allocation of an empty array, not an unnamed reference to one spot in memory which gets an empty array allocated for it the first time it is encountered.

In fact, to be very literal, in the Python example the default value is only [] (empty array) once - the first time. In my visit_city() example, after the first run it is now ["some city"]. But if you read that code, and even if you are interactively debugging, the code itself of course does not change. The code still tells you, "Hey, here's a default empty array []". So this utterly fails the principle of least surprise. We expect when we read code that something which appears to be a literal value (or an expression not based on a variable) will evaluate to exactly what it appears to be.

At this point we'll just have to disagree on whether the Python behavior is "sometimes surprising" or "supremely stupid". I would simply put it this way: the use cases for Ruby's (and Swift's and Javascript's and probably any other langauge which offers default function params) fits the more common need, and that Python's opposite behavior is a footgun in a land where people very rarely intentionally want to shoot at feet.

Your final Ruby example is obvious in what it will do, and not shocking at all. And you had to go out of your way to allow that behavior. Nobody does this. No beginner would see this, in a guide or a book. No beginner would accidentally decide to do this.

But beginner and experienced developers alike would come to Python and do def foo(arg=[]):.

> What is unexpected is that the default value assigned to a non-provided argument is not consistent. In my example I show that the parameter "visited" has a default value of [] if no argument is passed in.

No you don’t. And calling a mutable value “not consistent” because it’s not immutable is just changing the words used after your mistake of immutable vs mutable. You haven’t done what you think you’ve done because you don’t understand that [] is not “a new empty list every time it is referenced”, but is just a single mutable list. So you’ve provided a default mutable value, which initially is [], but by the nature of being mutable might change in the future. And that is what gets used if there is no value provided. It’s exactly the same behavior as if you define a named variable with an initially empty list, and pass that list to the default. If you want it to be initiated to [] every time no argument is passed, then you need to initiate a new mutable list as the argument on each call. Just like you wouldn’t be able to define a named list once as empty, and expect every call of .append() to give you a new list with only one element. You’ve passed a single list as the default, not a list imitator, or some crazy “spawns new empty lists on references” object.

And yes, you could have default arguments only support immutable values, then you would not be able to pass in a whole range of objects in defaults.

Sorry, but this is truly wild. The only language I’ve ever used that behaves this way is Python and it’s unbelievable to me that this is seen as acceptable behavior.
Funny how two poorly defined languages, JavaScript and Python, seem to be dominating the world lately. It's almost as if people disagree with your assertion.
Bringing up Javascript really doesn't help your argument as it is pretty universally deemed a "poorly designed language". Many, many people have written about this. Somewhat randomly selected is this: https://medium.com/javascript-non-grata/javascript-is-a-dysf... (and it references more).

Later ECMAScript editions have made good improvements, but there are still plenty of sharp edges and footguns in Javascript which simply do not need to exist.

Javascript is a leading language because _practically_ speaking (not strictly speaking) it is the only language you can use if you want to make things happen in the browser. And it came with every major web browser since the late 90s. THAT is why it gained immense marketshare.

Python has been included in Linux, *BSD, and macOS for years. It's a very simple language to make things happen with little effort; it is "accessible" to most people, particularly non computer scientists. It reads a little bit more like English than other languages, and it doesn't require compiling (again to accessibility).

All these things don't mean it's good. But it got picked up by non-developers and used to solve real world problems and a lot of research problems. More and more specialized modules were written for it, making it even easier to solve many problems.

So its accessibility and ecosystem have been good. But that doesn't mean it's good for building big, long lived, easily maintainable or extensible systems. It certainly doesn't mean it's good for building performant systems.

See elsewhere in this thread for my link to my biggest complaints (from an old discussion). I'm not a recognized expert on language design, but I've written significant quantities of production code in several languages, including Python, over many years.

No doubt people may disagree with my assertion, but using popularity as evidence of disagreement is not valid since popularity often does not correlate to the quality or suitability of something. History is full of companies, technologies, and practices where the most popular was absolutely not the best.

PHP was one of the most popular languages of the 2000s and was infamously badly designed.
And the key to all three of these was the ease of getting started: Javascript in every web browser, python looks like English without many symbols, and PHP was often already set up for you on hosted webservers.
> Python is a very poorly designed language,

If you're going to throw around your words so sloppily like this at least give us a few decisions you don't like so we have something real to agree or disagree about, lol

I've covered this in detail before, so I didn't feel like rehashing it. Also, while in fact I did have a run-on sentence (because I failed to use a semicolon in one place where I used a comma), I don't believe it's reasonable to say my words were sloppy.

See this thread if you want more than a few examples of how Python is poorly designed (as compared to Ruby): https://news.ycombinator.com/item?id=32116957

I don't think it's really a controversial opinion though (except among Python fans, of which there are many due to how popular a teaching language it is).

IMO the biggest issue is that they add features with absolutely no regard to how you could implement it with good performance. There are also a ton of features that let you do things that are just a really bad idea. Look at something like __subclasses__. Encourages terrible coding practices! (I learnt about it by reading some terrible code my colleagues wrote.) And good luck JITing that.

There's also a ton of syntax that is weird and asymmetric, like list comprehensions. Also the fact that you don't need to explicitly create variables is very error prone. Some of that is excusable given how old Python is but that doesn't mean it isn't bad.

Oh and import resolution. That's just downright terrible. No excuse there. I'd almost take #include over the insane mishmash of different ways imports work. It's even worse than JavaScript's module disaster.

Yeah, the rationale behind many bizarre decisions always seemed to be “because this is the obvious right way”. Or “because Python favors clarity over magic”. And I’m just “how does that explain why len() is a root namespace method that takes a string arg, while isupper() is a method on string objects?”
isupper makes sense only for strings, thus it makes sense not to put it in the global namespace. len is available for strings, but also lists, dicts and many other things. It's so useful that a 3-letter name in the global namespace is justified. Is there a reason for not making it a method though? Yes, actually. By using the dunder system, it makes len special. And it avoids a situation where some objects use a.len() others use b.length() and yet others use c.size() or d.len, e.length, f.size. Python has officially blessed the len name over the others, and that's useful.
That defense doesn't really hold up.

len() makes no sense for numbers, booleans, None, and probably other types I cannot think of at the moment.

Python has a LONG list of built in functions, most of which are only applicable to a few types of parameters. They could just as well be object methods.

https://docs.python.org/3/library/functions.html

Some of those methods, like sorted(), are terrible names. A past tense verb name... so instead of telling Python, "Hey, please sort() this", you say "Please give me a sorted() thing".

So you have the collection.sort() method or the built in sorted() method. They both sort, but they have different options; and significantly, sort() mutates the collection, whereas sorted() returns a new collection.

This is inconsistent and creates unnecessary cognitive load. It's another example of a Frankenstein language.

This isn't magic at all.

There is a method on all objects which are a collection:

    __len__()
You can try it out yourself.

    x = [1,2,3,4]
    print(x.__len__())  #4

    y = "abcdefg"
    print(y.__len__())  #7

    z = { "a": "b", "c": "d"}
    print(z.__len__())  #2

len() is just syntactic sugar for calling __len__(). The "dunder" methods in python are a standard way of extending classes.
Ahh dunders, and the lack of proper privacy mechanisms...

It is generally considered bad practice to call dunders, with the idea being that those are more private or more internal methods which you should not directly depend on (unless you're willing to accept more breaking changes). Instead, you depend on other more intentionally public methods which themselves may call dunders or do other things.

I think the issue the parent was really focusing on was the fact that many methods are instance methods, but there are some global functions.

Python suffers from being sort of OO, sort of procedural, and even less but also sort of functional.

It's a bit like a flying camper van. It's a weird plane with tricky controls, poor fuel economy, cramping sleeping areas, and a bump ride on roads. But boy does it have a lot of mods and attachments, so you can do just about anything with it using only a pair of plyers and a flathead screwdriver. Thus, it attracts beginner mechanics and enthusiasts who build monstrosities that experienced mechanics have nightmares about but which pointy haired bosses don't understand and think the feature set is great.

Humans might care whether or not you called __len__() vs len(), but that's their problem. Guido and the python community didn't enforce this in python, because I think they liked freedom over opinion of what should be done.

It seems like your inability to understand why a lot of people like python seems more like a professional inability (liability?) to adapt and change with the flow.

behind closed doors managers call that inability a career limiting move
len() is not the same as __len__. The built in len() function does type checking and produces a useful exception/message if you provide an argument for which len() is inappropriate. Instead, if you do someobj.__len__ and that object does not implement __len__, then you'll get an exception complaining that he object has no __len__.

Likewise, for example, abs() and __abs__() raise different exceptions if used on something for which abs() makes no sense. Like len(), the built in failure exceptions are somewhat more useful than the dunder exceptions.

But even the type related exceptions of the built in functions are inconsistent -- unnecessarily so.

abs("hello") => TypeError: bad operand type for abs(): 'str'

len(5) => TypeError: object of type 'int' has no len()

At least the exception types are uniform: TypeError. But the exception messages should be identical rather than two very different statements which say the same thing: function f is not appropriate for argument type t.

You may call all this inconsistency freedom, but it's just lack of structure and general sloppiness. Or to be kinder, it's just more organic. Inconsistency and organic evolution are not hallmarks of good tools for building reliable systems.

Finally, I do not lack the inability to understand why a lot of people like Python. I just posit that most people who like Python only like it because they don't (yet) know better. http://www.paulgraham.com/avg.html . Python is Blub in this essay.

If you don't know better, Python is a marvelous language. Although I would argue that some of the oddities and awkwardness of Python misshape peoples' thought processes in a way that can be difficult to unlearn (and which I believe even make these conversations like we are having in this topic extra challenging).

People choose tech based upon the business needs. I don't like Java, still don't. But I don't tell people they were making a huge mistake and they needed to be "schooled" into using C++, e.g. There's usually some business reason to use a particular technology -- one that you may not like.

Back to python. There is no type checking in len() or abs(). The best argument I can make is through code. You, yourself, can make your own len() method. This will behave exactly like len(). Let's call it len2():

    def len2(obj):

        try:
            return obj.__len__()
        except AttributeError:
            raise TypeError(f"object of type '{type(obj)}' has no len()")
There's no type checking, because there's no need. All that object needs is for __len__() to exist. Further we can add abs() and len() to an object like this:

        class X:
           def __len__(self):
               return 2

           def __abs__(self):
               return 1

        assert len(X()) == 2
        assert len2(X()) == 2
        assert abs(X()) == 1
Fortunately you can fix the issues with error uniformity by creating your own functions if you choose the method above. I won't judge you -- because there might be a reason you may want that.

> I just posit that most people who like Python only like it because they don't (yet) know better.

I don't know about that. I came from a background of C/C++/Ada/Java/68k Assembly/Perl/Shell Script/Awk/etc. I switched to python about a decade ago. The python community has some of the smartest people I've ever met. To me the smart people seem to have gravitated towards python.

I've been working on a programming language / interpreter, and I'd guess that in the beginning it was probably just easier to implement len() and other such common / generic functions this way.

You can either implement a single function that handles the various types or you can add a method to each type, but the latter can be pretty tedious when you're implementing a bunch of functions / methods that operate on numerous types.

I ran into this when I started implementing map() for a few builtin types and found myself duplicating the loop-over-items code and started thinking, "Hmm, I wonder if this is why Python implemented len(), map(), etc as functions instead of methods."

Of course, you could implement an abstract class or equivalent for the generic aspects of len(), map(), etc, but then that gets pretty complex too.

So, based on that, which to reiterate is just a guess, I don't think the decision is bizarre at all. It's just an implementation tradeoff.

From a developer point of view, I slightly prefer method syntax for aesthetic reasons, but practically speaking it doesn't make any difference whether I type obj.len() or len(obj), and IMO neither is more or less object-oriented than the other (which is a common complaint about len() et al).

I use lambda and closures (in Lisp), or awful approximation hacks of it (as one-element arrays of anonymous subclasses in Java) ALL THE TIME in graphical interfaces and many other contexts. Not once have I ever used lambdas for currying. In fact as a rule I yank out code I find using lambdas for that purpose as inevitably it can be done more readably with an iterator or for-loop.

But I agree that there should be no lambdas in Python-- but just Python -- because Python's enforced indendentation syntax rules are so hostile to those multi-statement lambdas and closures which are absolutely trivial in a language like Lisp. The hobbled lambdas that remain are, well, basically gunk for currying.

> as one-element arrays of anonymous subclasses in Java.

When's the last time you used Java? I remember having to use anonymous classes but we've had lambdas for a good 8 years now? Or is it that you don't like how lambdas "fullfil" a SAM interface instead of having a true Function representation.

If it's the latter, I'm sincerely very curious what we're missing out on since I'm mostly used to it.

Just last night I put out the latest versions of two open source tools totaling about 500K lines in Java. Sure I enforce an old style but I think I'm up to date with the latest in Java.

It's funny how Java developers perceive "lambdas" as (param, param) -> { code } blocks to be used in consumers and iterators etc. This is a weird mutation of the general concept due to Java's lack of functions: as a Lisp developer I don't perceive lambda expressions like that at all. The use of lambda expressions is much more general than this: it's simply the creation of function pointers for many tasks. For me, nearly 100% of the time it's to create them to be stashed away as callbacks. And in Java, the only realistic way to do this still is with anonymous classes.

Not trying to be defensive or anything: do you have any examples? I'd love to take a peek at what patterns and designs Java's lambdas don't work well for.
I can't build one right now, but one big item for me is true closures: Java lambdas are only closed over values, not over variables. Let's say you want to make a ActionListenerPair builder which sets up a writable local variable to be shared by two lambdas, all of which are ActionListeners and will be used as callbacks when you press buttons. This is extremely awkward in Java because lambdas cannot (or could not) write to variables in outer lexical scope.

But there's a workaround. You can't (or shouldn't) have a non-final int x = 4 in the outer lexical scope, but you can have a final int[1] x = { 4 }, and have all the lambdas access that instead. What an ugly hack, thanks to Java's poor consideration of how closures work.

At this stage, the syntax for closures is little more than a trick to reduce typing rather than an advantage over just explicitly declaring the anonymous class, and its clarity is poor, lost in the twisty syntax. Tricks make code hard to read: C is full of them.

Y'know that's really funny.I often refer to the way lambdas only capture final variables as an example of the absolute brilliance of Java's language design. Rather than a poor consideration I think it's a hallmark of how the obvious choice would be the worse choice in language design.

It a) preserves a principled confinement that the local scope provides, and b) removes all ambiguity around variables across lambdas, assignments inside or outside a closure before or after a stack frame collapses.

> Y'know that's really funny.I often refer to the way lambdas only capture final variables as an example of the absolute brilliance of Java's language design.

This was done not for any principled reason at all: but rather because real closures would require heap allocation, and early users were spooked that heap allocation would be too slow. Otherwise Guy Steele (of Scheme and CL fame) would have definitely inserted them. He supposed that one day Java would fix this.

https://web.archive.org/web/20090309081331/http://article.gm...

> This was done not for any principled reason at all

In light of all my praise for it that's devastating to hear, haha. I still think it was a brilliant move but now I can't fawn over it.

Edit: on reading this I don't get it what they're taking about, we do get a heap allocation per lambda, identically to a record with the captured variables.

Thanks for filling me in on dinner of the history of all of this.

I think the author is right that the meaning of `lambda` in the Python is really `anonymous def` and could be more succinctly and simply explained without any reference to lambdas at all.

The Lambda Calculus really is formally about single argument functions. Here is one of many good references for that: https://plato.stanford.edu/entries/lambda-calculus/

Hence, the multi-argument lambdas of Python are something of a contradiction in terms, or a kind of artistic license. Now that I think about it, I can't think of a good reason not to use `def` for the anonymous function syntax in Python:

    def (x, y, z...): <expr>
>The Lambda Calculus really is formally about single argument functions.

It needn't be: https://personal.utdallas.edu/~gupta/courses/apl/lambda.pdf

Currying is just an easy way to deal with multi-argument functions. You then need to add a bunch of syntactic sugar to avoid the issues the the author complains about here (something that Haskell has done). You could just as easily go the opposite route and add a bunch of different syntactic sugar and deal with multi-variable functions directly (as python does).

Lambdas are fine as is. They'd be better if there was an optional brace syntax to disambiguate scope (optional!) so you could do full functions. Never understood why that's a sticking point in the python community, you'd still have 99% whitespace based scoping, but with braces or begin/end if you need it. Feels like one of those decisions they just dug their heels in on as a matter of ideology rather than practicality.

Also though, I kind of feel like currying/partial application is one of the most overhyped feature of all time. With lexical scoping and closures, the number of times I've needed it is: never. Admittedly I'm not using lisp or haskell every day, but I feel like most of the greatest features of those languages have been generally stolen where possible, and yet currying always seems like an afterthought.

If it were available it would be part of how libraries are designed and you would use it all the time.
I mean, it essentially is available though, "from functools import partial". I just have never seen any examples other than the perfunctory useless math ones:

    mult_10 = partial(mult, 10)
    mult_10_20 = partial(mult_10, 20)
    print(mult_10_20(30))
The only context I can think of off the top of my head where currying might be useful is binding a "this" or "self" object, but, if I'm using a class I already get that. Maybe you could use it to pass through some set of context objects, but then, you could already do that by just creating a data structure to hold those objects.
Even if Python used the anonymous function syntax proposed, people would still call them "lambdas". If you want to have aa keyword for this, you might as well name it after what most people are going to type into Google when searching for it.
Not necessarily. In Perl we called lambda "anonymous sub" for ages, and nobody mixed those up. We also use a handy keyword less syntax to generate, store and use such lambda's. Thus I'm on the "Worse is Better" side, python cannot write proper compilers and cannot decide on proper syntax.

lambda: sub($x) {$x+1} decl sub{shift+1}(args) call

or ruby-like: {shift+1}->list