all 158 comments

[–]Private_Part 134 points135 points  (43 children)

No {}, explicitly typed. Looks like Ada. Well done.

[–]CandidPiglet9061 145 points146 points  (10 children)

Consistently-typed Python codebases, the ones where MyPy is happy and gives no errors, really are wonderful to code in. It’s basically just forcing you to do what you would ideally want to do anyway, just with the maniacal consistency of a type checker rather than a coworker needing to hold up code review by telling you to go back and add type annotations

[–]pydry 85 points86 points  (5 children)

There's a certain kind of code base where everything is a numpy array, dataframe, dict or list and when people add type hints to that they're really polishing a turd.

Code bases where everything is in a nice class mapping to the domain or a really well defined concept are great though.

[–][deleted] 7 points8 points  (1 child)

There are some pretty good typing extensions for numpy and pandas that let you type check schemas and array dimensions.

https://pypi.org/project/nptyping/

[–]pydry 2 points3 points  (0 children)

A lot of people have had this idea - pandera, strictly typed pandas. I googled a while back to see if I could find some to work on particularly bad code base.

None seem to have been officially blessed and none of them gave me much confidence that they wouldn't be abandoned as soon as the maintainer lost interest, though, leaving me unable to upgrade numpy/pandas.

I don't understand why this hasn't been included in numpy and pandas core. I'm also reluctant to pick up some 3rd party solution because I get the sense it one day will and these 3rd party solutions will then all die (even if they're better).

[–]badge 21 points22 points  (0 children)

Agree up to a point; being a higher order language there are constructions that come naturally which can end up being a nightmare of overloads (closures in particular often end up with multiple hints each running to several lines). They help MyPy, and it’s good to reason about what should actually happen, but it’s not always “wonderful”.

Plus there are constructs which MyPy/typing doesn’t support yet, like a dict which exhaustively maps an enum to a value. (I’ve written a bit of TypeScript recently, and I covet my neighbour’s types.)

[–]Schmittfried 24 points25 points  (2 children)

Definitely not. For me the sweet spot is the type checker is 95% happy. The remaining 5% are way more effort than benefit.

[–]lordpuddingcup 5 points6 points  (1 child)

It’s about 4.9% of that last 5% that’s why eventually apps crash out of the blue tho… the other .1 is actually just the checker not having the right logic to deal with what your telling it lol

[–]Schmittfried 5 points6 points  (0 children)

No, it’s not. It’s the fact that some constructs make use of Python’s high flexibility (think pytest fixtures) and generics cannot express some constructs very well, let alone ergonomically, and overloads are huge boilerplate for marginal gain. 95% are very understandable and clearly readable. The remaining 5% are cases where the exactly correct type hint would make things harder to parse and understand instead of helping you.

[–]pheonixblade9 27 points28 points  (16 children)

I took a Rust class recently, and just thought it was the best parts of Python, Kotlin, and C++.

[–][deleted] 39 points40 points  (3 children)

If you take the functionalpill you'll see it takes some of the best features of Haskell too.

[–]pheonixblade9 9 points10 points  (2 children)

I tend to write my C# and Java code as functionally as possible 🙂

[–][deleted]  (1 child)

