Most active commenters
  • KirinDave(13)
  • joshuamorton(7)

←back to thread

317 points est | 34 comments | | HN request time: 0.608s | source | bottom
1. KirinDave ◴[] No.17453884[source]
I hope I'm not the only person who reads this and really, really dislikes this.

If Python's gonna have breaking syntax, why not work on bringing it more in line with other modern languages that don't require special breakout syntax for expressions and rely more on functional features?

Are we still maintaining that lambdas are hard but suggesting expression-scoped variables are easy?

replies(3): >>17454262 #>>17454393 #>>17454442 #
2. viraptor ◴[] No.17454262[source]
The difference here is that this pattern of get-value-check-it is in pretty much every program longer than a few lines. And possibly in every one using regexes. Missing lambda opportunities are not nearly as easy to point out.
replies(2): >>17455067 #>>17456924 #
3. _diyu ◴[] No.17454393[source]
Speaking as someone whose first dynamic language was python, it’s full of weird inconsistencies and I have no idea why anyone voluntarily chooses it when teaching programming. Granted all languages have warts, but people say python is easier to learn programming concepts in than other languages and I just don’t see it. I think they just mean the indentation syntax discourages confusingly indented code?
replies(1): >>17454444 #
4. kevin_thibedeau ◴[] No.17454442[source]
> Are we still maintaining that lambdas are hard but suggesting expression-scoped variables are easy?

We maintain that list comprehensions do all the things lambdas can without the clunky anonymous functions. All while clearly communicating that a sequence is being transformed without any obscure syntax.

replies(2): >>17454933 #>>17455045 #
5. icebraining ◴[] No.17454444[source]
What would you choose?
replies(3): >>17454474 #>>17454500 #>>17455322 #
6. amorousf00p ◴[] No.17454474{3}[source]
C of course.
replies(1): >>17455039 #
7. _diyu ◴[] No.17454500{3}[source]
For beginners? Whatever’s most popular on the job market. Ideally something with C syntax. Most languages are similar enough and their quirks can be avoided. Lisps are definitely a bad idea for this since the syntax is less intuitive to most people. ML languages like Haskell are not for everyone either, they require mathematically inclined minds. So maybe Java or JavaScript?
replies(4): >>17456413 #>>17456786 #>>17456908 #>>17521071 #
8. nerdwaller ◴[] No.17454933[source]
List comprehensions are more a substitute for map than anything else (which is generally discouraged in the ecosystem). For comparison: `map(lambda x: x * 2, range(10))` vs `[x * 2 for x in range(10)]`.

However lambdas are mostly discouraged because they're often harder to read for anything non-trivial (and really you can't do much non-trivial with them since they are so dwarfed) and you can just define a function in the same block and pass the function reference in place of a lambda. I think the premise is more that it's clearer to pass a clearly named method describing the intent instead of a lambda describing the how.

replies(1): >>17455063 #
9. KirinDave ◴[] No.17455039{4}[source]
Why do you advocate such violence?
10. KirinDave ◴[] No.17455045[source]
You do so by labeling anonymous functions "clunky" and continuing to pretend that the only use of lambdas is working with collections.

Or, as I like to call it, "The same lie they've been telling for a decade, that everyone knows is false because lots of people learn JavaScript and do not find it excessively complicated."

replies(1): >>17460929 #
11. KirinDave ◴[] No.17455063{3}[source]
This is an assertion by a Supreme Dictator of the ecosystem. It's not actually backed by any metrics. It not indicated by any stuides. It's not borne out in any data brought to you by teachers. It's actually contradictory to previous findings.

