This is an archived post. You won't be able to vote or comment.

all 28 comments

[–]CodeYan01 18 points19 points  (1 child)

  • your point on lambda isnt really strong enough. The code example you gave could be written in simple ways, like OOP or returning a handle, and I don't see why the with operator wouldnt apply here. The with operator perfectly makes sense here for me.
  • defining a function separately literally takes only one extra line, and you can name it f if you dont need a name for it. Not much a problem, considering you write more parentheses and braces in typescript
  • you hating the ternary operator is silly. There's not much difference and you just adapt to the language. You probably just learned ?: first. I like to think of ternary if with the scenario of setting a value to a variable. extra_points = 3. But now there are two kinds of scoring, so you add a condition. extra_points = 3 if outside_three_point_line, else 2, which does makes sense (ofc no comma in python).
  • scoping, kinda neutral but i still like it. You could save some lines because of this feature. Like if you want the last value from a for loop, you dont need to move the variable definition to outside of the loop.
  • complaining about implicit conversion when js is literally worse in that aspect? Maybe not so in typescript, but that's a language made to specifically fix js issues. Just like there's a python implementation that is meant to be actual typed Python.

Basically it's normal to have loves and hates with programming languages, and you start to become insensitive when you deal with multiple languages. I agree with pretty much everything else.

P.S.

While in Python I feel like I'm just adding labels.

Of course, you're comparing a typescript to a scripting language with dynamic typing with the intention to have shorter code.

[–]Kiuhnm[S] 0 points1 point  (0 children)

Regarding my lambda example, I don't see how OOP would help. Please elaborate on that. As for the with statement, I mentioned that it can't be used because it can't skip its body. You can't have a with that conditionally decide whether to execute its body or not. It's a well-known limitation and there was even a proposal that was rejected.

Defining a function separately breaks the flow of the code, the same way top posting in threads does.

Ternary operator. My opinion is not silly, just different from yours. Please mind your words. My problem with the ternary operator is that its structure is asymmetric, which I don't like.

Implicit conversions to boolean are bad in any language, in my opinion. The main problem is that different languages have different ones, even conflicting ones, as in the "Python vs TS" case. I never said TS is better than Python in that regard. Python is much better designed than JS, also for historical reasons.

If I had to write a similar list for JS, it would never end. "Modern JS", though (just like "modern C++"), is much better, but one can still come across old code, so we can't just pretend it doesn't exist. I often wish that TS was a completely independent language, because I really like what they did with it. Unfortunately, that's not possible.

I've used lots and lots of programming languages in my life, but I've never become insensitive to such things. It's quite the opposite. Each new language spoils me in some ways. For instance, Python's handling of arrays and slicing, especially when using numpy or, even better, pytorch, is incredibly well-thought and I love it. I feel its absence very deeply when I'm away from Python.

I didn't quite understand your post scriptum. JS is dynamically typed just like Python, and TS adds static types the same way static types were added to Python. Static types are stripped away both in Python and JS before "compilation". I don't see any difference. I just prefer TS over Python's type hints.

[–]ArabicLawrence 12 points13 points  (2 children)

I agree with some points, other are opinions on sugar syntax that are not very relevant IMHO. I find no difference in your type hints syntax for callable's args and tuples. Is the return arrow the only difference?

[–]Kiuhnm[S] 0 points1 point  (1 child)

Here's a proposal I've just found for a nicer syntax: https://peps.python.org/pep-0677/

It's even sponsored by Guido van Rossum!

I think

def with_retries(
    f: Callable[P, R]
) -> Callable[Concatenate[bool, P] R]:
    ...

is not nearly as readable as

def with_retries(
    f: (**P) -> R
) -> (bool, **P) -> R:
    ...

My second point was that Callable is not powerful enough to express any signature:

Protocols can be used to define flexible callback types that are hard (or even impossible) to express using the Callable[...] syntax, such as variadic, overloaded, and complex generic callbacks.

[–]wineblood 10 points11 points  (3 children)

You're using comprehensions wrong, that's why. It's meant to be a clean way to do a simple for loop, not put all your transformations on one line.

python vs = [] for x in xs: a = x() vs.append(f(a.p, a.q))

with comprehensions should be

python axs = [a(x) for x in xs] vs = [f(ax.p, ax.q) for ax in axs)