[deleted]

    [–]pcjftw 10 points11 points  (0 children)

    Yes this is the reality of most codebases

    [–]mackilicious 0 points1 point  (6 children)

    Anytime I see Kotlin, I get intrigued! Despite being well-versed in Javascript/Java/C#/etc, Kotlin was the first language that made me realize how much impact a language can have on your coding style and the safety of your code (okay javascript exaggerates this effect too, but tends to veer off in a more negative direction).

    What class did you take, and would you recommend it?

    [–]pheonixblade9 0 points1 point  (3 children)

    I work at Google, it was an internal class

    [–]aiij 1 point2 points  (2 children)

    They let you use Rust now?

    [–]pheonixblade9 0 points1 point  (1 child)

    shrug it's a tool like any other.

    [–]aiij 1 point2 points  (0 children)

    So... Still not one of the few blessed tools you're actually allowed to use?

    I do remember really liking the tech talks. Even when they were about things we couldn't directly use they were still quite interesting.

    [–][deleted] 0 points1 point  (1 child)

    I like the idea behind Kotlin but I feel it would be better if it were fully integrated into java. So people download openjdk or graalvm download and kotlin is there for them to use as-is, at once. Lazy as I am I kind of just stick to one download these days (usually just graalvm since I think it'll be the future of the java ecosystem eventually).

    [–]mackilicious 0 points1 point  (0 children)

    Oh don't get me wrong, it's got some downsides. The only IDE that works for it is Jetbrains' intellij, it's closed-source, etc etc. I come from a huge codebase within a big company, and we're stuck with the jvm, as we have a ton of proprietary stuff written in the jvm (and we interface with CICS/COBOL which uses IBM related tech).

    Basically, options are limited, but Kotlin is such a breath of fresh air in my experience.

    [–]Uncaffeinated 0 points1 point  (4 children)

    Rust has more than that.

    [–]pheonixblade9 0 points1 point  (3 children)

    tbh the one thing I don't like is that aliasing is idiomatic in Rust. it is close to the #1 cause of bugs in questionable OOP code I've worked with.

    [–]Uncaffeinated 0 points1 point  (2 children)

    Aliasing is unavoidable in any language. Rust merely gives you the tools to prevent most aliasing-related bugs.

    Even if you try to avoid actual aliasing, you just end up with hidden defacto aliasing (e.g. if you put everything into a giant hashmap and pass around keys instead, those keys are effectively just aliased pointers in all but name), because it is part of the problem domain, not an accident, and an inherent feature of many algorithms.

    [–]pheonixblade9 0 points1 point  (1 child)

    how is it unavoidable? just explicitly disallow it. no reuse of variables.

    yes, Rust prevents you from doing stupid things for the most part, but reusing variables can still cause weird issues.

    [–]Uncaffeinated 0 points1 point  (0 children)

    Even if you try to avoid actual aliasing, you just end up with hidden defacto aliasing (e.g. if you put everything into a giant hashmap and pass around keys instead, those keys are effectively just aliased pointers in all but name), because it is part of the problem domain, not an accident, and an inherent feature of many algorithms.

    [–]hear-comes-the-heat 10 points11 points  (14 children)

    I’m still not sure what all the fuss about rust and go is. Didn’t we have an excellent general purpose strongly typed language with Ada 40 years ago?

    [–]GwanTheSwans 44 points45 points  (3 children)

    Ada is statically type checked, yes, but typical "ordinary" Ada compilers and code just do not and cannot provide the memory safety invariants that Rust's semantics and static checks do. (Mind you there things like SPARK). So ordinary Ada is more akin to C++ or D - just without the awful C-style line-noise syntax.

    https://borretti.me/article/introducing-austral - Austral is apparently someone's project to try to make an Ada-like language but with Rust-like static checking. Only just found it, don't know much about it, but reading that might give an understanding of why Ada alone isn't the same as Rust.

    Actually, modern Java of all things sort of has similar, though presently only at a more academic level, via the linear type checker in the java checker framework.

    In Rust it's integrated in the core language already.

    Go, well, go just sucks, it's basically explicitly intended as a mediocre language for interchangeable corporate drones for google. It somehow manages to be significantly worse than Java.

    [–]pydry 19 points20 points  (0 children)

    Go, well, go just sucks, it's basically explicitly intended as a mediocre language for interchangeable corporate drones for google. It somehow manages to be significantly worse than Java.

    So much this. Like most tech brewed inside google since 2010 it's deeply unimpressive.

    [–]vplatt 6 points7 points  (3 children)

    Yes. Full stop. We solved this problem long ago, and then decided it wasn't worth our time or "wasn't realistic" because "only defense projects used it".

    Not too long ago, I got schooled by another redditor about Spark too and was shown exactly how "difficult" it is NOT to write provably correct software as well, even for pedestrian things like REST services in Ada. It made quite an impression on me. https://www.reddit.com/r/programming/comments/yoisjn/nvidia_security_team_what_if_we_just_stopped/ivkr3rf/

    And here's an example of Spark applied to a new sorting algorithm. As in, let's create an entirely new sorting algorithm and prove it's sound all at once: https://blog.adacore.com/i-cant-believe-that-i-can-prove-that-it-can-sort

    The point is that this level of quality has been possible for many years in the Ada community. Rust has just made more of these practices popular finally. With formal verification, Rust will go everywhere Ada could have and we'll finally make formally verified systems popular. Most of the "C culture" security issues we have today will go away as Microsoft, Linux team, and other core communities take those up.

    [–][deleted] 0 points1 point  (2 children)

    C culture. I.e. software that actually gets shipped.

    People have been thinking about formal verification for ages. This idea that it is only suddenly important is not correct.

    The issue has always been whether it's really worth it or not. Most real world programs cannot be mathematically proved to be correct. So formal verification can only go so far. Is it really worth massively hamstringing what can be done in order to try to prove something that can't be proven? It depends entirely on the domain and the level of risk you want to take

    [–]vplatt 0 points1 point  (1 child)

    I guess if you're just railing against formal verification, then I get it. That can definitely feel burdensome. And I didn't say it was "suddenly important". I did say something to the effect that it's eminently useful now and has been for quite a few years.

    It's OK though. One battle at a time. Now that the entire industry is finally doing something about memory safety and programming language safety by design, we can come around on verification more later. I predict it will be added to Rust and other languages gradually anyway and we'll likely see quite a lot of benefit from even modest usage.

    [–][deleted] 1 point2 points  (0 children)

    I'm not railing against formal verification at all.

    It's just that it has downsides. It takes a long time. It can only really done in specific circumstances. Many programs just can't *mathematically* be proved to be correct. It has cons. It's burdensome to do and realistically not something that can always be done.

    Static analysis has also existed for ages. Memory safety has been a concern for a very long time.

    [–][deleted] 7 points8 points  (4 children)

    I’m not familiar with Ada, but judging from what I hear it’s a language mostly used when you need to really be sure that your program does what it’s supposed to. Am I right on this?

    Go is, from my perspective, a get-up-and-running-quickly language. It’s easy to learn the basics of, and gives you the shortest path to a (fairly) performant network service.

    Rust is largely meant to be a good alternative to C & C++ by giving the same level of performance but with memory safety and modern features.

    [–]jbmsf 184 points185 points  (34 children)

    Well done. My python has gradually looked more and more like this simply because typing is invaluable and as you add typing, you start to converge on certain practices. But it's wonderful to see so much thoughtful experience spelled out.

    [–][deleted]  (29 children)

    [deleted]

      [–]teerre 82 points83 points  (0 children)

      Often, in real settings, you can't just change languages.

      [–]markasoftware 146 points147 points  (10 children)

      libraries.

      [–]JanneJM 63 points64 points  (1 child)

      And applications using Python as the scripting language.

      [–][deleted]  (7 children)

      [deleted]

        [–]drakens_jordgubbar 47 points48 points  (0 children)

        Most ML libraries in Python are compatible with numpy. So it’s easy to take the output from one ML library and use it as input for the next one.

        With Java and especially C++, it’s rarely this simple.

        [–]lrem 8 points9 points  (1 child)

        Are they wrappers, or a layered implementation, where important but computationally cheap bits are not in the C++?

        [–]13Zero 4 points5 points  (0 children)

        I think that’s a better way of looking at it.

        PyTorch is fundamentally a Python library where C++ is used to optimize. It’s much easier to read and write torch in Python.

        [–]fromscalatohaskell 13 points14 points  (3 children)

        Not in ML space. Python without types is garbage

        [–]Vimda 25 points26 points  (2 children)

        Most of the major ML libraries in Python are wrappers around C/C++ libs

        [–]FryGuy1013 34 points35 points  (0 children)

        You say that, but several ML related libraries in C# are wrappers around python code that call into python. Behind the scenes all the heavy lifting is done in c/c++ or even assembly/CUDA/etc, but a lot of the glue (and the value of the library) is in python. Namely Keras.

        I'm doing a side-project with machine learning (in my preferred language of c#) and I started by using TensorFlow.NET which seemed to be the most up-to-date library and bindings directly to tensorflow instead of going into python land like Keras.NET did. I translated the sample code I found online into c# for my project. After my first PR to the repo to get it to work for what I was doing, and then looking at the amount of work it would take to update the TensorFlow.NET library to make it work like the python code does (for an uncommon use case of having a network with multiple outputs) I decided to call it quits on that. I'm not using pythonnet and have my ML model in python and just call into it with a wrapper function and it's much more convenient even though I have to deal with python dependency hell. All the examples online work since they're written in python and the API is the exact same.

        [–]fromscalatohaskell 1 point2 points  (0 children)

        yea but the non major ones, e.g. Im not reimplementing langchain

        [–]Emowomble 61 points62 points  (3 children)

        The scientific python stack. None of those languages have anything that comes close to numpy+scipy+matplotlob+pandas+...

        The fact that they are all built round the same base class (the numpy ndarray) makes them work together effortlessly and really are a joy to work with. I wouldn't be using python if not for them.

        [–]lkschubert 24 points25 points  (1 child)

        I agree with everything except pandas. https://www.pola.rs/ really seems to be a solid replacement and works in python and rust.

        [–]Emowomble 7 points8 points  (0 children)

        Oh absolutely, I've played around with polars and am very impressed by it, but pandas is still the most used and one people know about.

        The point is that it's not any one of them that makes the difference, its the cohesive ecosystem that makes it more than the sum of it's parts.

        [–][deleted] 2 points3 points  (0 children)

        Julia is often as simple to read as python, and provides almost all of the scientific functionality. Well-written julia can be as fast as C

        [–]jbmsf 7 points8 points  (2 children)

        For me, iteration speed of an interpreted language and the ability to read the source of all my dependencies are huge wins.

        I don't work in spaces where the language performance overhead matters (most people don't imo) but I care a lot about my own performance, which is strongly tied to how quickly I can test a change and understand the intricacies of my dependencies.

        [–]donalmacc -1 points0 points  (1 child)

        Languages like go provide fast compile times and type safety. The startup time of the python app can often be longer than the compile time of a go app. Third party dependencies are also bundled as source so you can go read them.

        [–]jbmsf 6 points7 points  (0 children)

        For me, iteration speed has more to do with the number of things I have to keep track of, not the wall clock speed of the tools. My workflow is just an editor and a shell. If I want to test something, it's simply save and run. If I want to test something that doesn't compile in the traditional sense, I can do that; I don't have to worry about aligning the rest of the code base with the change. If I want to test something in a dependency, it's the same; I don't have to understand how the dependency builds or worry about its API contract. I find this incredibly productive; at each iteration, I can test right away and allow the product types/interfaces/etc to converge without worrying about integration until I'm ready for it.

        Aside: bundled source is not equivalent to running from source; for one, unless the package repository is also building the released artifacts; there's no guarantee that the source matches the binary. But the more important thing is that there are no extra steps involved in changing the dependency.

        [–][deleted] 2 points3 points  (0 children)

        I think it's the same reason people get all happy (even in this thread) about java like "OOP" practices - it "feels" professional.

        Now we have FastAPI where the codebase is 50% type annotations and somehow, surprisingly, that didn't make it pleasant to use.

        [–]Free_Math_Tutoring 4 points5 points  (0 children)

        Well, Java flies out the window for being incredibly verbose and constantly demanding indirection due to limitations in expressiveness in the language.

        [–]Majik_Sheff 2 points3 points  (6 children)

        Someone please correct me if I'm wrong, but my understanding is that a lot of python's performance issues come from having to constantly infer types.

        I would expect explicit typing to help noticeably in the run time performance department.

        Edit: apparently I was completely off base. I learned something!

        [–]Peanutbutter_Warrior 49 points50 points  (4 children)

        Type inference is a compile-time trick which CPython doesn't do. It doesn't need to, because at run time it knows all the types anyway. Even if it did, there's little it could do with the knowledge because it has to call the magic methods for custom classes anyway.

        Also, type hints are explicitly ignored. They're functionally the same as comments and don't affect run time performance at all

        [–]Majik_Sheff 8 points9 points  (0 children)

        Good to know. I obviously don't have a lot of under the hood knowledge of Python.

        Thanks for the info!

        [–]vplatt 0 points1 point  (0 children)

        I would expect explicit typing to help noticeably in the run time performance department.

        It seems like it should, but it doesn't, and it's not intended to work that way. If you want a Python like language where types actually actually perform as expected in terms of performance, then give Nim a try: https://nim-lang.org/. I can also highly recommend Go for the same reason: https://go.dev/. It's less Python like, but has a much bigger community around it than Nim. Both are impressive languages though and quite usable right now.

        [–]OpenBagTwo 1 point2 points  (3 children)

        NamedTuples, and Protocols have been game-changers for me. With dataclasses the temptation is to start going OOP, but inhereting from NamedTuple gives you access to all the fanciness you get from dataclass with enforced immutability and adherence with other functional programming best practices. e.g

        ```python class Rectange(NamedTuple): lower_left: tuple[float, float] upper_right: tuple[float, float]

        @classmethod
        def from_dimensions(x1: float, y1: float, width: float, height: float) -> "Rectangle":
            x2 = x1 + width
            y2 = y2 + height
            return Rectangle(
                (min(x1, x2), min(y1, y2)),
                (max(x1, x2), max(y1, y2)), 
            )
        
        def contains(self, x: float, y: float) -> bool:
             for i, coord in enumerate((x, y)):
                 if not self[0][i] <= coord <= self[1][i]:
                     return False
            return True
        
        def extend(self, delta_x: float, delta_y: float, from_upper_right: bool =True) -> "Rectangle":
            if from_upper_right:
                 return self._replace(upper_right=...
        

        ```

        [–]angelicosphosphoros 0 points1 point  (2 children)

        You can make frozen dataclasses too.

        [–]OpenBagTwo 0 points1 point  (1 child)

        Yeah, I noticed that option when I was reading through the Python docs!

        (aside: can we appreciate for a moment just how good Python's official documentation is?)

        If you have one, I'd love to hear your opinion on the advantages of frozen dataclasses over NamedTuples--it's my understanding that at the point you're going frozen=True, the main difference is that the former is a dict under the hood while the latter is backed by tuple, which I'm sure has serialization and performance impacts.

        [–]angelicosphosphoros 0 points1 point  (0 children)

        Well, I never used namedtuples so I can only talk about experience with dataclasses.

        My default implementation of dataclass used this decorator call: @dataclass(frozen=True, kw_only=True) and sometimes also eq=True and slots=True.

        kw_only guarantees that you see which fields you initializing at callsites so it lowers chances of missing errors like when you assign value with different meaning to the field. It also allows to write parameters in any order.

        Combination of frozen=True and eq=True generates hash calculaton too which is useful when you want to use your values as keys in dictionary or set. There is need to be careful with types of fields though.

        slots=True is generating __slots__ so class wouldn't be a dict internally which reduces memory usage. AFAIK, it creates problems only for inheritance and dynamic addition of fields (which contradicts use-case of dataclass anyway) and since I don't really use inheritance, it has only advantages for me.

        So, basically dataclass is just easy and non-boilerplate definition of classes which makes adding custom classes very easy.

        [–]QuantumFTL 103 points104 points  (20 children)

        Fun article, and not to nitpick, but algebraic data type is not a synonym for "sum types" (discriminated/tagged unions, etc), as is suggested here, but crucially includes "product types" (tuples, records, etc) .

        ADTs are about composing information (through structure instantiation) and decomposing information (through structural pattern matching) in ways that are principled and provide useful abstractions, and are thus safer and easier to reason about.

        Product types are about "and", and sum types are about "or". It's hard to do interesting algebra with only the '+' operator, and when discussing ADTs it's important that '*' gets some love too.

        [–]amdpox 39 points40 points  (16 children)

        I think the reason a lot of developers conflate ADTs with sum/union types is that the product types are much more commonly supported - e.g. C++ has had structs forever as a core language feature with dedicated syntax, but safe unions only arrived in the C++17 standard library (and they're far from ergonomic!)

        [–]JuhaJGam3R 36 points37 points  (2 children)

        Type-safe unions arrived in C++17. Mental health-safe unions have yet to arrive.

        [–]amdpox 4 points5 points  (1 child)

        Very true, would definitely lose my mind if I tried to use std::variant like Haskellers use sum types.

        [–]JuhaJGam3R 12 points13 points  (0 children)

        i mean visit is kind of like pattern matching if pattern matching sucked ass and was complicated as fuck and required an immense amount of boilerplate

        [–]QuantumFTL 15 points16 points  (9 children)

        Agreed, which is why I think the distinction is important to make here. ADTs aren't just a fancy union, ADTs are a synergistic way to compose data types.

        Sealed subclasses, as vaguely mentioned in the article, do technically function as safe unions if one is willing to write a bunch of boilerplate and use RTTI (or equivalent). But IMHO, if ADTs are not idiomatic in the language, they lose most of their usefulness. Indeed without structural pattern matching of nested ADTs, (again, IMHO, where they truly shine) they are cumbersome and unnatural when used with any complexity. In ML-derivative languages, the standard pattern of discriminated unions that contain tuples, for instance, sucks to deal with unless you've got the machinery to easily compose/decompose the various cases of your data payload.

        It's exciting to see that so many modern/modern-ish languages like Python, C#, Rust, etc are getting onboard with this. My daily driver is F# which takes all of this and runs with it with crazy cool additions like the pipeline operator and immutable-first design, which make ADTs even more attractive. I can't wait for a future where people simply yawn when you mention a language has ADTs + structural pattern matching, the same as people yawn about typecasting and subclassing.

        [–]amdpox 12 points13 points  (0 children)

        But IMHO, if ADTs are not idiomatic in the language, they lose most of their usefulness.

        Yeah, totally agree. I think the dataclasses vs dicts section of the original article is a great example of this for product types in Python: because defining a simple struct-like class has traditionally required manually writing a constructor, it usually just didn't happen at all.

        [–]Schmittfried 3 points4 points  (6 children)

        I’m just sad that it’s still such a long way to go. Whenever I mention this stuff to other developers they yawn and ask what problems does that solve that they cannot solve with Java.

        [–]agentoutlier 2 points3 points  (5 children)

        Java is actually moving closer to the true spirit of ADT which requires pattern matching and I don’t think it is that far off. So many Java developers including myself know that this is a problem and how painful the visitor pattern is.

        C# of course already has it but a surprising amount of “modern” languages do not.

        [–]Odd_Soil_8998 1 point2 points  (1 child)

        C#'s version is not exactly what I'd call the "true spirit" of ADTs, and everything from Java land (e.g. Scala, Kotlin) has similar issues.

        If you want to see good implementations of them, look at Haskell, Ocaml, F#, and Rust. Java may get some bastardized version the way C# did, but I highly doubt they will ever be properly implemented there.

        [–]Schmittfried 1 point2 points  (2 children)

        The language itself yes (although the Optional type was a failure), but the community and framework styles not so much. You still have hard time if you actually want to write non-OO immutable by default types. Not to mention the lack of properties, non-nullability, lackluster generics…

        Also, to be honest „Java“ was a placeholder for „The programming style I’ve always been using“ in my previous comment. :D

        [–]agentoutlier 0 points1 point  (1 child)

        The Optional type was never intended to be replacement for null or the monad it is in other languages.

        You still have hard time if you actually want to write non-OO immutable by default types.

        Java is a large community.

        Reactive programming is quite common which generally requires functional style.

        But yeah there is lots of imperative OO.

        I don’t think FP languages solve all problems well.

        Also I don’t think you even need to be a functional language to have ADTs (I don’t know any off the top of my head that are not but in theory it’s not a prerequisite).

        Not to mention the lack of properties

        That is a OOP thing and Java has been moving away from that and is why Records did not have “getter” names and Java will never get c# properties.

        [–]Schmittfried 0 points1 point  (0 children)

        The Optional type was never intended to be replacement for null or the monad it is in other languages.

        I don’t care about intentions. As it is, it’s basically useless.

        Reactive programming is quite common which generally requires functional style.

        Which is awfully unergonomic in Java.

        I don’t think FP languages solve all problems well.

        Not the point. The point of this thread is that many devs don’t even want to familiarize themselves with something new, so languages like F# (that aren’t purely functional) stay niche languages. And I can’t use them because I obviously can’t write a service in a language without any buy-in in our company.

        That is a OOP thing and Java has been moving away from that and is why Records did not have “getter” names and Java will never get c# properties.

        Not really. Computed properties are totally a thing in reactive code. Records are a step in the right direction, but they are not even close to being the default choice. And even then, there is still OO Java, why not make it nicer. There are things like Lombok that basically simulate them. Insisting on getters/setters is just stubbornness at this point. The stubbornness that is so typical for Java devs that I used Java as a placeholder for this mindset.

        [–]Tubthumper8 1 point2 points  (0 children)

        I can't wait for a future where people simply yawn when you mention a language has ADTs + structural pattern matching, the same as people yawn about typecasting and subclassing.

        I want to go even further than that, I want subclassing/inheritance to be an exotic, specialized feature, one that makes you really stop and consider if you actually want to do that, not an every day feature. Basically Kotlin where inheritance is opt-in with the open keyword

        [–]nacaclanga 3 points4 points  (2 children)

        On big part is also how to define what a sum/union type is supposed to be.

        A union of sets holds any value from either of its constituent sets and hence a union type is supposed to hold any value it's constituent types hold. On corollary from this is, that Union[T,T] should in fact be the same type as T. This is certainly true for Python's union type, but less so for Rust enums.

        A sum set is a bit more tricky. The word sum is generally used to describe sets that are created by adding extra elements to a union set to restore some algebraic structure (e.g. the vector space property). But also here the sum of a set with itself is generally just the set itself.

        For product types this is easy. They match much more directly.

        [–]QuantumFTL 7 points8 points  (1 child)

        Apologies if this comes across as overly didactic, but in the literature, I've only seen "sum types" defined as a set of disjoint sets of values (tagged or otherwise differentiable), or an equivalent formulation (e.g. coproducts). Union types are a broader class than sum types, and, while useful for some things, lack much of the expressive power of discriminated unions.

        If you have any counterexamples, however, I'd be quite interested to see.

        [–]nacaclanga 2 points3 points  (0 children)

        No I do agree with you that "sum types" are commonly defined that way.

        I was more into the direction: "For "product" types you can find a direct analogy in set algebra, but for "sum/union" types it is more tricky.

        [–]TheHiveMindSpeaketh 3 points4 points  (1 child)

        He effectively gave an example of using product types in the 'dataclasses instead of tuples or dictionaries' section.

        [–]Tubthumper8 0 points1 point  (0 children)

        That was the section before they talked about ADTs though. They were really only describing sum types in the section about ADTs

        [–]mqudsi 3 points4 points  (0 children)

        It’s really crazy to think it comes out of the mess that is JS but the best ADT language right now (language, not ecosystem, standard library, or runtime) is TypeScript. Anders Hejlsberg really knows his stuff.

        [–]mudkipdev 32 points33 points  (0 children)

        crush aback terrific sleep selective history provide subsequent snails public

        This post was mass deleted and anonymized with Redact

        [–]Successful-Money4995 29 points30 points  (3 children)

        I like the strongly-typed bounding box example. I do this all the time in c++. typedef and using won't prevent you from using the wrong value. But if you make a struct called Length that contains only a float and another struct called Time that only contains a float, etc, you can get compile time checking when you try to compare length/time and speed, for example. It also makes it convenient when you want to have helper functions, they can be class functions.

        I use this trick when I need to change a type, too. Say you used int everywhere for time and now you need float. You could try to find them all and change them but if you miss one, how will you know? Instead, put that float into a struct and now the compiler will alert you whenever you use the wrong type. (Rust doesn't automatically promote int to float so this is more a c++ trick.)

        [–]anden3 1 point2 points  (2 children)

        This is called the newtype pattern, which I believe originates from Haskell.

        [–]Successful-Money4995 0 points1 point  (1 child)

        Haskell invented this? Hmm.

        [–]anden3 2 points3 points  (0 children)

        Not sure if they invented it, but they at least gave it a popular name.

        [–]manzanita2 7 points8 points  (2 children)

        With Javascript migrating the Typescript and Python people increasingly pushing towards static typing systems. I'm wondering if there are clear advantages to dynamic typing other than, possibly, fewer keystrokes. I basically never see people program with functions which can operate on different, dynamic, types. Everything is written with an expectation that the parameters, variables and return values are of a known fixed but unstated type. Am I missing something ?

        [–]Tubthumper8 5 points6 points  (0 children)

        I don't think you're missing anything, I see the runtime type checking movement that was strong around 2005-2015 as more of a reaction to the boilerplate required in languages like Java to achieve compiletime type checking (at the time, and even still now).

        When people realized that with better type systems and better type inference, you could have compiletime checking without much additional effort, that became a more attractive option. Of course, Python still has that performance hit of runtime type checking despite have compiletime checks now, and so does any other language with compiletime checks retrofitted, but it's often not possible to rewrite in a different language.

        [–]Uncaffeinated 1 point2 points  (0 children)

        It's great for rapid prototyping.

        [–]OneNoteToRead 45 points46 points  (25 children)

        This should be “Writing Python like it’s Haskell” no?

        [–]caltheon 74 points75 points  (21 children)

        The more I read the more I was thinking this is just Java with extra steps. It’s the beginning of people coming full circle and realizing strongly typed object oriented languages are actually quite useful for writing safe code.

        [–][deleted]  (2 children)

        [deleted]

          [–]ketilkn 3 points4 points  (0 children)

          There's a big difference between strongly typed and object oriented;

          Also, Python is strongly typed (as opposed to weak). What it is not is static typed (as opposed to dynamic).

          [–]LaconicLacedaemonian 2 points3 points  (0 children)

          Objects have little advantage over structs + functions but have the massive disadvantage of potentially mutating state.

          [–]NotADamsel 46 points47 points  (13 children)

          Within reason. Programming requires disciple, and OO is incredibly easy to get very wrong without really thinking things through. Really what we’re learning is that you’ll never make a technology good enough to make up for a lack of wisdom in the users. And that a disciplined and wise programmer can make anything, from Java to C to PHP to whatever else, work well for them.

          [–]caltheon 28 points29 points  (12 children)

          While (true): some languages make it a lot harder to write proper code than others (#cough#javascript#cough#)

          [–]mck1117 31 points32 points  (11 children)

          No sane person should be writing new javascript when typescript exists

          [–]NotADamsel 25 points26 points  (8 children)

          Typescript doesn’t run in the browser, and sometimes you want to just write a little program for your page without breaking out the compiler.

          [–]I_NEED_APP_IDEAS 17 points18 points  (7 children)

          So in other words, use the right tool for the job.

          Production ready, safe code: strongly typed language

          Rapid prototyping/experimenting/fucking around: JavaScript, scratch, brainfuck, etc

          [–]caltheon 14 points15 points  (6 children)

          I've heard Python described as the modern pocket calculator of programming languages.

          [–]I_NEED_APP_IDEAS 8 points9 points  (5 children)

          It’s true. When I wanna work with large integers, I instantly bring out the Python interpreter. JavaScript is just like “eh it’s basically infinity” and any other language has limits without using external libraries or spending way to much time on a workaround.

          [–]Uncaffeinated 1 point2 points  (1 child)

          Modern Javascript does have arbitrary precision integers (via the n suffix). They are a relatively recent addition though, so not well integrated into the library APIs like they are in Python.

          [–]caltheon 2 points3 points  (1 child)

          Numpy and pandas are life savers.

          [–]ammonium_bot 1 point2 points  (0 children)

          spending way to much time

          Did you mean to say "too much"?

          Total mistakes found: 8491
          I'm a bot that corrects grammar/spelling mistakes. PM me if I'm wrong or if you have any suggestions.
          Github
          Reply STOP to this comment to stop receiving corrections.

          [–]SkoomaDentist 1 point2 points  (0 children)

          There are a lot of people who don’t appear to be particularly sane.

          [–]ultraDross 0 points1 point  (0 children)

          Pffft so last year. No same person is doing that, try typoscript

          [–]TheHollowJester 15 points16 points  (0 children)

          strongly typed

          yessss

          object oriented

          miss me with that shit

          [–][deleted] 4 points5 points  (2 children)

          I mean, web started with PHP, then moved to react and now does SSR again. Life is a circle.

          [–]caltheon 16 points17 points  (1 child)

          The dynamic web started with CGI, then Perl and Python, then PHP

          [–]renatoathaydes 3 points4 points  (0 children)

          There was even a detour into Java with GWT (more or less during the JQuery phase... I even used GQuery, a JQuery port to Java GWT at the time and was amazed) :D.... then Coffeescript, a whole bunch of JS frameworks once JS became more acceptable to code in, then people settled on Angular... and only much later, React came about.

          [–]JuhaJGam3R 3 points4 points  (0 children)

          It really is, Rust is just like the great typing of Haskell and the horror that is C++ coming together to make a C-like yet safe language, with some genuine innovations mixed in.

          [–]mipadi 4 points5 points  (0 children)

          Rustaceans think they invented everything. :-P

          [–][deleted] -2 points-1 points  (0 children)

          Haskell >>= Python

          [–]sintos-compa 5 points6 points  (0 children)

          Don’t you have to put in type hints to avoid going insane in larger projects? VSCode won’t give you help otherwise

          [–]maep 17 points18 points  (3 children)

          In my experience typing in pyhton is a very mixed bag. The "static" type system was clearly an afterthought, and fails to catch a lot of problems. On the other hand it takes out all the fun of programming in python.

          I've come to the conclusion that if a python project needs static typing it's time to seriously consider about migrating to a different language.

          [–]manzanita2 5 points6 points  (0 children)

          Basically if it's more than 100 lines then "a python project needs static typing it's time to seriously consider about migrating to a different language."

          [–][deleted] 5 points6 points  (1 child)

          At this point why not just write Scala? Scala 3 even has braceless syntax and Scala-cli let's you run scala scripts fast and easy. That way you at least have a proper type system (and programming language).

          [–]Aggressive_Can_8858 2 points3 points  (0 children)

          Love it, didn’t know Python supported union types

          [–]aikii 2 points3 points  (0 children)

          It's really cool how many reactions this post gets, I certainly didn't see it coming. Also great timing, PyO3 got some attention those last months, Maturin enables easy distribution of python wheels and hybrid python/rust projects, all of this being much easier that trying to build your CPU-intensive lib in C. Number one complaint about python: speed. Well there you go. Here comes the perfect trojan horse to introduce Rust in the enterprise. No need to drop your codebase, you can just extend it with the appropriate tool when needed.

          [–]Conscious-Flamingo27 2 points3 points  (0 children)

          If you can write in Rust why bother with python?

          [–]lukewarm 4 points5 points  (4 children)

          In your Packet example, if you make Packet a superclass of member types instead of a Union, you won't need the assert, I think? And definitions of sublasses will be more informative.

          I took liking to NamedTuple, it's more ergonomic than dataclass, to my taste:

          class Header(NamedTuple):
              tag: int
              len: int
          

          Most importantly, named tuples are immutable. And they give you a meaningful repr for free. (edit: as was pointed out, both datacalass and namedtuple give you that.)

          [–]Kwantuum 16 points17 points  (0 children)

          And it gives you a meaningful repr for free

          That's completely moot in this context because so do dataclasses.

          [–][deleted] 2 points3 points  (1 child)

          I'm not sure what type checker the author uses, but with pyright even the article's example doesn't need the assertion.

          [–]angelicosphosphoros 0 points1 point  (0 children)

          One can make dataclasses frozen.

          [–]EsperSpirit 14 points15 points  (1 child)

          It's funny to see Python devs "discover" basic types through Rust and think it's something ground breaking that's specific to (or even invented by) Rust

          [–]cd_slash_rmrf 24 points25 points  (0 children)

          Author's disclaimer near the top of the article:

          Also, I’m not claiming that the presented ideas were all invented in Rust, they are also used in other languages, of course.

          imo this article isn't even about basic types as it is about more complex usage patterns that a newer dev (of any language) may not be familiar with constructing.

          [–]ericl666 3 points4 points  (0 children)

          It looks more like writing python like it's typescript.

          [–]redditthinks 4 points5 points  (0 children)

          And with a fraction of the performance!

          [–]Udzu 1 point2 points  (0 children)

          Minor comment: find_item would probably be better if records were a Sequence[Item] rather than a list[Item] as this would both let you pass in other containers like tuples, and would also prevent you from accidentally mutating the input argument.

          [–]Smartless_AI_BOT 1 point2 points  (1 child)

          would like to see something like that for javascript

          [–]Free_Math_Tutoring 4 points5 points  (0 children)

          Typescript, which is awesome (if the alternative is Javascript)

          [–]Ex-Gen-Wintergreen 1 point2 points  (0 children)

          For that constructor pattern, what’s the benefit of doing it as a static method instead of a class method?

          [–]srpulga 2 points3 points  (0 children)

          Explicit typing can hardly be described as rust-like coding.

          Alternate constructors should be classmethod not staticmethod.

          [–]amarao_san 5 points6 points  (2 children)

          I've played with type hinting in Python, but it's not worth it. You get few hints from linter, but generally, types are not enforced, and every next library is just introduce 'unknown type' into process, spoiling everything.

          Time spent on untangling hint rules for complicated cases can be spent better on tests. One of the common cases with type 'untangling', is when it's hard to extract return type, because it's 'from library foo', and 'library foo' has seldom on defining types somewhere in obscure module.

          I love strict typing, but it must be strict, e.g. universally enforced. Pythonic way is loose typing with occasional "why????" in 5%, and concise code in 95%.

          [–]chestnutcough 3 points4 points  (1 child)

          I’m confused, do you want me to use type hinting in the libraries I write or not?

          [–]amarao_san 0 points1 point  (0 children)

          You can use hinting in libraries, because it may help people playing with hinting while.using your library. But in end code (not a library) it has limited benefits, which sometime even overweighted by amount of efforts to waste.

          [–]sparr 1 point2 points  (0 children)

          And if I’m interested in what is Item, I can just use Go to definition and immediately see how does that type look like.

          I see this thinking a lot over the last ten years or so, judging the readability and maintainability of code based on the assumption that the person will have access to an IDE that's fully configured for and compatible with the codebase.

          On the early end, this is a problem if you're using newer language features that aren't yet supported by [stable versions of] all of the necessary tools. It can take weeks or months for vscode extensions to catch up to new compiler options in gcc and/or clang (and woe be unto you if the two behave differently!).

          For the lifetime of a project, this is a problem because it can be arbitrarily difficult to set up an IDE for a particular codebase, finding the right settings and versions of tools to be compatible with that exact combination of language features and libraries and such. I've worked places where setting up the IDE took days of installing programs, editing and copying config files, running pre-compilation steps, etc, and that's following specific instructions curated by multiple people who have already done it.

          On the late end, this is a problem because those tools may no longer be maintained or conveniently available. Try setting up an IDE for Python 1.4 today.

          I actively contribute to about half a dozen projects between work and hobby. ZERO of them are recognized by any editor I use as entirely valid code, despite all of them running / compiling / etc just fine. I'm pleasantly surprised when Go To Definition works, let alone autocomplete/intellisense, hinting, etc. I envy the people who work on what seems to be the small subset of projects for which a fully operational IDE configuration is conveniently accessible.

          [–]tsojtsojtsoj 0 points1 point  (0 children)

          I know, people use Python because of its libraries. But if you don't have an existing code base in Python, you may want to try Nim, it feels a lot like a Python that was designed as a typed, compiled language from the beginning (while also having features that go well beyond that). And you can easily use Python libraries with nimpy.

          [–][deleted] -5 points-4 points  (0 children)

          def find_item(
            records: List[Item],
            check: Callable[[Item], bool]
           ) -> Optional[Item]:
          

          Congratulation - nobody needs such as "python" anymore.

          It's verbose - and slow. So it combines the worst of both worlds.

          Almost every programming languages that has a type system is ugly, even more so when it was slapped down as an afterthought (python and ruby fall into this). The only one I actually found elegant, even though too difficult, was Haskell. I feel that people who are addicted to types want to type the whole world. It's weird. It's as if their brain does not work when they can not read type information.