[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)