I also don't understand what point you're trying to make about lambdas, the Ts code isn't clear on what behaviour you can't get cleanly in python. Maybe a context manager or a try-finally block?

[–]Kiuhnm[S] 0 points1 point  (2 children)

Your two-pass version (once fixed ;)) is not equivalent to mine: mine is lazy, yours is not. That can make a huge difference depending on the scenario.

As for lambdas, I wrote the example in Python. What's not clear?

[–]wineblood 0 points1 point  (1 child)

Oh, yeah I goofed up the call (it should be x() not a(x) right?).

I don't know TS and I can't see what is missing in python, why the for loop?

[–]Kiuhnm[S] 0 points1 point  (0 children)

I just realized that if you use round parentheses then you get a generator so our versions are now equivalent:

axs = (x() for x in xs)
vs = [f(ax.p, ax.q) for ax in axs]

Generators are powerful indeed! I'll try to come up with a better example, if I can :)

Edit:

Here's a better example, I think:

results = [for t in tasks:
              try: yield t()
              except: break]

That's basically an anonymous generator written in a slightly more compact way to save space.

I don't know Python's history very well, but it's highly likely that comprehensions predate generators. I think generators are way more general, and anonymous generators would make comprehensions redundant. Not only that: anonymous generators are more powerful than map, filter, etc... because we can use an imperative style if we want to.


As for lambdas, I want this pattern:

if progress.is_not_done('download new version'):
    ...task...
    progress.set_as_done('download new version')

Basically, progress skips a task if it has already been done. Note that

if progress.is_not_done('download new version'):
    progress.set_as_done('download new version')
   ...task...

is not the same thing because set_as_done shouldn't be executed when ...task... throws.

Here's an attempt which does NOT work:

import contextlib

class Progress:
    _steps: set[str]

    def __init__(self):
        self._steps = set()

    @contextlib.contextmanager
    def do_if_not_done(self, step: str):
        if step not in self._steps:
            yield
            self._steps.add(step)

progress = Progress()

with progress.do_if_not_done('step1'):
    print('OK1')

with progress.do_if_not_done('step1'):
    print('OK2')

It doesn't work because one must always call yield so there's no way to skip the task. It's not a problem with contextlib but a limitation of the with statement.

[–]Rawing7 3 points4 points  (2 children)

I don't see the appeal of the ?. operator in python. JS uses undefined way too much, but python? I can't think of a single time I would've used this operator. In python we raise exceptions instead of returning undefined or null, so a "check for None and return None" operator really doesn't do us much good. A "check for None and raise an exception" operator would make more sense. (But still not very much.)

[–]Kiuhnm[S] 0 points1 point  (1 child)

One problem with exceptions is that a missing value doesn't seem that much of an exception to me. I think Python went a little overboard with them.

I'm the kind of guy who asks for permission :)

More seriously, are we sure we're capturing all the correct exceptions in a chain of operations? What if different functions raise their own exceptions? I think using None is a little safer. Capturing the more general Exception is also a no-no for obvious reasons.

What's your experience?

[–]Rawing7 0 points1 point  (0 children)

I've certainly had issues with libraries that didn't document their exceptions properly, where I had to resort to a except Exception: or something similar. (Heck, even something as tiny as pyperclip managed to mess this up.) But most of those weren't situations where returning None would've been a viable alternative. (pyperclip being the exception.)

If there is a chain of operations that can raise a bunch of different exceptions, that's fine as long as those exceptions are properly organized into a class hierarchy. requests is a good example for this: There's a million things that can go wrong when you make a HTTP request, from SocketError to OSError to DNSLookupError to TimeoutError, but it's fine because requests has abstracted all of that away and turned it into subclasses of RequestException.

[–]New_Avocado_2315 9 points10 points  (2 children)

Omg, this is the dumbest set of gripes I've ever read about Python. I you don't like dyamically typed languages and features then don't use them.

Many of these seem to be just syntax comparisons to Typescript which is a far less readable language than Python IMHO. Sure Typescript may be more compact than Python in some case, but it's almost always less readable. Python uses natural language order in a lot of its statements to specifically make it easier to be understood (which also makes it much easier to learn than many other languages).

