[HN Gopher] Pyrefly vs. Ty: Comparing Python's two new Rust-base...
       ___________________________________________________________________
        
       Pyrefly vs. Ty: Comparing Python's two new Rust-based type checkers
        
       Author : edwardjxli
       Score  : 375 points
       Date   : 2025-05-27 15:01 UTC (1 days ago)
        
 (HTM) web link (blog.edward-li.com)
 (TXT) w3m dump (blog.edward-li.com)
        
       | senkora wrote:
       | > ty, on the other hand, follows a different mantra: the gradual
       | guarantee. The principal idea is that in a well-typed program,
       | removing a type annotation should not cause a type error. In
       | other words: you shouldn't need to add new types to working code
       | to resolve type errors.
       | 
       | The gradual guarantee that Ty offers is intriguing. I'm
       | considering giving it a try based on that.
       | 
       | With a language like Python with existing dynamic codebases, it
       | seems like the right way to do gradual typing.
        
         | yoyohello13 wrote:
         | Unless you're doing greenfield, gradual typing is really the
         | only way. I've incorporated type hinting in several legacy
         | Python code bases with mypy and really the only sensible way is
         | to "opt-in" one module at a time. If pyrefly doesn't support
         | that I think its use will be pretty limited. Unless maybe they
         | are going for the llm code gen angle. I could see a very fast
         | and strict type checker being useful for llm generating python
         | scripts.
        
         | RandomBK wrote:
         | It reminds me of the early days of Typescript rollout, which
         | similarly focused on a smooth on-boarding path for existing
         | large projects.
         | 
         | More restrictive requirements (ie `noImplicitAny`) could be
         | turned on one at a time before eventually flipping the `strict`
         | switch to opt in to all the checks.
        
         | rendaw wrote:
         | Gradual typing means that an implicit "any" (unknown type)
         | anywhere in your code base is not an error or even a warning.
         | Even in critical code you thought was fully typed. Where you
         | mistakenly introduce a type bug and due to some syntax or
         | inference limits the type checker unexpectedly loses the plot
         | and tells you confidently "no problems in this file!"
         | 
         | I get where they're coming from, but the endgame was a huge
         | issue when I tried mypy - there was no way to actually
         | guarantee that you were getting any protection from types. A
         | way to assert "no graduality to this file, it's fully typed!"
         | is critical, but gradual typing is not just about migrating but
         | also about the crazy things you can do in dynamic languages and
         | being terrified of false positives scaring away the people who
         | didn't value static typing in the first place. Maybe calling it
         | "soft" typing would be clearer.
         | 
         | I think gradual typing is an anti-pattern at this point.
        
           | mmoskal wrote:
           | As mentioned in other comments - in TypeScript which follows
           | this gradual typing there is a number of flags to disable it
           | (gradually so to speak). No reason ty wouldn't do it.
        
           | belmont_sup wrote:
           | Responding to your gradual typing anti-pattern bit: Agree
           | that dynamic language behaviors can be extreme but it's also
           | easy to get into crazy type land. Putting aside a discussion
           | of type systems, teams can always add runtime checks like
           | pydantic to ensure your types match reality.
           | 
           | Sorbet (Ruby typechecker) does this where it introduces a
           | runtime checks on signatures.
           | 
           | Similarly in ts, we have zod.
        
             | MeetingsBrowser wrote:
             | > teams can always add runtime checks like pydantic to
             | ensure your types match reality.
             | 
             | That's the problem with bugs though, there's always
             | something that _could_ have been done to avoid it =)
             | 
             | Pydantic works great in specific places, like validating
             | user supplied data, but runtime checks as a replacement for
             | static type checkers are not really feasible.
             | 
             | Every caller would need to check that the function is being
             | called correctly (number and position of args, kwarg names,
             | etc) and every callee would need to manually validate that
             | each arg passed matches some expected type.
        
               | eternityforest wrote:
               | Pydantic also takes CPU time and doesn't do anything till
               | runtime.
               | 
               | Type checking is real time in the IDE and lets you fix
               | stuff before you waste fifteen minutes actually running
               | it.
        
               | Spivak wrote:
               | pydantic does have @validate_call for this use-case.
        
               | guappa wrote:
               | Profile it and see how much slower it gets :)
        
               | belmont_sup wrote:
               | To be clear, I myself prefer sound type systems.
               | 
               | But the reality is that teams have started with untyped
               | Python, Ruby, and Javascript, have been productive, and
               | now need to gradually add static types to remain
               | productive.
               | 
               | > Every caller would need to check that the function...
               | 
               | The nice part here is where the gradual part comes in. As
               | you are able to type more of your code, you're able to
               | move where you add your runtime validation, and
               | eventually you'll be able to move all validation to the
               | edges of your system.
        
           | dcreager wrote:
           | > Gradual typing means that an implicit "any" (unknown type)
           | anywhere in your code base is not an error or even a warning.
           | Even in critical code you thought was fully typed. Where you
           | mistakenly introduce a type bug and due to some syntax or
           | inference limits the type checker unexpectedly loses the plot
           | and tells you confidently "no problems in this file!"
           | 
           | This is a good point, and one that we are taking into account
           | when developing ty.
           | 
           | The benefit of the gradual guarantee is that it makes the
           | onboarding process less fraught when you want to start
           | (gradually) adding types to an untyped codebase. No one wants
           | a wall of false positive errors when you first start invoking
           | your type checker.
           | 
           | The downside is exactly what you point out. For this, we want
           | to leverage that ty is part of a _suite_ of tools that we 're
           | developing. One goal in developing ty is to create the
           | infrastructure that would let ruff support multi-file and
           | type-aware linter rules. That's a bit hand-wavy atm, since
           | we're still working out the details of how the two tools
           | would work together.
           | 
           | So we _do_ want to provide more opinionated feedback about
           | your code -- for instance, highlighting when implicit `Any`s
           | show up in an otherwise fully type-annotated function. But we
           | view that as being a _linter rule_ , which will likely be
           | handled by ruff.
        
             | eternityforest wrote:
             | Could there ever be a flag to turn off the gradual
             | guarantee and get stricter behavior?
        
               | ramses0 wrote:
               | "use strict";
        
             | genshii wrote:
             | This makes sense to me and is exactly what TypeScript does.
             | Implicit `any`s do not raise TypeScript errors (which, by
             | definition, is expected), but obviously that means if there
             | is an `any`, it's potentially unsafe. To deal with this,
             | you can turn on `noImplicitAny` or strict mode (which 99%
             | of projects probably have enabled anyway).
             | 
             | Difference here that strict mode is a tsc option vs. having
             | this kind of rule in the linter (ruff), but the end result
             | is the same.
             | 
             | Anyway, that was a long winded way of saying that ty or
             | ruff definitely needs its own version of a "strict" mode
             | for type checking. :)
        
             | pydry wrote:
             | You could give a score to different folders or files to
             | indicate a level of "type certainty" and allow people to
             | define failure thresholds.
        
           | josevalim wrote:
           | > Gradual typing means that an implicit "any" (unknown type)
           | anywhere in your code base is not an error or even a warning.
           | 
           | That depends on the implementation of gradual typing. Elixir
           | implements gradual set-theoretic types where dynamic types
           | are a range of existing types and can be refined for typing
           | violations. Here is a trivial example:                   def
           | example(x) do           {Integer.to_string(x),
           | Atom.to_string(x)}         end
           | 
           | Since the function is untyped, `x` gets an initial value of
           | `dynamic()`, but it still reports a typing violation because
           | it first gets refined as `dynamic(integer())` which is then
           | incompatible with the `atom()` type.
           | 
           | We also introduced the concept of strong arrows, which allows
           | dynamic and static parts of a codebase to interact without
           | introducing runtime checks and remaining sound. More
           | information here: https://elixir-
           | lang.org/blog/2023/09/20/strong-arrows-gradua...
        
             | _carljm wrote:
             | ty also implements gradual set-theoretic types, and can
             | represent "ranged" dynamic types (as intersections or
             | unions with Any/Unknown). We don't currently refine dynamic
             | type based on all uses, as suggested here, though we've
             | considered something very much like this for invariant
             | generics.
             | 
             | In your example, wouldn't `none()` be a type for `x` that
             | satisfies both `Integer.to_string(x)` and
             | `Atom.to_string(x)`? Or do you special-case `none()` and
             | error if it occurs?
        
               | josevalim wrote:
               | Oh, that's exciting to hear! I would love to exchange
               | notes and I know one of the lead researchers of set
               | theoretic types would love to learn more about your uses
               | too. If that sounds fun to you, you can find me on Gmail
               | (same username).
               | 
               | In our case, we implement a bidirectional system where
               | before applying x to Integer.to_string, we compute the
               | domain of Integer.to_string (which is integer) and pass
               | it up. If x is a dynamic type, then we refine it. So on
               | the first call, x refines to `dynamic & integer`, then we
               | apply it.
               | 
               | The second refinement fails because it becomes none, so
               | we discard it, but it means the application on
               | Atom.to_string will fail anyway. So yes, we check for
               | emptiness and discard none.
        
             | HelloNurse wrote:
             | How is this function definition (or maybe just its
             | parameter x) "untyped"? There is enough information to
             | deduce that the type of parameter x is empty and the type
             | of the function doesn't matter because there is an error.
             | 
             | If the body of the function contained only the first or the
             | second call, the verdict would have been that x is
             | respectively an Integer or an Atom and the type of the
             | function is the type of the contained expression.
        
               | josevalim wrote:
               | For us type inference is the same as type checking where
               | all parameters are given the dynamic type. So even if you
               | explicitly added a signature that said dynamic, we would
               | still find a violation, where others would not. The point
               | is that dynamic does not have to mean "anything goes".
        
           | tclancy wrote:
           | Yeah, I'm torn because, in my experience, gradual typing
           | means the team members who want it implement it in their code
           | and the others do not or are very lax in their typing. Some
           | way of swapping between gradual and strict would be nice.
        
           | rtpg wrote:
           | In code where you really want to have these guarantees you
           | turn on errors lke "no implicit any" in mypy and tighten the
           | restrictions on the files you care about.
           | 
           | You still have the "garbage in/garbage out" problem on the
           | boundaries but at the very least you can improve confidence.
           | And if you're hardcore... turn that on all over, turn off
           | explicit Any, write wrappers around all of your untyped
           | dependencies etc etc. You can get what you want, just might
           | be a lot of work
        
           | guappa wrote:
           | 15 seconds after doing "man mypy": --disallow-any-expr
           | 
           | Less than it took you to write all that.
        
         | tialaramex wrote:
         | Although I'm paid to write (among other things) Python and not
         | Rust, I would think of myself as a Rust programmer and to me
         | the gradual guarantee also makes most sense.
        
         | IshKebab wrote:
         | This is a big turnoff for me. Half the point of adding type
         | annotations to Python is to tame its error-prone dynamic
         | typing. I _want_ to know when I 've done something stupid, even
         | if it is technically allowed by Python itself.
         | 
         | Hopefully they'll add some kind of no-implicit-any or "strict"
         | mode for people who care about having working code...
        
       | paddy_m wrote:
       | I hope some typechecker starts doing serious supported notebook
       | integration. And integration for live coding, not just a batch
       | script to statically check your notebook. Finding errors with
       | typing before running a 1-60 minute cell is a huge win.
        
         | thelastbender12 wrote:
         | Do you use Jupyter notebooks in VSCode? It uses the same
         | pylance as regular python files, which actually gets annoying
         | when I want to write throwaway code.
        
           | ZeroCool2u wrote:
           | Anyone reading this, if you're like me and prefer the open
           | source version of VSCode where Microsoft disables Pylance,
           | I'd encourage you to try BasedPyright instead.
        
             | baggiponte wrote:
             | BasedPyright is much better than pyright! Must use.
        
         | tomtom1337 wrote:
         | I echo the other response here. You absolutely should switch to
         | using notebooks in VSCode with their static typenchecker.
         | Language Servers do exactly what you are wanting, with both
         | notebook integration and <<live coding>>.
        
       | dgroshev wrote:
       | I really wish they get first class Django support. Sadly, its ORM
       | architecture is impossible to type and impossible to change now.
       | Django is one of the most important use cases for Python, having
       | fast full type checking with Django is a dream, but it does
       | require some special casing from the type checker.
        
         | catlover76 wrote:
         | Isn't that largely on the shoulders of Django maintainers?
        
         | MeetingsBrowser wrote:
         | What makes the Django ORM impossible to type check?
        
           | zbentley wrote:
           | It uses a huge amount of what I'm terming "getattr bullshit":
           | many/most fields of ORM objects are only determined at
           | runtime (they're technically determinABLE at _early_ runtime
           | during Django initialization, but in practice are often not
           | actually visible via reflection until they are first used due
           | to lazy caching).
           | 
           | What fields are present and what types they have is extremely
           | non uniform: it depends heavily on ORM objects' internal
           | configuration and the way a given model class relates to
           | other models, including circular dependencies.
           | 
           | (And when I say "fields" here, I'm not only referring to data
           | fields; even simple models include many, many computed
           | method-like fields, complex lazily-evaluatable and
           | parametrizable query objects, fields whose types and behavior
           | change temporally or in response to far-distant settings, and
           | more).
           | 
           | Some of this complexity is inherent to what ORMs are as a
           | class of tool--many ORMs in all sorts of languages provide
           | developer affordances in the form of highly dynamic,
           | metaprogramming-based DSL-ish APIs--but Django really leans
           | in to that pattern more than most.
           | 
           | Add to that a very strong community tendency to lazily (as in
           | diligence, not caching) subclass ORM objects in ways that
           | shadow computed fields--and often sloppily override the logic
           | used to compute what fields are available and how they act--
           | and you have a very thorny problem space for type checkers.
           | 
           | I also want to emphasize that this isn't some rare Django
           | power-user functionality that is seldom used, nor is it
           | considered deprecated or questionable--these computed fields
           | are _the core API of the Django ORM_ , so not only are they a
           | moving target that changes with Django (and Django extension
           | module) releases, but they're also such a common kind of code
           | that even minor errors in attempts to type-check them will be
           | extremely visible and frustrating to a wide range of users.
           | 
           | None of that should be taken as an indictment of the Django
           | ORM's design (for the most part I find it quite good, and
           | most of my major issues with it have little to do with type
           | checking). Just trying to answer the question as directly as
           | possible.
        
           | rtpg wrote:
           | It's possible to write a Django typecheck shim using
           | descriptors. There's some annoying stuff on the edges though,
           | and for example if you are changing up fields in `__init__`
           | then those aren't going to show up in your types.
           | 
           | Ultimately you can get typing for the usual cases, but it
           | won't be complete because you can outright change the shape
           | of your models in Django at runtime (actions that aren't type
           | safe of course)
        
         | the__alchemist wrote:
         | I'm with you. I regularly hit errors with its ORM that make me
         | think: "I thought I cast this class of errors aside years ago".
         | I go over my query code very carefully, since the MK-1 eyeball
         | is important here for spotting typos etc.
         | 
         | (I'm not commenting on it being possible or not to fix; but the
         | current status)
        
       | kuratkull wrote:
       | > my_list = [1, 2, 3]
       | 
       | > pyrefly, mypy, and pyright all assume that
       | my_list.append("foo") is a typing error, even though it is
       | technically allowed (Python collections can have multiple types
       | of objects!)
       | 
       | > If this is the intended behavior, ty is the only checker that
       | implicitly allows this without requiring additional explicit
       | typing on my_list.
       | 
       | EDIT: I didn't intend my comment to be this sharp, I am actually
       | rooting for ty to succeed :)
       | 
       | ORIGINAL: I am strongly against ty behaviour here. In production
       | code you almost always have single type lists and it is critical
       | that the typechecker assumes this, especially if the list already
       | has same-type _literal_ items.
       | 
       | The fact that Python allows this has no bearing at all. To me
       | having list[int | str] implicitly allowed by the typechecker
       | seems like optimizing for beginner-level code.
        
         | ameliaquining wrote:
         | I don't think it's optimizing for beginner-level code, I think
         | it's optimizing for _legacy_ code. Introducing a type checker
         | to a large existing untyped codebase is a big lift, but becomes
         | less of one if almost all existing code is accepted.
        
           | luoc wrote:
           | Well then support an option to enable that kind behaviour?
           | Make it an explicit decision by the devs. I think running in
           | a type error and then adding an exception to your config is
           | safer than silently pass and only learn about the mixed types
           | in a production bug
        
             | fastasucan wrote:
             | The tool doesn't have a version number yet, its in preview.
             | Chill.
        
             | shoeb00m wrote:
             | I think this should be handled by a type assisted linter
             | not typechecker.
             | 
             | Imo a type checker in a dynamic language should is
             | primarily there to avoid runtime errors. In a list with
             | multiple types the typechecker should instead force you to
             | check the type before using an element in that list.
             | 
             | If you want static types python is the wrong language
        
         | SkiFire13 wrote:
         | > and it is critical that the typechecker assumes this
         | 
         | Why is it critical though? If having a `list[int]` was a
         | requirement I would expect a type error where that's explicit.
        
           | kuratkull wrote:
           | Because to me this seems like a fantastic example of a highly
           | possible mistake that a typechecker _should_ catch. Without
           | defined types in this situation a couple of things could
           | happen: 1) it gets printed or passed to some other Any method
           | and the typechecker never yells at you and it crashes in
           | production 2) the typechecker catches the error somewhere
           | long down the line and you have to backtrack to find where
           | you might be appending a str to a list[int].
           | 
           | Instead it could mark it as an error (as all the other
           | checkers do), and if that's what the user really intended
           | they can declare the type as list[str | int] and everything
           | down the line is checked correctly.
           | 
           | So in short, this seems like a great place to start pushing
           | the user towards actually (gradually) typing their code, not
           | just pushing likely bugs under the rug.
        
         | bb88 wrote:
         | I don't know. I would argue that since type checking in python
         | is optional, the type checkers shouldn't care unless the
         | programmer cares. A more interesting case would be
         | my_list.append(2.45) or my_list.append(Decimal("2.0")). Those
         | cases would be "numbers" not just "ints".
         | 
         | In the real world, a row of CSV data is not type checked -- and
         | the world hasn't pushed the spreadsheet industry to adopt typed
         | CSV data.
        
         | gostsamo wrote:
         | It depends on what happens with the list after that. Are there
         | int specific operations applied or it is just printed? What if
         | it is fed into objects with a str attribute where the ints
         | could be cast to str?
        
         | dcreager wrote:
         | > I am strongly against ty behaviour here.
         | 
         | [ty developer here]
         | 
         | Please note that ty is not complete!
         | 
         | In this particular example, we are tripped up because ty does
         | not do anything clever to infer the type of a list literal. We
         | just infer `list[Unknown]` as a placeholder, regardless of what
         | elements are present. `Unknown` is a gradual type (just like
         | `Any`), and so the `append` call succeeds because _every_ type
         | is assignable to `Unknown`.
         | 
         | We do have plans for inferring a more precise type of the list.
         | It will be more complex than you might anticipate, since it
         | will require "bidirectional" typing to take into account what
         | you're doing with the list in the surrounding context. We have
         | a tracking issue for that here: https://github.com/astral-
         | sh/ty/issues/168
        
           | kuratkull wrote:
           | I hope I didn't come off as angry or anything, I was just
           | very surprised by the behaviour :)
           | 
           | I am talking from some experience as I had to convert circa
           | 40k lines of untyped code (dicts passed around etc) to fully
           | typed. IIRC this behaviour would have masked a lot of bugs in
           | my situation. (I relied on mypy at first, but migrated to
           | pyright about 1/4 in).
           | 
           | But otherwise it's good to hear that this is still in
           | progress and I wish the project the best of luck.
        
             | dcreager wrote:
             | > I hope I didn't come off as angry or anything, I was just
             | very surprised by the behaviour
             | 
             | Not at all! :-) Just wanted to clarify for anyone else
             | reading along
        
           | ijustlovemath wrote:
           | Have you all looked at how Pyrefly does it, or are your
           | methods incompatible?
        
             | dcreager wrote:
             | Well ours is not yet implemented, so it's too early to say
             | whether they're compatible. :-)
             | 
             | But less snarkily, we do talk to them often (and the
             | authors of other tools like mypy and pyright) to make sure
             | we aren't introducing gross incompatibilities between the
             | different type checkers. When there are inconsistencies, we
             | want to make sure they are mindful rather than accidental;
             | for good reasons; spec-compliant; and well documented.
        
           | Epa095 wrote:
           | So, how does that relate to this quote from the article?
           | >ty, on the other hand, follows a different mantra: the
           | gradual guarantee. The principal idea is that in a well-typed
           | program, removing a type annotation should not cause a type
           | error. In other words: you shouldn't need to add new types to
           | working code to resolve type errors.
           | 
           | It seems like `ty`'s current behaviour is compatible with
           | this, but changing it won't (unless it will just be
           | impossible to type a list of different types).
        
             | rtpg wrote:
             | You could have a `list[int | str]` but then you need to
             | check the type of the elements in the list on usage to see
             | if they are `int` or `str` (if you are actually trying to
             | put the elements into a place that requires an `int` or
             | requires a `str` but wouldn't accept an `int | str`...).
             | 
             | If your code doesn't do that then your program isn't well
             | typed according to Python's typing semantics... I think.
             | 
             | So you can have lists of multiple types, but then you get
             | consequences from that in needing type guards.
             | 
             | Of course you still have stuff like `tuple[int, int, int,
             | str]` to get more of the way there. Maybe one day we'll get
             | `FixedList[int, int, int, str]`....
        
         | lacker wrote:
         | The problem with the pyrefly behavior is that if you have a
         | large codebase that isn't using any sort of Python
         | typechecking, you can't just adopt this tool incrementally. You
         | have to go fix up all of these issues. So you need to get
         | widespread support for this migration.
         | 
         | For an internal tool at Meta, this is fine. Just make all your
         | engineers adopt the style guide.
         | 
         | For introducing a tool gradually at an organization where this
         | sort of change isn't one of the top priorities of engineering
         | leadership, being more accepting is great. So I prefer the way
         | ty does this, even though in my own personal code I would like
         | my tool to warn me if I mix types like this.
        
         | zem wrote:
         | I am strongly for ty's behaviour here. working python code
         | should not raise type errors unless the user explicitly opts in
         | to a more static subset of the language by adding type
         | annotations.
        
         | fastasucan wrote:
         | >The fact that Python allows this has no bearing at all. To me
         | having list[int | str] implicitly allowed by the typechecker
         | seems like optimizing for beginner-level code.
         | 
         | Yes, lets base our tooling on your opinion rather what is
         | allowed in python.
        
         | dmurray wrote:
         | list[int | str] might usually be a mistake, but what about
         | 
         | my_list = [BarWidget(...), FooWidget(...)] ?
         | 
         | my_list.append(BazWidget(...))
         | 
         | my_list.append(7)
         | 
         | Wouldn't it be nice if the type checker could infer the type
         | hint there, which is almost certainly intended to be
         | list[Widget], and allow the first append and flag the second
         | one?
        
       | melodyogonna wrote:
       | I think I prefer Pyrefly's stronger type inference. It can be a
       | pain on Projects with a lot of dynamism, but I'll personally make
       | the tradeoff
        
       | wejick wrote:
       | Ty will have lower barrier of entry, similar to the early days of
       | Typescript.
       | 
       | I'm curious to see which way the community will lean to.
        
       | kombine wrote:
       | Are any of these already useful as LSPs for code editors such as
       | Neovim? I run pyright in my Neovim config but I could certainly
       | use something faster.
        
         | dcreager wrote:
         | ty is definitely _not_ ready to be a pyright replacement yet.
         | But it is usable as an LSP for simple things like go to
         | definition, and deeper LSP features are on the roadmap for the
         | eventual beta and GA releases.
         | 
         | https://github.com/astral-sh/ty/blob/main/docs/README.md#oth...
        
           | boomer_joe wrote:
           | What's the plan for ruff? Will it be part of ty one day?
        
             | dcreager wrote:
             | The current plan is that they will remain separate tools,
             | but will work together nicely if you are using both. For
             | instance, we want to add type-aware and multi-file lints to
             | ruff at some point down the line.
        
         | alfalfasprout wrote:
         | Not yet tbh. I would realistically expect at least a year or
         | more before you can expect any kind of parity with eg;
         | pyright/basedpyright. Type checkers are hard and have a long
         | tail of functionality that must be implemented.
        
         | djrj477dhsnv wrote:
         | What kind of codebases cause performance problems?
         | 
         | I've been using pyright with neovim for years and have never
         | experienced any kind of noticeable lag.
        
       | dcreager wrote:
       | [ty developer here]
       | 
       | We are happy with the attention that ty is starting to receive,
       | but it's important to call out that both ty and pyrefly are still
       | incomplete! (OP mentions this, but it's worth emphasizing again
       | here.)
       | 
       | There are definitely examples cropping up that hit features that
       | are not yet implemented. So when you encounter something where
       | you think what we're doing is daft, please recognize that we
       | might have just not gotten around to that yet. Python is a big
       | language!
        
         | zem wrote:
         | surfacing revealed types as `@TODO` made me laugh, but thinking
         | about it it's actually a pretty neat touch!
        
           | dcreager wrote:
           | It really helps in our mdtests, because then we can assert
           | that not-implemented things are currently wrong but for the
           | right reasons!
        
         | echelon wrote:
         | Totally orthogonal question, but since you're deep in that side
         | of Rust dev -
         | 
         | The subject of a "scripting language for Rust" has come up a
         | few times [1]. A language that fits nicely with the syntax of
         | Rust, can compile right alongside rust, can natively import
         | Rust types, but can compile/run/hot reload quickly.
         | 
         | Do you know of anyone in your network working on that?
         | 
         | And modulus the syntax piece, do you think Python could ever
         | fill that gap?
         | 
         | [1] https://news.ycombinator.com/item?id=44050222
        
           | mdaniel wrote:
           | > And modulus the syntax piece, do you think Python could
           | ever fill that gap?
           | 
           | I would never ever want a full fledged programming language
           | to build type checking plugins, and doubly so in cases where
           | one expects the tool to run in a read-write context
           | 
           | I am not saying that Skylark _is_ the solution, but it 's
           | sandboxed mental model aligns with what I'd want for such a
           | solution
           | 
           | I get the impression the wasm-adjacent libraries could also
           | help this due to the WASI boundary already limiting what
           | mutations it is allowed
        
           | julienfr112 wrote:
           | Most of the time, you want the type to be dynamic in a
           | scripting langage, as you don't want to expose the types to
           | the user. With this in mind, rhai and rune are pretty good.
           | On the python front, there was also the pyoxidizer thing, put
           | it seems dead.
        
             | tadfisher wrote:
             | If the language has types at all, they're exposed to the
             | user, even if the time of exposure is a runtime failure. I
             | suspect you want inferred types, which can be had in
             | statically-typed languages.
        
             | echelon wrote:
             | Not necessarily!
             | 
             | These are the strong vs weak, static vs dynamic axes.
             | 
             | You probably want strong, but dynamic typing. eg., a
             | function explicitly accepts only a string and won't accept
             | or convert a float into a string implicitly or magically.
             | 
             | You're free to bind or rebind variables to anything at any
             | time, but using them in the wrong way leads to type errors.
             | 
             | JavaScript has weak dynamic typing.
             | 
             | Python has strong dynamic typing (though since types aren't
             | annotated in function definitions, you don't always see it
             | until a type is used in the wrong way at the leaves of the
             | AST / call tree).
             | 
             | Ruby has strong dynamic typing, but Rails uses
             | method_missing and monkey patching to make it weaker though
             | lots of implicit type coercions.
             | 
             | C and C++ have weak static typing. You frequently deal with
             | unstructured memory and pointers, casting, and implicit
             | coercions.
             | 
             | Java and Rust have strong static typing.
        
           | dcreager wrote:
           | I don't know that I'd want the scripting language to be
           | compiled, for reasons that are outside the scope of this
           | reply. So removing that constraint, the coolest thing I've
           | seen in this space recently is kyren's Piccolo:
           | 
           | https://kyju.org/blog/piccolo-a-stackless-lua-interpreter/
        
           | tadfisher wrote:
           | There's Gluon, which doesn't share Rust's syntax but does
           | have a Hindley-Milner-based type system and embeds pretty
           | seamlessly in a Rust program.
           | 
           | https://github.com/gluon-lang/gluon
        
             | mdaniel wrote:
             | Please no                 let { (*>), (<*), wrap } =
             | import! std.applicative
        
         | flakes wrote:
         | Really loving those markdown style tests. I think it's a really
         | fantastic idea that allows the tests to easily act as
         | documentation too.
         | 
         | Can you explain how you came up with this solution? Rust docs
         | code-examples inspired?
        
           | hathawsh wrote:
           | That concept has been formalized as part of the Python
           | standard library.
           | 
           | https://docs.python.org/3/library/doctest.html
        
             | flakes wrote:
             | Ah very nice! Did not realize this was a part of the
             | standard library!
        
               | lmz wrote:
               | It's been there since Python v2.1
               | https://docs.python.org/release/2.1/lib/module-
               | doctest.html
        
               | sakesun wrote:
               | Here is the first announce of doctest I can find (1999) I
               | think it's Python 1.5 time.
               | 
               | https://groups.google.com/g/comp.lang.python/c/DfzH5Nrt05
               | E/m...
        
           | indiosmo wrote:
           | Elixir has this.
           | 
           | https://hexdocs.pm/elixir/main/docs-tests-and-with.html
        
           | __mharrison__ wrote:
           | I use this in my books to show the output but also to "test"
           | that the code found in my books actually works.
        
           | _whiteCaps_ wrote:
           | I love doctest as it works so well with a REPL but
           | unfortunately it hasn't really gained traction anywhere I've
           | seen.
        
         | davedx wrote:
         | I am very interested in both of these. Coming from the
         | TypeScript world I'm really interested in the different
         | directions (type inference or not, intersections and type
         | narrowing...). As a Python developer I'm wearily resigned to
         | there being 4+ python type checkers out there, all of which
         | behave differently. How very python...
         | 
         | Following these projects with great interest though. At the end
         | of the day, a good type checker should let us write code faster
         | and more reliably, which I feel isn't yet the case with the
         | current state of the art of type checking for python.
         | 
         | Good luck with the project!
        
       | muglug wrote:
       | I've built a few typecheckers (in different languages) that hew
       | closer to Pyrefly's behaviour than Ty's behaviour.
       | 
       | If you have a large codebase that you want to be typesafe,
       | Pyrefly's approach means writing far fewer type annotations
       | overall, even if the initial lift is much steeper.
       | 
       | Ty effectively has noImplicitAny set to false.
        
       | NeutralForest wrote:
       | Nice, I'm using basedpyright right now, both as a type checker in
       | my IDE as well as in GitHub actions. It's good and mainly does
       | what I want.
       | 
       | I'm not very fond of mypy as it struggles even with simple typing
       | at times.
        
       | modeless wrote:
       | As they're described here, Pyrefly's design choices make more
       | sense to me. I like the way Typescript does type inference and it
       | seems like Pyrefly is closer to that. Module-level incrementalism
       | also seems like a good tradeoff. Fine-grained incrementalism on a
       | function level seems like overkill. Performance should be good
       | enough that it's not required.
        
       | vintagedave wrote:
       | What was not immediately obvious to me (but should have been) is
       | that these are dev-time type checkers -- I think. (I think, both
       | from from the github descriptions which focus heavily on editing,
       | and from the article.) That's really useful because type
       | inference is lacking, to me, in-editor. I tend to ask Copilot:
       | 'add type annotations'.
       | 
       | So in complement to this can I share my favorite _run-time_ type
       | checker? Beartype: this reads your type annotations (ie I see
       | this is where Pyrefly and Ty come in), and enforces the types at
       | runtime. It is blazingly fast, as in, incredibly fast. I use it
       | for all my deployed code.
       | 
       | https://beartype.readthedocs.io/en/latest/
       | 
       | I suspect either of Pyrefly or Ty will be highly complementary
       | with Beartype in terms of editor additions, and then runtime
       | requirements.
       | 
       | The docs also have a great sense of humour.
        
         | maleldil wrote:
         | beartype is great, but I only find it useful at the edges.
         | Runtime checks aren't needed if you have strict typing
         | throughout the project. On a gradually-typed codebase, you can
         | use beartype (e.g. is_bearable) to ensure the data you're
         | ingesting has the proper type. I usually use it when I'm
         | dealing with JSON types.
        
           | vintagedave wrote:
           | Why isn't it necessary? Do you mean that with edit-time type
           | checking, you can catch all errors, so no need for runtime
           | verification the edit-time type decls match?
           | 
           | What about interacting with other libraries?
        
             | maleldil wrote:
             | If you have strict static type checking, type errors can't
             | creep in, so you don't need runtime checking. pyright (type
             | checker) will tell you when a runtime check is redundant.
             | For example, if you already have `var` annotated (or
             | inferred) as `str`, then `if isinstance(var, str)` is
             | statically guaranteed to be true.
             | 
             | Of course, that's only if you trust all the types in your
             | code. You still have escape hatches, such as Any and cast,
             | that can break this guarantee. There are lints (from ruff)
             | and pyright options to help with this. Concerning external
             | libraries, I either use libraries that are 100% typed
             | (which is common these days), or write my own type-safe
             | wrappers around the others.
        
       | suspended_state wrote:
       | I am not well versed in python programming, this is just my
       | opinion as an outsider.
       | 
       | For anyone interested in using these tools, I suggest reading the
       | following:
       | 
       | https://www.reddit.com/r/Python/comments/10zdidm/why_type_hi...
       | 
       | That post should probably be taken lightly, but I think that the
       | goal there is to understand that even with the best typing tools,
       | you will have troubles, unless you start by establishing good
       | practices.
       | 
       | For example, Django is large code base, and if you look at it,
       | you will observe that the code is consistent in which features of
       | python are used and how; this project passes the stricter type
       | checking test without troubles. Likewise, Meta certainly has a
       | very large code base (why develop a type checker otherwise?), and
       | they must have figured out that they cannot let their programmers
       | write code however they like; I guess their type checker is the
       | stricter one for that reason.
       | 
       | Python, AFAIK, has many features, a very permissive runtime, and
       | perhaps (not unlike C++) only some limited subset should be used
       | at any time to ensure that the code is manageable. Unfortunately,
       | that subset is probably different depending on who you ask, and
       | what you aim to do.
       | 
       | (Interestingly, the Reddit post somehow reminded me of the
       | hurdles Rust people have getting the Linux kernel guys to accept
       | their practice: C has a much simpler and carefree type system,
       | but Rust being much more strict rubs those C guys the wrong way).
        
         | mhh__ wrote:
         | At this point I'm fairly convinced that the effort one would
         | spend trying to typecheck a python program is better spent
         | migrating away from python into a language that has a proper
         | type system, then using interop so you can still have the
         | bits/people that need python be in python.
         | 
         | Obviously that isn't always possible but you can spend far too
         | long trying to make python work.
        
           | davedx wrote:
           | Unfortunately with us being in the middle of the AI hype
           | cycle, everyone and their dog is currently busy migrating
           | _to_ python.
        
             | hu3 wrote:
             | I don't see why AI hype means more Python code.
             | 
             | State of the art AI models are all closed source and
             | accessible through an API anyways. APIs that any language
             | can easily access.
             | 
             | AAa for AI model development in itself, yes it's mostly
             | Pyython, but niche.
        
               | davedx wrote:
               | Because everyone and their dog thinks they need to be
               | _ready to develop their own models, in python_.
               | 
               | This is honestly a thing, at least in the startup world.
        
               | mzl wrote:
               | I think you are underestimating the massive amounts of
               | Python code that is built around these things. Also, a
               | lot of businesses are not really interested in using an
               | API for an LLM, instead they will modify and fine-tune
               | their own models and deploy in their own data-centers
               | (virtual or physical), and that means even more Python
               | code.
               | 
               | Sure, a system that only relies on token factory LLM APIs
               | can be written in any language, but that is not the full
               | width and breadth of the AI hype.
        
               | hu3 wrote:
               | > Also, a lot of businesses are not really interested in
               | using an API for an LLM, instead they will modify and
               | fine-tune their own models and deploy in their own data-
               | centers
               | 
               | You realize model training cost millions right? "a lot of
               | businesses" doesn't pass sniff test here.
               | 
               | I'm not even counting the large swaths of data required
               | to train. And the expensive specialists.
               | 
               | And then you'll have to retrain outdated models every so
               | often.
               | 
               | There's a reason that AI has only a handful of players
               | delivering SoTA models and these players are all worth
               | $5B+.
        
             | adamors wrote:
             | I'd be surprised if _anyone_ is migrating server code to
             | Python because of AI.
        
           | J_Shelby_J wrote:
           | Six month into learning to build a modern python app, with
           | linters, type systems, tests, venvs, package managers, etc...
           | I realized that the supposed difficulty of rust is
           | drastically less than coming to speed and then keeping up
           | with the python "at scale" ecosystem.
        
             | mdaniel wrote:
             | My strong suspicion is that such a story depends a great
             | deal upon the personalities of the developers much more
             | than any {tool chaos + type chaos} --- {new syntax +
             | lifespan annotations} spectrum
        
           | bjackman wrote:
           | I think you're forgetting how easy type annotation is.
           | 
           | I occasionally spend like 2h working on some old python code.
           | I will spend say 15 minutes of that time adding type
           | annotations (sometimes requires some trivial refactoring).
           | This has an enormous ROI, the cost is so low and the benefit
           | is so immediate.
           | 
           | In these cases migrating code to a proper language and
           | figuring out interop is not on my radar, it would be insane.
           | So having the option to get some best-effort type safety is
           | absolutely fantastic.
           | 
           | I can definitely see your point, it's a useful analysis for
           | projects under heavy development. But if you have a big
           | Python codebase that basically just works and only sees
           | incremental changes, adding type annotations is a great
           | strategy.
        
           | guappa wrote:
           | If you do that you need to compile, which means you can't
           | just distribute a text file with your python program. You
           | need a build infrastructure for every python version, every
           | architecture and every OS.
           | 
           | Have fun with that!
        
           | rtpg wrote:
           | I don't understand this point at all. I've worked on Django
           | codebases which have a huge set of typing problems... and
           | while it's not 100% I get a lot of value out of type
           | checking.
           | 
           | You annotate enough functions and you get a really good
           | linter out of it!
        
           | ramraj07 wrote:
           | If you're supposedly good at software and you spent too long
           | trying to make python work consider the possibility that
           | you're not good at software?
           | 
           | Python has flaws and big ones at that, but there's a reason
           | it's popular. Especially with tools like pydantic and fastapi
           | and uv (and streamlit) you can do insane things in hours what
           | would take weeks and months before. Not to mention how good
           | AI is at generating code in these frameworks. I especially
           | like typing using pydantic, any method is now able to dump
           | and load data from files and dbs and you get extremely terse
           | validated code. Modern IDEs also make quick work of
           | extracting value even from partially typed code. I'd suggest
           | you just open your mind up to imperfect things and give them
           | a shot.
        
         | kccqzy wrote:
         | > Python, AFAIK, has many features, a very permissive runtime,
         | and perhaps (not unlike C++) only some limited subset should be
         | used at any time to ensure that the code is manageable.
         | Unfortunately, that subset is probably different depending on
         | who you ask, and what you aim to do.
         | 
         | I'll get started on the subset of Python that I personally do
         | not wish to use in my own codebase: meta classes, descriptors,
         | callable objects using __call__, object.__new__(cls), names
         | trigger the name mangling rules, self.__dict__. In my opinion,
         | all of the above features involve too much magic and hinder
         | code comprehension.
        
           | kstrauser wrote:
           | There's a time and a place for each of them:
           | 
           | * Meta classes: You're writing Pydantic or an ORM.
           | 
           | * Descriptors: You're writing Pydantic or an ORM.
           | 
           | * Callable objects: I've used these for things like making
           | validators you initialize with their parameters in one place,
           | then pass them around so other functions can call them. I'd
           | probably just use closures if at all possible now.
           | 
           | * object.__new__: You're writing Pydantic or an ORM.
           | 
           | * Name mangling: I'm fine with using _foo and __bar where
           | appropriate. Those are nice. Don't ever, _ever_ try to de-
           | mangle them or I 'll throw a stick at you.
           | 
           | * self.__dict__: You're writing Pydantic or an ORM, although
           | if you use this as shorthand for "doing things that need
           | introspection", that's a useful skill and not deep wizardry.
           | 
           | Basically, you won't need those things 99.99% of the time. If
           | you think you do, you probably don't. If you're absolutely
           | certain you do, you might. It's still good and important to
           | understand what they are, though. Even if you never write
           | them yourself, at some point you're going to want to figure
           | out why some dependency isn't working the way you expected,
           | and you'll need to read and know what it's doing.
        
             | kccqzy wrote:
             | > Basically, you won't need those things 99.99% of the time
             | 
             | That's kind of my point. If you don't need a language
             | feature 99.99% of the time perhaps it is better to cut it
             | out from your language altogether. Well unless your
             | language is striving to have the same reputation as C++. In
             | Python's case here's a compromise: such features can only
             | be used in a Python extension in C code, signifying their
             | magic nature.
        
               | orbital223 wrote:
               | I don't need my car's airbags 99.99% of the time.
        
               | mixmastamyk wrote:
               | A lot of people want pydantics and orms.
        
               | maleldil wrote:
               | I think it's fine to have those if it makes API design
               | and better. In my mind, there's "code you should write"
               | and there's "code only libraries should write".
        
             | guappa wrote:
             | I never understood why pydantic reimplemented attrs, but
             | doing it much slower, instead of just using attrs.
        
           | bbkane wrote:
           | You should try Go!
        
             | guappa wrote:
             | It should be banned by the geneva convention.
        
         | mjr00 wrote:
         | The top comment in that post shuts down the whole nonsense
         | pretty quickly and firmly:
         | 
         | > If you have a super-generic function like that and type
         | hinting enforced, you just use Any and don't care about it.
         | 
         | It's a stupid example, but even within the context of a
         | `slow_add` function in a library: maybe the author originally
         | _never even thought_ people would pass in non-numeric values,
         | so in the next version update instead of a hardcoded
         | `time.sleep(0.1)` they decide to `time.sleep(a  / b)`. Oops,
         | now it crashes for users who passed in strings or tuples! If
         | only there were a way to declare that the function is only
         | intended to work with numeric values, instead of forcing
         | yourself to provide backwards compatibility for users who used
         | that function in unexpected ways that happened to work.
         | 
         | IMO: for Python meant to run non-interactively with any sort of
         | uptime guarantees, type checking is a no-brainer. You're
         | actively making a mistake if you choose to not add type
         | checking.
        
           | notatallshaw wrote:
           | As the author of that post, I'd like to point out the example
           | was meant to be stupid.
           | 
           | The purpose was to show different ideologies and expectations
           | on the same code don't work, such as strict backwards
           | compatibilities, duck typing, and strictly following linting
           | or type hinting rules (due to some arbitrary enforcement).
           | Although re-reading it now I wish I'd spent more than an
           | evening working on it, it's full of issues and not very
           | polished.
           | 
           | > If you have a super-generic function like that and type
           | hinting enforced, you just use Any and don't care about it.
           | 
           | Following the general stupidness of the post: they are now
           | unable to do that because a security consultant said they
           | have to enable and can not break RUFF rule ANN401:
           | https://docs.astral.sh/ruff/rules/any-type/
        
             | mjr00 wrote:
             | > Following the general stupidness of the post: they are
             | now unable to do that because a security consultant said
             | they have to enable and can not break RUFF rule ANN401:
             | https://docs.astral.sh/ruff/rules/any-type/
             | 
             | Okay, then your function which is extremely generic and
             | needs to support 25 different use cases needs to have an
             | insane type definition which covers all 25 use cases.
             | 
             | This isn't an indictment of the type system, this is an
             | indictment of bad code. Don't write functions that support
             | hundreds of input data types, most of which are unintended.
             | Type systems help you avoid this, by the way.
        
               | notatallshaw wrote:
               | > Don't write functions that support hundreds of input
               | data types
               | 
               | But by it's nature, duck typing supports an unbounded
               | number of input types and is what Python was built on.
               | 
               | You've already decided duck typing is wrong and strict
               | type adherence is correct, which is fine, but that
               | doesn't fit the vast history of Python code, or in fact
               | many of the core Python libraries.
        
               | mjr00 wrote:
               | > But by it's nature, duck typing supports an unbounded
               | number of input types and is what Python was built on.
               | 
               | You're trying to shove a square peg into a round hole.
               | It's not about right or wrong. Either you _want_ your
               | function to operate on any type, attempt to add the two
               | values (or perform any operation which may or may not be
               | supported, i.e. duck typing), and throw an runtime error
               | if it doesn 't work--in which case you can leave it
               | untyped or use `Any`--or you want stronger type safety
               | guarantees so you can validate before runtime that nobody
               | is calling your method with incorrect arguments, in which
               | case you have to represent the types which you accept
               | somehow.
               | 
               | If you want to have a method that's fully duck typed,
               | you're _supposed_ to use `Any`. That 's exactly why it
               | exists. Inventing contrived scenarios about how you can't
               | use `Any` is missing the point. It's like complaining C
               | doesn't work if you're not allowed to use pointers.
               | 
               | You're right that historically Python code was written
               | with duck typing in mind, but now even highly flexible
               | libraries like Pandas have type definition support. The
               | ecosystem is way different from even 5-6 years ago, I
               | can't think of any well-known libraries which don't have
               | good typing support by now.
        
               | sevensor wrote:
               | Duck typing is great and Python's type system has
               | powerful support for it. You can for instance restrict a
               | function to only objects with a frobnicate() method,
               | without in any way constraining yourself on which
               | implementation you accept. Type checking plus duck typing
               | is very precise and powerful, and it helps me sleep at
               | night.
        
             | the_af wrote:
             | But there was a conceivable way (maybe not in Python) to
             | make a `slow_add` function very generic, yet only be
             | defined over structures where any conceivable `+` operation
             | is defined.
             | 
             | You just have to say the type implements Semigroup.
             | 
             | Yes, this would work if the arguments are lists, or
             | integers, or strings. And it won't pass the typecheck for
             | arguments that are not Semigroups.
             | 
             | It may not work with Python, but only because it's
             | designers weren't initially interested in typechecking.
        
               | sevensor wrote:
               | Challenge accepted.                   from dataclasses
               | import dataclass         from typing import Protocol,
               | Self, TypeVar              class Semigroup(Protocol):
               | def __add__(self, other: Self) -> Self:
               | ...              T = TypeVar("T", bound=Semigroup)
               | def join_stuff(first: T, *rest: T) -> T:
               | accum = first             for x in rest:
               | accum += x             return accum
               | @dataclass         class C:             x: int
               | @dataclass         class D:             x: int
               | def __add__(self, other: Self) -> Self:
               | return type(self)(self.x + other.x)
               | @dataclass         class E:             x: int
               | def __add__(self, other: Self) -> Self:
               | return type(self)(self.x + other.x)              _:
               | type[Semigroup] = D         _ = E              def doit()
               | -> None:             print(join_stuff(1,2,3))
               | print(join_stuff((1,), tuple(), (2,)))
               | print(join_stuff("a", "b", "c"))
               | print(join_stuff(D(1), D(2)))
               | print(join_stuff(D(1), 3))             print(D(1) + 3) #
               | caught by mypy             print(D(1) + E(3)) # caught by
               | mypy             print(join_stuff(1,2,"a")) # Not caught
               | by mypy             print(join_stuff(C(1), C(2))) #
               | caught by mypy         doit()
               | 
               | Now, this doesn't _quite_ work to my satisfaction. Mypy
               | lets you freely mix and match values of incompatible
               | types, and I don 't know how to fix that. Basically, if
               | you directly try to add a D and an int, mypy will yell at
               | you, but there's no way I've found to insist that the
               | arguments to join_stuff, in addition to being Semigroups,
               | are all of the compatible types. It looks like mypy is
               | checking join_stuff as if Semigroup were a concrete
               | class, so once you're inside join_stuff, the actual types
               | of the arguments become irrelevant.
               | 
               | However, it will correctly tell you that it can't accept
               | arguments that don't define addition at all, and that's
               | better than nothing.
        
               | the_af wrote:
               | Pretty cool that you got this far though!
               | 
               | I think at this point one starts to fight against Python,
               | which wasn't designed with this in mind. But cool
               | nonetheless.
        
             | ramraj07 wrote:
             | Stupid is okay. _Nonsense_ is not. Your example was
             | nonsense, it was absurd. The moment I saw the first example
             | I was like, this should be add_ints and should only take
             | ints.
             | 
             | Imagine I say "the human body is dumb! Here's an example:
             | if I stab myself, it bleeds!" Like is that stupid or
             | absurd?
        
               | notatallshaw wrote:
               | And yet, some Python users insistent on type hinting very
               | dynamic Python code while trying to keep how dynamic it
               | is.
        
           | lexicality wrote:
           | One thing that post does do though is very clearly highlight
           | the difference between Python's type system and say ...
           | TypeScript's.
           | 
           | TypeScript's goal is to take a language with an unhinged duck
           | type system that allows people to do terrible things and then
           | allow you to codify and lock in all of those behaviours
           | exactly as they're used.
           | 
           | Mypy (and since it was written by GVM and codified in the
           | stdlib by extension Python and all other typecheckers)'s goal
           | is to take a language with an unhinged duck type system that
           | allows people to do terrible things and then pretend that
           | isn't the case and enforce strict academic rules and
           | behaviours that don't particularly care about how real people
           | write code and interact with libraries.
           | 
           | If you include type hints from the very beginning than you
           | are forced to use the very limited subset of behaviours that
           | mypy allow you to codify and everything will be "fine".
           | 
           | If you try to add type hints to a mature project, you will
           | scream with frustration as you discover how many parts of the
           | codebase literally cannot be represented in the extremely
           | limited type system.
        
         | fastasucan wrote:
         | Can you share a little bit about what makes you form opinions
         | when you are not even using the language? I think its
         | fascinating how especially discussions about typing makes
         | people shake their fists against a language they don't even use
         | - and like your post make up some contrived example.
         | 
         | >I think that the goal there is to understand that even with
         | the best typing tools, you will have troubles, unless you start
         | by establishing good practices.
         | 
         | Like - what makes you think that python developers doesn't
         | understand stuff about Python, when they are actively using the
         | language as opposed to you?
        
           | suspended_state wrote:
           | Indeed, I'm not a regular Python practitioner. I had to use
           | it from time to time because it's the language chosen by the
           | tools I happened to use at that time, like Blender, or
           | Django. In the former case, it wasn't very enjoyable (which
           | says a lot about my skills in that area, or rather lack
           | thereof), while in the latter case I found it quite likeable.
           | So that's my background as far as python goes.
           | 
           | I must admit that I largely prefer static typing, which is
           | why I got interested in that article. It's true that trying
           | to shoehorn this feature in the Python ecosystem is an uphill
           | battle: there's a lot of good engineering skill spent on
           | this.
           | 
           | Perhaps there's a connection to make between this situation
           | and an old theorem about incompleteness?
           | 
           | https://copilot.microsoft.com/shares/2LpT2HFBa3m6jYxUhk9fW
           | 
           | (was generated in quick mode, so you might want to double
           | check).
        
         | Groxx wrote:
         | To try to tl;dr that rather long post:
         | 
         | > _When you add type hints to your library 's arguments, you're
         | going to be bitten by Hyrum's Law and you are not prepared to
         | accurately type your full universe of users_
         | 
         | That's understandable. But they're making breaking changes, and
         | those are just breaking change pains - it's almost exactly the
         | same if they had instead done this:                   def
         | slow_add(a, b):             throw TypeError if !isinstance(a,
         | int)             ...
         | 
         | but anyone looking at that would say "well yeah, that's a
         | breaking change, of course people are going to complain".
         | 
         | The only real difference here is that it's a _developer_
         | -breaking change, not a _runtime_ -breaking one, because Python
         | does not enforce type hints at runtime. Existing code will run,
         | but existing _tools looking at the code_ will fail. That offers
         | an easier workaround (just ignore it), but is otherwise just as
         | interruptive to developers because the same code needs to
         | change in the same ways.
         | 
         | ---
         | 
         | In contrast! Libraries can _very frequently_ add types to their
         | _return values_ and it 's immediately useful to their users.
         | You're restricting your output to only the values that you
         | _already_ output - essentially by definition, only incorrect
         | code will fail when you do this.
        
         | flanked-evergl wrote:
         | As someone who has been writing python for years the worst
         | mistake I have ever seen people make is not add type hints and
         | not using a type checker.
        
           | stavros wrote:
           | Also not creating custom, expressive Pydantic types and using
           | nested dicts in places. Nested dicts suck, you never know
           | what you're getting, and it's well worth the time converting
           | them to classes.
        
             | mejutoco wrote:
             | TypedDicts or data classes are both a good idea.
        
       | merksoftworks wrote:
       | I'd like to experiment with writing a reflection based dynamic
       | type annotator for one of these. Just as a toy. Imagine a module
       | which monkey patches every function, class in the parent module
       | it is loaded into, then reflects on the arguments at run time
       | slowly building up a database of expected type signatures. The
       | user would then pick through the deltas approving what made
       | sense.
        
         | LoganDark wrote:
         | This is a neat idea, but for large systems it could take a long
         | time to fully exercise every code path with all possible input
         | data. (If this sounds way too excessive, that's because normal
         | people don't do this.)
         | 
         | Unless you mean something like record prod for a few weeks to
         | months, similar to how Netflix uses eBPF (except they run it
         | all the time).
        
       | whyho wrote:
       | Astral tooling is great and brings new energy into python land
       | but what is the long game of all astral projects? Integrate them
       | into python natively? Be gone in 5 years and leave unmaintained
       | tooling behind? Rug pull all of us with a subscription?
        
         | move-on-by wrote:
         | I don't think any of these questions are specific to Astral and
         | can be applied to pretty much any project. 'Be gone in 5 years
         | and leave unmaintained tooling' seems particularly plausible
         | with regard to Facebook's tooling.
         | 
         | Use any of them at your own risk I suppose.
        
         | ijustlovemath wrote:
         | They'll most likely pursue some sort of business source
         | licensing, where you will not be allowed to deploy apps in
         | production using their tooling without the business paying some
         | kind of subscription. I understand that none of their existing
         | products fit this use case, but it will probably be a similar
         | approach. VCs are not charities.
        
         | belmont_sup wrote:
         | As a Redditor said:
         | 
         | > The standard VC business model is to invest in stuff that
         | FAANG will buy from them one day. The standard approach is to
         | invest in stuff that's enough of a threat to FAANG that they'll
         | buy it to kill it, but this seems more like they're gambling on
         | an acqui-hire in the future.
        
           | ipsum2 wrote:
           | I have never seen a FAANG company buy a pure programming-
           | language based tooling startup.
        
             | tjalfi wrote:
             | Facebook acquired Monoidics in 2013; they were the startup
             | that created Infer[0].
             | 
             | [0] https://en.wikipedia.org/wiki/Infer_Static_Analyzer
        
           | guappa wrote:
           | Why would they do that when they can just fork for free?
        
         | tnkuehne wrote:
         | I think I heard somewhere that they are working on other tools
         | that only big enterprises need like a hosted private package
         | registry.
        
         | rkangel wrote:
         | The announcement talked about selling services built on top of
         | the tools: https://astral.sh/blog/announcing-astral-the-
         | company-behind-...
        
       | claytonjy wrote:
       | for decades, big tech contributed relatively little in the way of
       | python ecosystem tooling. There's Facebooks Pyre, but that's
       | about it. Nothing for package/dependency management, linting,
       | formatting, so folks like those at Astral have stepped up to fill
       | the gap.
       | 
       | why is type checking the exception? with google and facebook and
       | astral all writing their own mypy replacements, i'm curious why
       | this space is suddenly so busy
        
         | flakes wrote:
         | Probably because a large amount of AIs are churning out Python
         | code, and they need type-checkers to sanitize/validate that
         | output quickly. Dynamic languages are hard enough for people to
         | make sense of half the time, and I bet AI agents are struggling
         | even more.
        
         | emptysea wrote:
         | Instagram built a linter with the ability to fix errors which
         | is an improvement over flake8 & pylint:
         | https://github.com/Instagram/Fixit
         | 
         | But Ruff is an even greater improvement over that
        
         | ckwalsh wrote:
         | Coming from a Meta background (not speaking on behalf of Meta):
         | 
         | "package/dependency management" - Everything is checked into a
         | monorepo, and built with [Buck2](https://buck2.build/). There's
         | tooling to import/update packages, but no need to reinvent pip
         | or other package managers. Btw, Buck2 is pretty awesome and
         | supports a ton of languages beyond python, but hasn't gotten a
         | ton of traction outside of Meta.
         | 
         | "linting, formatting" - [Black](https://github.com/psf/black)
         | and other public ecosystem tooling is great, no need to develop
         | internally.
         | 
         | "why is type checking the exception" - Don't know about Astral,
         | but for Meta / Google, most everyone else doesn't design for
         | the scale of their monorepos. Meta moved from SVN to Git to
         | Mercurial, then forked Mercurial into
         | [Sapling](https://sapling-scm.com/) because simple operations
         | were too slow for the number of files in their repo, and how
         | frequently they receive diffs.
         | 
         | There are obvious safety benefits to type checking, but with
         | how much Python code Meta has, mypy is not an option - it would
         | take far too much time / memory to provide any value.
        
       | librasteve wrote:
       | it is sad to see how painful typed python is ... i prefer a
       | language where (gradual) types were designed infrom the get go
       | ... https://raku.org
        
         | flanked-evergl wrote:
         | As someone who has added type hints to two huge code bases that
         | had nine. It's not that painful. Something much more painful is
         | realizing the code base you are adding type hints to is
         | irreconcilably bugged by design, which would not have been
         | possible had type checking been used.
        
       | thom wrote:
       | Any progress in the Python ecosystem on static checking for
       | things like tensors and data frames? As I understand it, the
       | comments at the bottom of this FAQ still apply:
       | 
       | https://docs.kidger.site/jaxtyping/faq/
        
       ___________________________________________________________________
       (page generated 2025-05-28 23:01 UTC)