×
all 60 comments

[–]markehammons 169 points170 points  (46 children)

Why does this keep happening for dynamically typed languages? It feels to me like a vindication of static typing when all these dynamically typed languages keep bolting static types on after the fact.

[–]syklemil 79 points80 points  (2 children)

I think one part of an explanation could be that in the way-back-when, the alternative to dynamic typing was manifest, restrictive, even weak typing. E.g. languages like C where you

  • have to specify all the types,
  • but lack the power of generics and interfaces that have become common in this millennium,
  • and where the types may not even be trustworthy, as with C's implicit conversions ("promotions"), or plenty of languages that have implicit nulls resulting in NPEs and segfaults.

The gradual type systems we see added to various dynamic languages don't seem to have those same flaws. (Plenty of the older languages have been working on those flaws as well.)

Elixir is old enough that it could've been typed from the start (roughly the same age as Typescript), but if we take its beginnings as something like "Erlang in Ruby's clothing" then it's no wonder that it wasn't.

[–]spider-mario 0 points1 point  (1 child)

Elixir is old enough that it could've been typed from the start

Young enough?

[–]syklemil 0 points1 point  (0 children)

I'd rather phrase it as

Elixir's age indicates that it could've been typed from the start

than get into a pissing match over "old" vs "young"

[–]kerakk19 186 points187 points  (14 children)

Because dynamically typed languages sucks. Always will, always have.

It's convenient for scripting and so, but as soon as you need to have something stable (actual project), dynamic languages fall flat

[–]QuickQuirk 36 points37 points  (9 children)

Awesome for quick scripting and apps that are less than a thousand lines.

I really dislike it for anything larger. Good type systems eliminate certain classes of bugs.

[–]SoInsightful 18 points19 points  (8 children)

Awesome for quick scripting

I've never even understood this. I'll create a small quick script in TypeScript over JavaScript every day.

[–]stumblinbear 7 points8 points  (6 children)

Shit, I'll write a quick script in Rust if It's going to be in use for more than few months

[–]kabocha_ 2 points3 points  (5 children)

Tbh my barrier is basically just "is it large enough that I want to save it as a file" nowadays.

Quick bash scripts still win if it's mostly just calling existing programs, but otherwise my muscle memory + Rusts ease of spinning up a new project and adding dependencies wins.

I think I only launch Python as a quick terminal calculator anymore, lol

[–]tukanoid 2 points3 points  (1 child)

And if you switch to nushell, you wont even need python for that😂 (cuz thats the only thing i was using it for as well, outside of couple projects at work i seldomly interact with)

[–]kabocha_ 0 points1 point  (0 children)

One of these days I'll pick a different shell and switch to it.

I always keep thinking "but what about all the other systems I have to log into, my muscle memory will break for seconds before I remember to install it on that system too!" 😂

[–]syklemil 1 point2 points  (2 children)

As far as the terminal calculator goes, I'd been using units with a couple of flags; these days I just use numbat.

[–]kabocha_ 0 points1 point  (1 child)

Ooo I hadn't even thought of using a unit-aware program/language, that actually might be super useful for most of those "terminal calculator" use cases. Good tip!

The python mostly stuck around as muscle memory, so I'll need to break that habit 😂

[–]syklemil 1 point2 points  (0 children)

I do have an alias nb=numbat that I think helped get the muscle memory adjusted.

[–]QuickQuirk 1 point2 points  (0 children)

Typescript is actually pretty good. Much better than base javascript.

I'm still going to reach for python or erlang first.

[–]-Ch4s3- 8 points9 points  (2 children)

Every phone call you’ve ever made has touched an Ericsson switch running Erlang code. I’d say the telecom system is highly stable.

[–][deleted]  (1 child)