We can also see a migration away from list and object comprehensions in languages that support both them and lambdas (e.g., C#, Haskell).

How the Python community has maintained it's opinion that everyone else is stupid and wrong while also suggesting that their tooling is impossible to understand is beyond me. It seems like a contradictory position.

replies(2): >>17457089 #>>17457095 #
12. KirinDave ◴[] No.17455067[source]
Since when has brevity been a goal for Python tho?
13. void_starer ◴[] No.17455322{3}[source]
Scheme
replies(1): >>17518294 #
14. kalesho ◴[] No.17456413{4}[source]
Do you really believe JS is less wart-y than Python?
replies(1): >>17457482 #
15. cup-of-tea ◴[] No.17456786{4}[source]
When I read your first post I thought you were going to suggest something sensible like using a language geared toward paedagogy instead of whatever language du jour, but what you've written here is absurd. C does not have intuitive syntax. Many places already do teach Java as a first language and it's a disastter for many reasons. Lisp has very intuitive syntax. The only people who don't find it intuitive are those who have struggled with C-like syntax for long enough they can't see anything else.

The only reason Python is used is because it's popular so it seems to fit your criteria outlined in this comment just fine.

16. ehsankia ◴[] No.17456908{4}[source]
Depending on the market, python is one of the most popular languages. JS is probably on top also alongside C but neither of those are better starters.
17. ehsankia ◴[] No.17456924[source]
Making your whole language and code more complicated just to save one line of code? No thanks. Python to me has always been about clarity and simplicity. If i wanted to write cipher unreadable code, I'd just use C++
18. nerdwaller ◴[] No.17457089{4}[source]
That's true, though being pretty involved in the ecosystem (e.g. anecdote) I believe that people tend to agree with the general concepts (with exceptions).

> it's opinion that everyone else is stupid and wrong

I can't speak for the BDFL, but I don't believe this is the intent. It's not meant to say everyone else is wrong and Python is right, rather that Python prefers fewer ways to do things and certain complexities aren't worth it when there's a good way already in the language to do $feature. Complexity may not be in the python layer even, it could be maintenance in the underlying implementation (CPython, PyPy, Jython, ...)

19. dorfsmay ◴[] No.17457095{4}[source]
In Python without type annotation, what do you find about a lambda that is easier than a named function?
replies(1): >>17457518 #
20. KirinDave ◴[] No.17457482{5}[source]
Do you really believe Python is less warty than JavaScript?

A lot of the same numeric tower decision exist. There's a big schism between List Comprehensions and not, there's no simple way to ship closures around, and Python's concurrent I/O story is a sizzling hot mess.

JavaScript has plenty of problems. So does Python. So why does Python maintain everyone else is stupid for embracing programming constructs that have been deployed successfully since the 60's?

21. KirinDave ◴[] No.17457518{5}[source]
The ability to move closures and to rely on simple lambda applications rather than ad hoc hierarchies or more complex annotations that map over the same decorator patterns anyways.

That's the big irony. Python embraces some of the most complex, detail oriented aspects of function-first programming with decorators, but then throws away most of the payoff. It's like eating the peel of a banana and refusing to eat the interior.

22. joshuamorton ◴[] No.17460929{3}[source]
Except that the entire job ecosystem has been moving away from a lot of the ways they used anonymous functions (callbacks replaced by promises, replaced by async/await).

It's pretty rare that I write multi expression anonymous functions in js, and the language appears to be doing everything in it's power to make them wholly unnecessary.

replies(1): >>17463987 #
23. KirinDave ◴[] No.17463987{4}[source]
> Except that the entire job ecosystem has been moving away from a lot of the ways they used anonymous functions (callbacks replaced by promises, replaced by async/await).

This isn't true at all. Async/await is just a nice way to think about chaining callbacks. They're still callbacks. You can await raw promises and lambdas. Callbacks are still used when they ought to be used, which is in situations where there isn't an "end" to the deferred computation.

> It's pretty rare that I write multi expression anonymous functions in js, and the language appears to be doing everything in it's power to make them wholly unnecessary.

I don't agree with that assessment and I think a lot of Javascript programmers would too. You still see multi-expression lambdas used a lot, and as I've said elsewhere async/await is just a way of monadically handling lambdas with the appropriate call signature.

Heck, the entire point of the PEP we're discussing is to try to make it easier to write what'd trivially be recognizable multi-expression lambdas into things like list comprehensions or for decls. What a victory! Now you have to read the entire list comprehension at least twice!

replies(1): >>17464776 #
24. joshuamorton ◴[] No.17464776{5}[source]
I think that reducing async/await to just sugar for callbacks is reductive. They provide improved error handling and easier extension. Sure they don't do any more than callbacks, but they're not callbacks. They're an alternative to callbacks that allows you to better structure your code. And importantly, they tip the before towards named functions.

As for this pep, I don't really like it, I think it adds dubious value and overly terse syntax. But that has nothing to do with the general idea that multi expression lambdas are unnecessary.

replies(1): >>17465127 #
25. KirinDave ◴[] No.17465127{6}[source]
I think reducing lambdas to callbacks is reductive. They provide a lot more utility and often have returned values (which callbacks do not).

The fact that the python community is proposing and approving PEPs like this is evidence they have a desire for multi expression lambda functionality. They've just made a decision they refused to go back on because of over a decade of pride. They end up rebuilding every use case as an ad hoc piece of syntax.

replies(1): >>17465284 #
26. joshuamorton ◴[] No.17465284{7}[source]
>callbacks cannot

This is wrong. Please fully inform yourself before making incorrect claims that I'm being reductive.

Some callbacks may not return a value, but callbacks in general absolutely can and do. (A callback simply need conform to an interface, that interface can be whatever the enclosing code decides it should be).

Either that, or async/await is more powerful than callbacks, because most of the `async` functions I've seen have a return value (coroutines can and do yield results, and async/await is a way of implementing coroutines).

I also don't really think that this solves the same issue as multiline lambdas in all cases. Sure, one of the examples (the case of a double function invocation in a comprehension) can be solved with a multiline lambda (among other ways). But multiline lambdas don't particularly clean up `if (x := re.match(...)): process(x)`. Which I'd argue is the more motivating example.

So sure, this doesn't solve your problem with the language in the best way possible, but it was never really intended to, it just sort of can as a side effect. (I don't really think this is a good thing, mind you, but it is a thing).