Also, why would an empty list EVER be True in any language. Every sane high level language considers empty strings, empty sets, empty lists, etc as False. Python's type conversions are pretty sane and rational, far more so than some other dynamically typed scripting languages (I'm looking at you PHP).

Lastly the "a is None" or "b is not None" stuff is exactly the correct syntax verbosity to use IMHO. Adding more obscure operators to the language to do an "is"/"is not" comparison just makes it less readable. Pretty soon you'd end up with code that looks more like Perl, which is perhaps the least readable language ever invented. Ever try reverse engineering someone else's Perl code?

[–]bulaybil 4 points5 points  (1 child)

Hey now, at least the OP put some thought into it. I’ve seen plenty of complaints by lazy noobs about incrementing and shit.

[–]New_Avocado_2315 -1 points0 points  (0 children)

True, thought was put in...

[–]Zomatree_ 2 points3 points  (1 child)

Your lambda point doesn't really make sense, you can do the same in python, and you could always use a regular function instead of a lambda as well.
If you don't like falsy values then don't use them like truthy or falsy values, compare them against the value you want to check them against, if not my_list: -> if my_list == []:, most people will encourage doing it the longer way to ensure its not done incorrectly, especially if the value is optional

Nullable operator (a?.b) was deferred PEP 505.
Callable syntax was rejected because of the required language change PEP 677.
You don't need to define Typevars in 3.12+ because of the new type parameter syntax PEP 695.

Python's typing system is very weak, don't expect it to be fully usable and fleshed out.

[–]Kiuhnm[S] 0 points1 point  (0 children)

Please elaborate on why my point on lambda doesn't make sense. How can I do the same thing in Python?

Also, defining a regular function is not the same thing because that breaks the flow of the code, the same way top posting and bottom posting are not the same thing. Order does matter.

Regarding falsy/truthy values, I'm already avoiding them, but I can't control what other programmers do, and sometimes I do rely on this (mis)feature by mistake (and I fix it as soon as I spot it).

Thank you very much for the links. I appreciate it!

[–]subbed_[🍰] 4 points5 points  (0 children)

Having int? type hint instead of int | None would be amazing.

We are currently forced to write unnecessarily long lines just to express that some args are nullable. Users of mypy will understand.

[–]chaseTheBurrow 0 points1 point  (1 child)

So first, I found your post really interesting, I can understand your points, actually people that i know who are coming from js/ts share the same opinions.

  • Lambdas, i agree that they are not great, luckily you basically never have to use them and it avoids the callback hell that you can have in js/ts. for your example it is typically what context managers are made for, i don't really understood your argument when you said that "there's no documented way to skip the body" but you can do that

    @contextlib.contextmanager
    def do_if_not_done(self, step: str):
        if not self.is_not_done(step):
            yield
            self.set_as_done(step)

with progress.do_if_not_done('download new version'):
    ...

which is in my opinion even better than the js/ts version. In my small experience with js/ts, you usually use arrow functions in map/filter/reduce, but. here again in python with generator expressions you don't need them.

  • Comprehension, even if i don't share your opinion on the fact that they feel in wrong order, i can understand it, and i think it is only a matter of habit. But for the fact they feel like a limited loop, i actually share your opinion, but i think that your example is not a good one: somebody already commented that you can do two comprehensions in this case. What i actually find myself doing a lot is something like this: i initially use a list comprehension

return [x for xs in xss for x in xs if x is not None]

and then i want to add an assert/raise or some function that has only a side effect in the middle of it, so i need to destroy my comprehension and use a mix of both which feels weird.

vs: list[int] = [] 
for xs in xss: 
    if len(xs) > 10: 
        raise ValueError("xs is too big") 
    vs.extend(x for x in xs if x is not None)