[deleted]

    [–]-Ch4s3- 2 points3 points  (0 children)

    Just because you don’t like dynamic languages doesn’t mean they suck. Distributed systems build for live code reloading while messages are in flight need to have at least some capacity for dynamic types. You can’t have nodes declaring different types at once for the same message. Dynamic types allow you to roll forward in a running system, it’s a very strong solution to that problem, and has proven robust for 40 years. Conversely trying to do this with static types would suck.

    Moreover a lot of basic web protocols are inherently stringly typed and dynamic.

    [–]efvie 8 points9 points  (0 children)

    Hilarious thing to say when Erlang itself is dynamically typed.

    [–]TypeSafeBug 8 points9 points  (2 children)

    I haven’t used this feature or modern elixir tooling yet but IMO this is a huge improvement over most dynamically typed languages. Most dynamically typed languages have pretty limited inference and require static type annotations to do useful things, and there’s a disconnect between the static types and the runtime behaviour (except in Python with Pydantic I guess).

    This looks like with guards (which have additional useful properties eg overloading, good luck with that, TypeScript!) you can pretty much have a statically analysable and typesafe program with the convenience of writing it like a dynamic one.

    [–]jessepence 0 points1 point  (1 child)

    [–]TypeSafeBug 0 points1 point  (0 children)

    Edit: didn’t notice the link; see rest regardless: Not really, in TS you can declare the method signature in such a way that when people go to use it, it shows up as if it’s got several overloads, but you are basically just writing a bit JS function that pulls apart arguments manually and has a bunch of internal logic inside.

    Elixir (like Erlang) uses arity of arguments + a guard clause with predicate functions to determine which version of a function to run. Which is technically not overloading like Java, C# or C++ do but is probably more flexible/useful (and more like Haskell pattern matching on function signature).

    [–]astonished_lasagna 49 points50 points  (9 children)

    Because it's very useful to have a language that can do both.

    [–]pheonixblade9 28 points29 points  (8 children)

    why? everything is typed at the end of the day, it's just chucking the validation down the road.

    [–]astonished_lasagna 17 points18 points  (2 children)

    Some things are harder to express in a static type system. For larger pieces of software, that tradeoff is usually worth it, but for smaller / one-off things it's not.

    [–]Wonderful-Habit-139 2 points3 points  (0 children)

    The tradeoff is worth it the moment you start defining functions imo. I always write types regardless of how small it is.

    I even wrote types during technical interviews in Python.

    [–]Saint_Nitouche 0 points1 point  (0 children)

    What things did you have in mind? API payloads?

    [–]syklemil 12 points13 points  (2 children)

    Part of it is just the power and effort that needs to go into some types. Dynamic typing generally wants the programmer to be able to express valid programs that would either be inexpressible or require inordinate amounts of efforts to type in static languages.

    If you've ever seen someone complain about complex generics, those are the cases where a dynamic type system would let you just write the behaviour and move on without needing a PhD in type theory.

    In practice plenty of people will just punch a hole in their type system with Any (AKA void*, Object, interface{}) when they come across a situation like that, which practically is static type systems moving towards the same middle ground, just from the opposite starting point.

    I prefer static typing myself, but it should be plenty clear to anyone that it ain't all sunshine and rainbows all the time either.

    [–]tukanoid 0 points1 point  (1 child)

    If you struggle with (complex) generics in types, i find it hard to believe that dynamic typing will make it easier, cuz now you have to keep all that context on your head, good luck remembering/re-figuring shit out in a week of not working on that part of code.

    Although, guess depends on the language. While the Rusts type system is not perfect, its expressive enough for me to be able to cram my logic into types 95% of the time

    [–]syklemil 0 points1 point  (0 children)

    If you struggle with (complex) generics in types, i find it hard to believe that dynamic typing will make it easier, cuz now you have to keep all that context on your head,

    That's the neat thing, you don't. Programmers who are used to untyped languages just don't reason around things the same way someone used to e.g. Rust or Haskell do. Shit, just talking about ADTs/"enums" and tuples to gophers is enough to make a lot of them confused, because they're not a part of the type system that they're used to.

    Instead they'll write tests and maybe some assertions. I suspect that in the extreme cases, that's actually the path of least resistance, as opposed to practically formally proving the behaviour through the type system.

    Programmers don't all think alike, just like how some have a more imperative mind and some a more expression-oriented mind.

    Although, guess depends on the language. While the Rusts type system is not perfect, its expressive enough for me to be able to cram my logic into types 95% of the time

    I also generally like Rust's type system, but that is on the more powerful side of the mainstream spectrum, and having lifetimes be part of the type system means you can describe some behaviour, like typestate, that's more likely to have wonky behaviour in GC languages.

    And I think most of us would still reach for some escape hatch before we wound up actually writing a signature like this one (via).

    [–]M4mb0 3 points4 points  (0 children)

    Because adding static types can be a huge effort and restrictions in the type system like lack of intersection types, dependent types and higher kinded types can prevent you from even statically expressing perfectly valid runtime code.

    I'd invite you to take a look at how complex annotations become for instance for python libs like numpy, scipy or pandas. Often functions need 10+ overloads to even statically express all their runtime capabilities.

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

    Scripting or when you need more complex return values

    [–]ultrasneeze 6 points7 points  (0 children)

    Type theory has advanced a lot during this century, and it turns out you can keep the benefits of a dynamic language while taking advantage of some static typing goodies. The same has happened in the statically typed languages: type inference has advanced a lot so you end up with languages like Scala or Rust that have very powerful type systems that can be almost completely ignored when the language is used as a scripting tool.

    [–]Wonderful-Habit-139 5 points6 points  (1 child)

    The way tooling has improved (editors, LSPs, linters, type checkers), they provide a lot of benefits for writing correct code as well as helping you develop correct code faster before you even run it. It also makes programs easier to understand when you can see exactly what shape objects have, rather than having to rely on running programs and checking the object's types at runtime (e.g. responses from external REST APIs).

    [–]syklemil 1 point2 points  (0 children)

    Not just the software, but the hardware, too. If we'd chucked today's software on a machine from when various popular dynamic languages were being developed (as in, early-mid 90s), we'd be having a terrible time. Most of it probably wouldn't even run with the available memory in those machines.

    My early experiences with Java + Eclipse vs Perl + vim left me with the feeling that I could have a running, working program in Perl before Eclipse had even displayed everything I'd typed on the screen.

    [–]HiPhish 3 points4 points  (0 children)

    At least in the case of Elixir it's because of Erlang, and in the case of Erlang it's because statically typing Erlang is really hard. An Erlang program can change at any time because the runtime supports hot code reloading, so how do you express that in a static type system at compile time? The easy answer is you don't, you just live with dynamic typing. The hard answer is that you start a multi-year research project to get gradual typing added after the fact.

    To be fair, static typing in Erlang and Elixir is not actually as needed as in Python or JavaScript because you have pattern-matching built-in at the language level. You can see at a glance what shape your data needs to be in. Of course having that enforced by the compiler is better, but it is what it is.

    [–]radozok 3 points4 points  (0 children)

    There is a talk about that: https://youtu.be/Tml94je2edk

    [–]efvie 4 points5 points  (0 children)

    Why not? Both explicit and static typing are very useful in some cases, so being able to be explicit when you want to is great.

    [–]faiface 11 points12 points  (0 children)

    Of course! There are innumerable advantages to static typing: - Preventing (many) runtime crashes - Domain model / module documentation that doesn’t go obsolete and is exact - Better IDE support / autocomplete

    [–]RogueDotSly 1 point2 points  (0 children)

    types are a blessing whether they're static gradual (fake)

    [–]JustBadPlaya 1 point2 points  (0 children)

    Elixir, Erlang and OTP in general are untyped/dynamically typed, and properly typing them at the time was not really possible, it took a good amount of research to make what Elixir is doing viable

    [–]Psychological-Rub505 5 points6 points  (0 children)

    It's because everyone who starts these kinds of new projects has the misconception that much of the existing code that looks like boilerplate is unnecessary and inefficient. Then the language gets used for something serious, the wheels fall off, and people realize that those seemingly unnecessary features were actually what kept the language sane. Voila, the dynamically typed language starts moving toward static typing.

    [–]Revolutionary_Ad6574 3 points4 points  (2 children)

    I'm glad I'm not the only one noticing this, I thought I was going crazy because no one is talking about it. But I don't see the problem. Dynamic typing was never a good idea, static typing has always been superior. It's normal for dynamic languages to have static envy.

    [–]ptoki 7 points8 points  (1 child)

    People talk about this but there is a huge crowd of half brains who think they know better and are loud about it.

    Same with nosql databases, local apps running like web (large parts of windows now), javascript ecosystem and few others.

    You come up with explanation why it is bad and the crowd will make sure you fell like you are crazy.

    BUT! Dynamic typing is not bad idea. It just should not be used for many things it is used for now.

    [–]Revolutionary_Ad6574 3 points4 points  (0 children)

    I agree. It has its uses. But for large, scalable projects static typing is the way to go.

    [–]f311a[S] 2 points3 points  (1 child)

    So what's bad about about catching runtime errors and crashes before they happen at runtime? They will happen if not found, unless it's a dead code. No one forces you to specify types.

    The examples in the blog post clearly demonstrates that, you can't divide string by a number. Even if a dynamic language can do that, it would be a bug in most of the cases.

    You can do this is JavaScript and it will silently give you a Nan, which most people won't even bother checking after division.

    [–]syklemil 0 points1 point  (0 children)

    The examples in the blog post clearly demonstrates that, you can't divide string by a number. Even if a dynamic language can do that, it would be a bug in most of the cases.

    It can also be the correct, intended behaviour, see e.g. this Perl program:

    print "divisor: ";
    chomp( my $divisor = <> );
    
    print "dividend: ";
    chomp( my $dividend = <> );
    
    say "result: " . $divisor / $dividend;
    

    Perl has conversion rules for string -> number, which is something along the lines of "parse the string until you encounter a non-numeric character; 0 if the string starts with a non-numeric character". Its operators are also explicitly for either numbers (like /, +, ==) or strings (like ., eq).

    I'm not really sure about it being a bug in most cases either, the problem with implicit conversions is more that they're a PITA to locate and correct when they are bugs.

    Like when PHP, using a similar conversion rule, wound up comparing some SHAs as if they were numbers with some garbage string at the end, e.g. "0efoo…" == "0ebar…" because 0 == 0, and it doesn't have the ==/eq split that Perl does to give the programmer control over what sort of comparison will be done.

    And in cases like JS, if we get a NaN after division, we might have a 0/0 or we might have "hello"/"world", so it's a really weak error signal compared to what we get out of typechecking.

    [–]ajr901 15 points16 points  (7 children)

    A year ago or so I started toying around with Elixir and genuinely enjoyed it but eventually dropped it because I kept repeatedly thinking, “I really wish this was typed.”

    I’m really glad it’s heading in that direction.

    [–]moltonel 4 points5 points  (0 children)

    Maybe have a look at Gleam ? Same ecosystem, but statically typed from the start.

    [–]legoman25 -5 points-4 points  (5 children)

    Elixir was typed, before: dynamically typed. People really need to stop implying static typing is "having types" and dynamic typing is "no types"

    [–]bythenumbers10 1 point2 points  (0 children)

    You could always have the worst of both worlds: Static weak typing, like C++!!!

    Strong types are mandatory, sit down JS. Gradual typing allows one-off scripts to develop into more deterministic code libraries as foundational assumptions settle into place. But while definitions are in flux, let them.

    [–]Atulin 0 points1 point  (0 children)

    dynamically typed

    Might as well have not been typed, then

    [–]TankorSmash 0 points1 point  (2 children)

    People really need to stop implying static typing is "having types" and dynamic typing is "no types"

    That's semantics. What people mean is dynamic types is not checked at 'compile' time

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

    It's not semantics, its the definition of the terms.

    [–]QuickQuirk 3 points4 points  (3 children)

    Now we just need it in Erlang. I still prefer erlang.

    [–]michalmuskala 1 point2 points  (2 children)

    We have it in erlang already for a while through eqwalizer

    [–]QuickQuirk 3 points4 points  (1 child)

    What they're doing here is the first step in a real type system in the elixir language itself with support in the compiler, rather than running another process on the side. I mean, there was dialyser before.

    The problem with the 'linter' style tools is that they're not mandatory. Even if you use it in your project, it breaks down when you pull in dependencies that don't.

    Since it's in the compiler, it means that over time, everything in the ecosystem will be using the type system.

    [–]Wonderful-Habit-139 1 point2 points  (0 children)

    Correct. Just wanted to give an upvote here, and mention that Python went through the same thing when they started having an actual typing module in the standard library, rather than relying on external tools with no direct interaction with the Python language.

    [–]PersonalDatabase31 4 points5 points  (0 children)

    Bare minimum

    [–]No-Hat-2797 -3 points-2 points  (0 children)

    gradual typing in elixir is a big deal for teams that want the flexibility of dynamic code