[HN Gopher] What Dynamic Typing Is For
       ___________________________________________________________________
        
       What Dynamic Typing Is For
        
       Author : hit8run
       Score  : 63 points
       Date   : 2025-10-14 06:23 UTC (4 days ago)
        
 (HTM) web link (unplannedobsolescence.com)
 (TXT) w3m dump (unplannedobsolescence.com)
        
       | hit8run wrote:
       | Just read this article and it vibes with my personal journey.
       | I've built and maintained many web applications for more than 20
       | years. PHP, Ruby, Python, Java, Dotnet, Go, Node, Bun etc.
       | 
       | The most maintainable codebase is the one that requires the least
       | guessing from the developers perspective. Where everyone that
       | looks at the code immediately gets a good understanding of what
       | is going on. When it comes to this I personally had the best
       | experience with Go so far as it is verbose when it comes to error
       | handling (I don't need to guess what happens or what can go wrong
       | etc.). Is it the most elegant? No, but I don't need to build a
       | big context window in my brain to understand what is going on
       | immediately (looking at you Rails callbacks).
       | 
       | Anyways I found the article interesting and as I am also using
       | SQLC I think it is something that goes in that direction.
        
       | TeaVMFan wrote:
       | I agree strong typing is a necessity in the front end. To prevent
       | the language explosion issue, I recommend writing your single
       | page apps in Java. The Flavour framework makes this quick and
       | easy with complete maven tooling and powerful components.
       | 
       | Flavour book: https://frequal.com/Flavour/book.html
       | 
       | Flavour home page: https://flavour.sourceforge.io/
       | 
       | Example app: https://frequal.com/wordii
        
         | kstrauser wrote:
         | The article isn't about strong typing. It's about static vs
         | dynamic typing. For example, Python is strongly and dynamically
         | typed.
         | 
         | Personally, I'd rather stop writing software than write
         | everything in Java, and writing software is just about my
         | favorite thing in the world. I don't contend that Java is a bad
         | language. However, it's almost diametrically opposed to how I
         | think about programming logic, and its style and conventions
         | set my teeth on edge. I'm sure Java programmers would say the
         | same about my preferences, too, and that's OK! I'm not saying
         | that my opinions are objectively right. But I am saying that no
         | one would be willing to pay me what it would take to get me to
         | write Java for a living.
        
           | smt88 wrote:
           | You can use the Java ecosystem with a number of other
           | languages that are completely different paradigms, like Scala
           | or Clojure, or you can use Kotlin as a "better Java"
        
       | jamesclar wrote:
       | nice article !
        
       | BoppreH wrote:
       | I agree, but I also thing that static analysis is a requirement
       | for high quality. The obvious solution is (global) type
       | inference, but I have yet to see a language that embraces it.
        
         | _flux wrote:
         | Have you seen OCaml? Though its inference stops at module
         | boundaries (but you don't need to annotate them) and for some
         | more special type features. In addition it solves some type
         | inference problems by having e.g. different operators on
         | integers and floats, there are no type-directed traits.
         | 
         | But in practice it's _quite handy_ to annotate module
         | interfaces and even individual functions.
        
           | BoppreH wrote:
           | I hear that OCaml without interface files can cause spooky-
           | action-at-a-distance type errors[1]. Have you had experience
           | with that?
           | 
           | > But in practice it's quite handy to annotate module
           | interfaces and even individual functions.
           | 
           | Yes, but it also limits what information the types can
           | represent because of syntax and human patience limitations. A
           | fully inferred language could associate numbers with possible
           | value ranges, lists with their sizes, enumeration of fixed
           | string values, etc.
           | 
           | [1] https://news.ycombinator.com/item?id=39615796
        
         | pjc50 wrote:
         | Hindley - Milner dates from the 70s, but the only language to
         | fully embrace it is ML.
        
           | taolson wrote:
           | What about Miranda, Haskell, OCaml and F#?
        
       | IshKebab wrote:
       | Well... Yeah but then you lose all the advantages of static
       | typing that he rightly acknowledges!
       | 
       | TSX in particular is a terrible example to make the point. It's
       | very very similar to HTML so there is barely anything more to
       | learn, and in return you get type checking, auto complete, go-to-
       | definition, etc. Its so good I use it for completely static
       | sites.
       | 
       | The SQL example is more compelling, but again it's really hard to
       | paint "SQL makes static typing a bit awkward" as "dynamic typing
       | is great here!". Like, you shouldn't be super happy about walking
       | across a desert because your car can't drive on sand. "Look how
       | simple legs are, and they work on sand".
       | 
       | That said, the SQL example does suck. I'm surprised nobody has
       | made something like sqlx that generates the Rust types and does
       | all the mapping for you from your schema. That's clearly the way
       | to go IMO.
        
         | 8n4vidtmkvmk wrote:
         | Kysley and others give you the types in TS. I believe you can
         | write SQL in Linq C# but it's been ages since I've done it. If
         | rust truly doesn't have that yet.. I guess they're missing out.
        
       | pydry wrote:
       | The thing I think most arch enemies of dynamic typing miss is the
       | underlying economics of writing software.
       | 
       | ~98% of software gets thrown on the trash heap before it reaches
       | the point where it really needs to be maintained.
       | 
       | That last 2% of gold dust _desperately_ needs to be maintained
       | well and to work, but to get to the point where you have that
       | asset you 'll probably find you need to _throw away_ an awful lot
       | of code first.
       | 
       | If you write that 98% using static typing it will slow you right
       | down - potentially to the point where you dont even get to the
       | gold dust.
       | 
       | If you write that 2% with dynamic typing it will fall apart. A
       | lot of programmers see this process without ever appreciating the
       | fast prototyping that got it there in the first place.
       | 
       | So, this is why Im a fan of gradual typing.
        
         | chuckadams wrote:
         | I find static types actually speed up writing new code, since
         | that's when I'm most likely to make typos in fields -- just
         | today I had to fix some goofs where I used snake_cased fields
         | when it was expecting camelCase. LLMs love types, and any
         | hallucinations are instantly red-lined. Agreed on gradual
         | typing, but I'd rather approach it from a strict language with
         | an opt-out dynamic type like TS's 'unknown' rather than a duck-
         | typed language with type "hints" the runtime has to always
         | check anyway.
         | 
         | Structural subtyping usually hits the sweet spot for me: I can
         | just create random structures on the fly, the type system makes
         | sure I use them consistently, and my IDE helps me extract a
         | named type out of them after the fact with just a few
         | keystrokes.
        
           | lycopodiopsida wrote:
           | I like common lisp, where sbcl will catch the worst type
           | errors while compiling, but you can also specify types and
           | they will speed up your code.
        
           | Devasta wrote:
           | I'm the same. When writing python I write some lines, run and
           | test, write some more, run and test... with Rust I bash out
           | lines of code for an hour then have the compiler and Clippy
           | help me fix it all afterward.
        
         | analog31 wrote:
         | Can the addition of type hints to the 2% be largely automated?
        
       | weavejester wrote:
       | Static typing is a useful constraint, but it's not the _only_
       | constraint. Focusing too much on dynamic vs. static typing can
       | make one miss the more general problem: we want code that 's
       | expressive enough to do what we want, while being constrained
       | enough to _not_ do what we don 't.
       | 
       | Immutability, for example, is another great constraint that's not
       | considered in the article, but should certainly be on your mind
       | if you're deciding between, say, Rust and Java.
       | 
       | The article delves into some of the drawbacks of static typing,
       | in that while it _can_ be more expressive, it can also contain a
       | lot of information that 's useful for the compiler but decidedly
       | _less_ useful for a reader. The Rust example that loads a SQL
       | resultset into a collection of structs is a standard problem with
       | dealing with data that 's coming from outside of your static type
       | system.
       | 
       | The author's solution to this is the classic one: we just need a
       | Sufficiently Smart Compiler(tm). Now, don't me wrong; compilers
       | have gotten a lot better, and Rust is the poster child of what a
       | good compiler can accomplish. But it feels optimistic to believe
       | that a future compiler will entirely solve the current drawbacks
       | of static typing.
       | 
       | I was also slightly surprised when templates were suggested.
       | Surely if you're aiming for rigor and correctness, you want to be
       | dealing with properly typed data structures.
        
         | addaon wrote:
         | > we want code that's expressive enough to do what we want,
         | while being constrained enough to not do what we don't
         | 
         | I don't think that's an ideal mental model. Code in any
         | (useful) language can do what you want, and can not do what you
         | don't want. The question is how far that code is from code that
         | breaks those properties -- using a distance measure that takes
         | into account likelihood of a given defect being written by a
         | coder, passing code review, being missed in testing, etc.
         | (Which is a key point -- the distance metric changes with your
         | quality processes! The ideal language for a person writing on
         | their own with maybe some unit testing is not the same as for a
         | team with rigorous quality processes.) Static typing is not
         | about making correct code better, it's about making incorrect
         | code more likely to be detected earlier in the process (by you,
         | not your customers).
        
         | vlovich123 wrote:
         | > we want code that's expressive enough to do what we want,
         | while being constrained enough to not do what we don't.
         | 
         | Have you somehow solved the halting problem? AFAIK, all Turing
         | complete languages are perfectly capable of expressing the
         | exact same programs.
        
           | otikik wrote:
           | A program does not tell the machine what to do. It tells the
           | next programmer what we wanted the machine to do.
        
           | tialaramex wrote:
           | The price for not making a Turing Complete language is that
           | you can't solve all possible problems. But, you probably
           | didn't want to solve all possible problems.
           | 
           | That's one of the insights in WUFFS. Yes, most problems
           | cannot be solved with WUFFS, but, we often don't _want_ to
           | solve those problems so that 's fine. WUFFS code, even
           | written by an incompetent noob, categorically does not have
           | most of the notorious problems from systems languages, yet in
           | the hands of an expert it's as fast or faster. It has a very
           | limited purpose, but... why aren't we making more of these
           | special purpose languages with their excellent safety and
           | performance, rather than building so many Swiss Army Chainsaw
           | languages which are more dangerous but slower ?
        
       | brabel wrote:
       | Very good summary of why static types are so useful, and when
       | they're not so much...
       | 
       | BTW, Java has a framework called Manifold that provides type-safe
       | SQL just like the author wants, with IDE support and everything:
       | 
       | https://github.com/manifold-systems/manifold/blob/master/man...
        
       | zahlman wrote:
       | > Unsurprisingly, the equivalent Rust code is much more explicit.
       | 
       | Okay, but you can do the same in dynamically typed Python, while
       | still using familiar exception logic and not requiring the type
       | annotation on `req` (although of course you can still use one):
       | def authenticate(req):           match req.cookies.get("token"):
       | case None:                   raise AuthenticationFailure("Token
       | not included in request")               case cookie_token:
       | pass           match req.db.get_user_by_token(cookie_token):
       | case None:                   raise AuthenticationFailure("Could
       | not find user for token")               case user:
       | return user
       | 
       | Although I normally use plain old `if`/`else` for this sort of
       | thing:                 def authenticate(req):
       | cookie_token = req.cookies.get("token")           if cookie_token
       | is None:               raise AuthenticationFailure("Token not
       | included in request")           user =
       | req.db.get_user_by_token(cookie_token)           if user is None:
       | raise AuthenticationFailure("Could not find user for token")
       | return user
       | 
       | Nothing ever forces you to pass "null objects" around in
       | dynamically-typed languages, although it might be more idiomatic
       | in places where you don't care about the reason for failure (or
       | where "failure" might be entirely inconsequential).
       | 
       | The nice thing about the Rust syntax shown is that constructs
       | like `let` and `match` allow for a bit of type inference, so you
       | aren't declaring manifest-typed temporaries like you'd have to in
       | many other languages.
       | 
       | > It's possible to write sloppier Rust than this, but the
       | baseline is quite a bit higher.
       | 
       | The flip side: the baseline for Python might be low, _but that 's
       | deliberate_. Because there are common idioms and expectations:
       | dictionaries have both a `.get` method and key-indexing syntax
       | for a reason (and user-defined types are free to emulate that
       | approach). So indeed we could rewrite again:                 def
       | authenticate(req):           try:               cookie_token =
       | req.cookies.get("token")           except KeyError:
       | raise AuthenticationFailure("Token not included in request")
       | user = req.db.get_user_by_token(cookie_token)           if user
       | is None:               raise AuthenticationFailure("Could not
       | find user for token")           return user
       | 
       | And for that matter, Pythonistas would probably usually have
       | `req.db.get_user_by_token` raise the exception directly rather
       | than returning `None`.
       | 
       | You can always add more checks. The Zen says "explicit is better
       | than implicit", but I would add that "implicit is better than
       | redundant".
       | 
       | > In essence, dynamically-typed languages help you write the
       | least amount of server code possible, leaning heavily on the DSLs
       | that define web programming while validating small amounts of
       | server code via means other than static type checking.
       | 
       | Well, no; it's not because of access to the DSLs. It's because
       | (as seen later) you aren't expected to worry about _declaring
       | types for the interfaces_ between the DSLs and the main code.
       | (Interfaces that, as correctly pointed out, _could fail at
       | runtime anyway_ , if e.g. an underlying SQL database's column
       | types can't be checked against the code's declared data
       | structures at compile time.)
       | 
       | The main thing that _personally_ bothers me with static typing is
       | that, well, sometimes the type calculus is difficult. When your
       | types don 't check, it's still on you to figure out whether
       | that's because you supplied the wrong thing, or because you had
       | the wrong specification of what to supply. And understanding the
       | resulting compile-time error message (which is fundamentally
       | written in terms of what the _type is_ ) is typically harder than
       | understanding a run-time dynamic type error (which can usually be
       | understood in terms of what the _object does_ ).
        
         | hazbot wrote:
         | > Okay, but you can do the same in dynamically typed Python
         | 
         | But the rust code is still safer, e.g. you haven't checked for
         | an `AttributeError` in case `req.cookies`, the point is Rust
         | protects you from this rabbit-hole, if you're prepared to pay
         | the price of wrestling the compiler.
        
       | qbane wrote:
       | Nitpick: SQL is a programming language. But for most CRUD tasks
       | you should rely less on SQL's programming capabilities until
       | processing information is too expensive outside the SQL
       | server/engine. The advice is also for maintainability.
        
       | skybrian wrote:
       | A fundamental limitation is that static analysis enforces
       | guarantees when a compiler can see and understand all the code at
       | the same time.
       | 
       | It's great when you compile large amounts of code written in the
       | same language into a single binary.
       | 
       | It's not so great for calls between languages or for servers
       | communicating across a network. To get static guarantees back
       | again, you often need to validate your inputs, for example using
       | something like Zod for TypeScript. And then it's not a static
       | guarantee anymore; it's a runtime error.
       | 
       | Database tables often live on a different server than the server-
       | side processes that access them, so mix-and-match between
       | different schema versions that the compiler never saw together is
       | possible in most systems.
       | 
       | To prevent this, you would need some kind of monolithic release
       | process. That runs into lifecycle issues, since data is often
       | much longer-lived than the code that accesses it.
        
         | tines wrote:
         | > you often need to validate your inputs, for example using
         | something like Zod for TypeScript. And then it's not a static
         | guarantee anymore; it's a runtime error.
         | 
         | True, but validating at the boundaries and having a safe core
         | is much better than having the unsafe portion everywhere imo.
        
         | IshKebab wrote:
         | Yes you have to validate untrusted input at runtime. Seems a
         | bit odd to call that a fundamental limitation of static types.
        
       | dzonga wrote:
       | or just use primitive types - maps, arrays & you skip this noise.
       | if a map key is empty then it's empty.
       | 
       | maybe people should watch Rich Hickey's Value of Values.
        
       | jonorsi wrote:
       | Very much enjoyed the article, it aligns with a lot of my own
       | thoughts on coding :P.
       | 
       | nitpick - in the Rust example it seems the events are not passed
       | into rendering the events.html template.
       | 
       | https://unplannedobsolescence.com/blog/what-dynamic-typing-i...
        
       | t43562 wrote:
       | Tests are how one constrains dynamically typed programs to make
       | sure they work as expected. I have found that adding tests to
       | untyped python code is far better at exposing bugs than adding
       | typing is. I like my IDE to do completion too but I tend to add
       | typing where I think it helps most rather than everywhere.
       | 
       | It just seems that some people like static types and good for
       | them. Dynamic languages are for those of us who are, perhaps,
       | lazy and want to be free to think about how to solve the problem
       | at a slightly higher level without having to get into details.
       | 
       | For me the details activate my perfectionism and that blocks me
       | from advancing with thinking about the goal and the effort of
       | creating an elegant set of types makes me less willing to
       | suddenly throw everything away when I realise there's a better
       | approach. Dynamic languages give me that ability to "sketch"
       | until I have an answer and at that point I have to ask: do I
       | really need this to be in a higher performance language? Once or
       | twice the answer has been yes but working out the approach in a
       | dynamic language allowed me to do it right in the static language
       | first time.
        
         | spooky_deep wrote:
         | Type checks are proofs across all inputs. Tests are proofs
         | across a given set of inputs. They are complimentary. If you
         | want really robust code, use both.
        
           | layer8 wrote:
           | complementary
        
         | IshKebab wrote:
         | Sure but nobody ever writes tests that are as comprehensive at
         | checking the basic types of stuff as simply adding static type
         | hints (which is very easy if you start with them).
         | 
         | How many times have you seen a NoneType error or a KeyError?
         | It's probably the most common class of bugs in Python and
         | there's an easy way to eliminate it. And as a nice side effect
         | your code is way easier to understand, navigate and edit.
         | 
         | I still sometimes have to give up and use Any in Python, partly
         | because half the Python community haven't realised their
         | mistake yet. But that's fine. It's still way better.
        
         | stevepotter wrote:
         | Agree on the importance of testing. Among the production-grade
         | codebases I've worked on, I've found that the dynamically-typed
         | ones have more comprehensive testing. It's just so easy to
         | think that a successful compile means your code will work. I've
         | also found that it's harder to set up great test systems for
         | static languages because you often have to modify your logic
         | just to make it testable (looking at you, IoC). A delightful
         | test system is one that engineers will use during development
         | because it saves time, not an afterthought. For whatever
         | reason, I haven't ever found something that provides this type
         | of experience out of the box. In one organization, we spent
         | months building a rig and achieved true TDD. The result was
         | fewer production issues, faster development, and of course,
         | better test coverage numbers. We eventually switched from
         | javascript to typescript, but it didn't compare to the
         | difference that test system made.
        
       | Waterluvian wrote:
       | What I say is that the types exist in your code whether you write
       | them down or not.
       | 
       | When you don't write them down, you're saying, "trust me, this
       | works." But now the types are written down in your head (a very
       | volatile form of memory that can't be accessed easily by others).
       | 
       | One time when dynamic works well is when "prove this works" is
       | simply not worth the time. Eg. a quick script.
        
       | swlkr wrote:
       | Pulling everything into the type system does lead to madness.
       | 
       | Luckily i'm just the mad scientist to recreate a non-async rust
       | web stack, here's the sqlite bit if you're interested.
       | 
       | https://github.com/swlkr/sqltight
        
       | SchwKatze wrote:
       | I'm thinking... couldn't a zig library infer the result columns
       | at compile time with comptime and return a proper type to it?
        
       ___________________________________________________________________
       (page generated 2025-10-18 23:00 UTC)