replies(1): >>17465410 #
27. KirinDave ◴[] No.17465410{8}[source]
> This is wrong.

Async "callbacks" in Javascript cannot have semantically meaningful return values, because a callback context cannot read the values. EVERY lambda has a return value in Javascript, but this is more an implementation detail of dynamic typing than a statement of intent. They don't have callers that accept values of any type, so they're effectively a function of (Context -> IO ()). They're executed strictly for side effects.

If you have a meaningful return value, it's not properly called a callback. You can google about this because obviously the "spec" is "how it is used in literature and the common vernacular." As an example, the first SO hit on Google I say has top rated answer saying " An async callback function can return a value, in other words, but the code that calls the function won't pay attention to the return value." https://stackoverflow.com/questions/6847697/how-to-return-va...). You can surely find more, this is what distinguishes constructions like "callbacks" from "hooks" (which often return booleans, but may modify internal state passed to them or execute side effects as a callback) and further from "Promises" (which compute a value once and allow useful synchronization on the completion of that computation).

> Either that, or async/await is more powerful than callbacks, because most of the `async` functions I've seen have a return value (coroutines can and do yield results, and async/await is a way of implementing coroutines).

Well of course they are. I don't think I said they were equal in every respect, did I? I was a bit overly specific in talking about how Promises are often just used as callbacks, so sorry if I was unclear.

Async-await is a continuation monad. That's strictly a more powerful construction than a side-effect-only callback.

> I also don't really think that this solves the same issue as multiline lambdas in all cases.

I agree. It solves one very small slice: not having multi-line lambdas for filters and maps, nor a compiler that can fuse them.

> But multiline lambdas don't particularly clean up `if (x := re.match(...)): process(x)`. Which I'd argue is the more motivating example.

This PEP is even more exquisitely awful if that is truly the dominate use case. "We saved a line for the sake of saving a line" is a brutally frivolous reason to introduce that much new machinery.

replies(1): >>17465495 #
28. joshuamorton ◴[] No.17465495{9}[source]
>Async "callbacks" in Javascript do not have return semantically meaningful values.

Async callbacks in javascript cannot return semantically meaningful values to sync code. But the classic "callback hell" is when you have async code calling other async code. In that case, a callback can absolutely return a meaningful value to the other async callback. And yes, that is a common and valid use.

>This PEP is even more exquisitely awful if that is truly the dominate use case.

I don't particularly disagree with you here.

replies(1): >>17468164 #
29. KirinDave ◴[] No.17468164{10}[source]
> But the classic "callback hell" is when you have async code calling other async code. In that case, a callback can absolutely return a meaningful value to the other async callback.

But then it ceases to be a callback? It's a one-shot function called within the context of a callback. A use case promises are particularly good at enabling.

replies(1): >>17468197 #
30. joshuamorton ◴[] No.17468197{11}[source]
>But then it ceases to be a callback?

No, it is a synchronous callback called in an async context. I think the confusion here is that you think that "callback" and "asynchronous callback" are synonyms when they are not[1]. A callback is simply any function passed into another function to be executed at a later time. An async callback is such a function which will be called asynchronously.

In JS, asynchronous callbacks are not allowed to return values (or well they can return values they just cannot be meaningfully accessed). Synchronous callbacks, however, certainly are.