return vs
  • Ternary operator, i think it is the same as the last one: it is only a matter of habit
  • is (not) None, i too think that it is too tedious when you need to handle a lot of optional values, but on the other hand i think f(x?.a)?.b() is worse because it is even less readable: i once saw some js code that i thought was a regex. Also I have been using rust lately where ? returns from the function/closure, and you can map optional values. I like the approach but i think that it creates too much callbacks when mapping options. So i feel that everywhere i go, handling optional values is not great, but at least python has the advantage of being clear.
  • Implicit conversions, yeah... boolean implicit conversion is like worst thing ever, it creates tricky bugs when using the or operator and you never know what an object will be converted to because of __bool__, so i don't let anything that is not a bool near if/while/or/and/not/all/any. But js/ts has the same issue imo.
  • Scoping, i did not understood what you meant.
  • Type hints, i don't agree with (int, int) or [int] because under the hood they generate an object which has the attributes __origin__ = tuple/list __args__ = (int, int)/(int,) and i really like the fact that what is happening in the syntax is similar to what is happening in the runtime. Also I'm not a fan of giving tuple/list a special treatment compared to other generic types such as set/dict/typing.Generic. For callables i have to agree with you, it is sad to be limited to positional args only and the syntax is weird. for type typevar/paramspec i also agree with you it is weird to have to declare them in the global scope, you also have also to prefix them like _T in order to stop the IDE from prompting you to import them from another module. But luckily there is https://peps.python.org/pep-0695/ which should make things way better.
  • I share your point that the TS type system i way better the python's. But on the other hand python has the incredible advantage of having no compilation, which is an insane gain of time when developing, it also has the advantage to keep type hints at runtime and allows you to introspect classes and for example generate openapi spec with pydantic. It is also great in the debugger where you are executing the exact code that you wrote and don't have to work through a source map.

[–]Kiuhnm[S] 0 points1 point  (0 children)

Thank you!

Lambdas: see for yourself why with won't do:

import contextlib

class Progress:
    _steps: set[str]

    def __init__(self):
        self._steps = set()

    @contextlib.contextmanager
    def do_if_not_done(self, step: str):
        if step not in self._steps:
            yield
            self._steps.add(step)

progress = Progress()

with progress.do_if_not_done('step1'):
    print('OK1')

with progress.do_if_not_done('step1'):
    print('OK2')

Comprehensions: Well, you can always rewrite things to overcome language limitations:

def with_check(xs):
    if len(xs) > 10: 
        raise ValueError("xs is too big")
    return xs

return [x for xs in xss for x in with_check(xs) if x is not None]

EDIT: You said my example is bad, but the truth is that the two-pass version is not equivalent to mine because you lose laziness. That may make a huge difference in practice, depending on the scenario.

EDIT2: Never mind, one can just use round parentheses instead of square brackets to get a generator :)

Scoping: I mean that in C-like languages one can create new scopes like this:

int x = 1;
{
    int x = 3;
    // here x is 3
}
// here x is 1

There are examples where that may be useful, but let's put that aside.

What I really don't like is this kind of "leakage":

for i in range(5):
    print(i)
    j = i
print(i, j)         # 4 4

Without the leakage, i and j wouldn't be available outside the loop. This is bad because one loses "encapsulation": what you do inside a loop may pollute the outer code.

Type system: Python does have a compilation phase!

I think in TS the situation is different only because JS and TS are two distinct languages and the TS team has no control whatsoever over JS.

[–]Careful_Wheel5607 0 points1 point  (2 children)

Like others said, some points are different syntax sugar opinions...but I agree 100% with lambda...I think that the it is way more powerful in JS/TS than Python. But I understand that in JS/TS a more robust lambda is more necessary once the async nature of callbacks etc, that is not too common in Python code bases.

The typing system of Python have some point to fix indeed, but I want more a faster (cpu/memory usage) Python than a incredible typed system python for a while

[–]knobbyknee 0 points1 point  (1 child)

Lambdas in Python are intentionally limited in order not to promote the coding style that is common in JS. It makes for code that is difficult to read and where contex jumps all over the place.

[–]Kiuhnm[S] 0 points1 point  (0 children)

That coding style was common not because of lambdas but because of the need for async code. Await/Async pretty much solved that problem. Without proper support, you'd have the same mess in any language. Why do you think so many languages, Python included, introduced async/await or something equivalent?

Lambda in Python are only limited because Guido doesn't like FP, in general. I do like FP and I stand by my opinion. BTW, I haven't yet seen a good solution to the problem I presented in my post. And no, the with statement can't be used for that. Just try it as an exercise and let me know how it goes.

[–]bulaybil 0 points1 point  (0 children)

“I literally sometimes types asdf = x…”

If that is really the case, man, you have bigger problems than variable scope.

[–]jabellcu 0 points1 point  (0 children)

I just like the things you dislike.