Yes indeed, promises are incredibly good at un-nesting said synchronous-inside-of-asynchronous code.

[1]: https://en.wikipedia.org/wiki/Callback_(computer_programming...

replies(1): >>17468513 #
31. KirinDave ◴[] No.17468513{12}[source]
Can you show me an example of a "synchronous callback" in JavaScript code that is labeled as such in open source code?

Because generally what I call those is "higher order functions." For example, by the definition you've offered Array.map takes a "synchronous callback" and that seems quite wrong.

replies(1): >>17468639 #
32. joshuamorton ◴[] No.17468639{13}[source]
Do the MDN docs count?

> The then() method returns a Promise. It takes up to two arguments: callback functions for the success and failure cases of the Promise.

Both of these functions may be synchronous, and may return values. Thus

    Promise.resolve(2)
           .then(x => x + 1)
           .then(x => x + 1)
           .then(console.log);
Will print 4, not undefined. The documentation also refers to these as "handler" functions, but uses "callback" interchangeably, although less often.

Generally speaking, "callback" is only used in the context of the function being executed in an async context, but again there's nothing stopping a sync code from being executed in an async context, and indeed this is pretty common (anytime one invokes a promise, as a simple example).

[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

replies(1): >>17469112 #
33. KirinDave ◴[] No.17469112{14}[source]
> Both of these functions may be synchronous, and may return values. Thus

That promise chain is not synchronous. You can prove this by putting your code sample and a following console.log("meep") into a function. The console.log("meep") will always fire before your promise's final handler runs, when you call the function. That's because it's scheduled after your calling function returns.

Try it.

> Will print 4, not undefined.

That's indeed true, but it's a special property of promises that you can extend the construction of a callback with `then` calls. You can never return that value to the calling context. Creating the illusion that you can is what async/await is for. The promise context extended the computation to the final callback, which executed the side effect.

It would be a much neater implementation if the underlying resolve/reject functions that construct the promise were synchronously calling the functions supplied by then. That'd be awesome. Sadly, that's not possible with this implementation, because you can then-extend a resolved promise. But I confess I like how algebraic this implementation is.

It's not wrong to call a then-extension a "callback" if it's the final one in the chain. It's computed value is forever discarded, as ultimately is the value of the entire promise. All promises terminate with a callback who's value is discarded.

As I said, Promises are special and expose a continuation monad, in that they're a way to construct callbacks piece-wise. It's a very useful piece of functionality for JavaScript to have and enables the underlying syntactic transformations that make async/await work.

replies(1): >>17480071 #
34. joshuamorton ◴[] No.17480071{15}[source]
>That promise chain is not synchronous.

The promise chain is executed synchronously in an asynchronous context. You seem to misunderstand this nuance. `x => x + 1` is a synchronous function. It will always execute synchronously and will return a result when it is invoked and block (within its context) until that invocation is complete. However, its calling context may be paused/pre-empted.

Importantly though, the promise won't be pre-empted during the evaluation of a synchronous function:

    function sleep(ms) {
        var start = new Date().getTime(), expire = start + ms;
        while (new Date().getTime() < expire) { }
        return;
    }

    Promise.resolve(0).then(
        (x) => {
            for (i = 0; i < 15; i++) {
                sleep(1000);
                console.log(`doing stuff ${i}`);
            }
        }).then(console.log); 
If you run this in your console and then try to do anything, you will be unable to. You'll be blocked by the asynchronous code (because it never releases). Replace my blocking sleep with setTimeout, a nonblocking alternative, and you'll find that things work normally. You're executing synchronous, blocking code in an asynchronous context.

Promises aren't doing anything magical, they're simply syntactic sugar for flattening otherwise nested chains of this (where +1 is a standin for the action that this specific function is taking):

    f = (cb, v) => cb(v + 1)
which quickly balloons to

    f = (cb, v) => cb(v + 1)
    (v) => (f(console.log, v))
    f((v) => (f(console.log, v)), 1)
    f((v) => (f((v) => (f(console.log, v)), v)), 1)
    
back to something sane:

    Promise.resolve(1)
           .then(f)
           .then(f)
           .then(f)
           .then(console.log)
There's really no major difference between those two constructs (well, promises also provide infrastructure for releasing to another control.

All of those are synchronous functions, executed synchronously, in an asynchronous context, and all of them are called callbacks by what is perhaps the most authoritative source on web programming today.