As an Australian it's always a bit of a shock to see brand new European and American trains stopping at platforms no taller than a kerb, meaning you have to walk up steps.
I'd be very interested to see how much of a bottleneck boarding from the lower level of these trains is, in sydney we have double deckers where you board from the middle level from high platforms[1] and the stairs are already a flow limitation in the city section, I can't imagine how bad it would be with people going up two flights of stairs
I often see people say static typing slows them down and I'd really like to know why that is because for me it's the exact opposite, I really don't like not knowing what format data is in. I'd much rather have to write slightly more verbose code and have a vast number of possible errors caught at compile time instead of having things go wrong in production when someone inputs something a bit weird with nothing so much as an error.
I think it vastly depends what you're writing. Any kind of function that does not have a fixed input and output gets a lot more complex with static types. And by extension any code that depends on that also gets more complex.
I tried at some point to use Rust for an API but then I depended on making calls to a very complex API. In JavaScript I would have just gotten back a complex object where I can then pick whatever I want progressively through object access methods. In Rust I ended up with more than 500 lines of type definition for the API and it still wasn't enough so I gave up. It is a bit extreme but when you work with an API from an ERP for instance you can get very very complex and extensive types that are not in your control and not very well specified.
Another good example is how complex all ORM internal code get once they try to add static typing. The typing in the ORMs code feels like black magic because they would really really need types as code but don't have it so have to rely on a bunch of crazy generic combinators.
> Any kind of function that does not have a fixed input and output gets a lot more complex with static types.
If you don't know what kind of data people give your function and you don't know what's supposed to happen, how can you write that function?
I think many people use too strict of a type system. If your function works with any object that has a toString()->string function, then just write an in-line interface that defines it.
I actually love TypeScript here. It allows for `any` if you're prototyping, lazy or simply don't care.
It allows mapped types, where the next dev can see how a type came into being - for example, Mutable<T>, Readonly<T>, Required<T>, Partial<T>. The names actually speak for themselves! And it eliminates the Java-style verbosity of creating classes and files all just for a type.
I prefer dynamic types when I prototype and hack on code.
I'd prefer if dynamic languages would converge to gradual typing support, and static languages to gradual dynamic support. TypeScript is an example of the later, and PHP of the former.
You perhaps know, but I believe one should prefer the "unknown" type in TypeScript in such scenarios. Of course, then you need to check what its type actually is to make use of it, but this time TS checks that you do.
Btw, the equivalent in Python (and MyPy/pyright) is object.
> I tried at some point to use Rust for an API but then I depended on making calls to a very complex API. In JavaScript I would have just gotten back a complex object where I can then pick whatever I want progressively through object access methods. In Rust I ended up with more than 500 lines of type definition for the API and it still wasn't enough so I gave up.
You can always deserialize things as `serde_json::Value` instead of making types for everything to get similar behavior out of Rust.
The perceived difficulty of static typing depends on the type system itself. For example, there was a version of Swift where it became extremely painful to work with String and Substring, or Array and ArraySlice, which you’d intuitively just pass around whenever you’re making smaller pieces of collections but which the compiler complained about during type-checking when you are substituting one type for another. They solved the problem by just defining a protocol such as StringProtocol (and just reusing Sequence for arrays I think) and using that protocol wherever parameters are passed around, so it became possible again to swap those types for each other.
So then there’s more code for the language devs to write of course, but that’s just the cost of safety, which is highly desirable if you want the technology that you are building to be attractive for use in systems where correctness is critical, which also tend to be highly valuable. As a working professional it’s in my best economic interests to have such valuable tools in my skillset.
You can do that in every language, even in Java 1.0, by just using a hash maps of objects, and cast those objects to whatever you like at runtime before accessing them.
But then, you are basically throwing out your static type checks and just using it as a dynamic language, but with much more verbosity, cruft and additional ways to should yourself in the foot.
Yep. It’s awkward. The language fights you every step of the way and it becomes hard to read and write your code. x.foo = 6 becomes x.set(String(“foo”), Num(6));. When you read that value back you’ll need to unwrap the type you expect before you can do anything with it. It would be utterly miserable. You’d be much better off just using Python, JavaScript or Ruby.
You also lose all of the performance benefits typing brings, and gain nothing in return. In JavaScript land, V8 moves heaven and earth to guess what your types are and optimistically compile your code behind the scenes assuming its type annotations are correct. Then it dynamically deoptimises and reoptimises at runtime with that new information, while your program runs. As well as being ugly, the equivalent “dynamically typed” rust program would have none of V8’s smarts. It would run dog slow compared to normal rust and still way slower than the (much cleaner) JavaScript equivalent.
Like wood, programming languages all have a grain. If you program in harmony with the language’s design, everything will seem easy. Compiler errors make sense. The standard library will seem useful and well designed. Program against the language’s design and you’ll hate every minute of it.
Lack of proper types is what killed the joy of programming in Elixir for me (I lasted 6 months, hoping I would somehow adapt, but the experience was so miserable that I decided to move on). I simply can't understand how anyone can be productive in a big codebase without the rigor of static typing, but those people exist, so I guess there must be something about our brains that divides us in the dynamic vs static typing camps.
I'm firmly in the camp that it's all perception. The studies I've seen aren't what I would consider conclusive, but they certainly suggest there is very little difference in the output and bugs of dynamic vs static languages.
In my experience, static typing seemed to lend itself to poor testing, maybe some sort of belief that static types were good enough to not need tests that can prevent regressions. So from my point of view the static typing is negative value. It prevents such a low value class of bugs while seemingly incentivizing people to be lax with the most important classes of bugs.
I literally struggle to read and work with dynamic code. My head explodes when trying to hold return types or the shape of semi-complex data structures in my head, versus having it spelled out. I wouldn't call it an issue of "perception", I really think in my case it's "capability", pure and simple. I've programmed for thousands of hours in both styles, so we're well past the possibility of this being an adaptation problem.
I am totally convinced that the advocates of dynamic programming are right, too, just possibly built differently. For example, if I had an order of magnitude more working memory than I have as an individual (I'm assuming that's neurologically plausible), maybe I'd view dynamic programming differently, too.
I’m a proponent of dynamic typing. I’d say the same thing. I don’t have enough working memory in my head to store the compile-time type information about every relevant variable, in addition to the actually important information about what the runtime data could be.
The way that I write static-typed code is by imagining how I would write dynamic code to solve the problem, and then additionally imagining what types and type constraints I need to add.
I honestly think it’s a kind of an instance of Paul Graham’s Blub Paradox. I know JavaScript and I spent years writing JavaScript. So if you ask me to write TypeScript, well, I write the JavaScript that I want to write and then go back and add types to make the typescript compiler happy.
There are a bunch of other things I could talk about. I should write a post.
> I don’t have enough working memory in my head to store the compile-time type information about every relevant variable
Neither do I, but I know the compiler will check that for me so I don't need to hold all of that in working memory. I know my IDE will always be able to tell me the types too, and flag if anything is wrong immediately.
The different points of view on this are really interesting.
In the end both approaches work well enough to keep companies in business. Companies fail not because of static or dynamic typing but because of marketing, internal fights, etc. The technical details are something that impacts very rarely.
Personally I worked in C for the first years of my career. Then the web happened and the two languages for it were Perl and Java. No more mallocs and frees, it was so great that I kind of forgot C. Then I was assigned to higher level tasks than programming and when I came back to it almost 10 years later I refreshed my Java (I kept doing Perl for my own stuff - CLI scripts and CGIs) and discovered Ruby on Rails. I realized that I could do the same web apps I was writing in Java with much less code and without having to lose my time after obvious but nearly useless details such as specifying that a given variable is a string, or number and how big, or an array of that class of objects indexed by strings (I intentionally use generic terms), etc. It's almost always clear what it is, especially if one picks good names for identifiers. There are some hiccups but not every year.
By the way, a great feature of Elixir is pattern matching in function definitions. In a pythonish pseudocode
Of course n is an integer. It works with floats too and probably fails with anything else. But who would run fib on some complicated not numerical class that happens to implement the - operator?
I accept the argument that providing type information to a compiler lets it generate faster code. However none of my customers from the last 10 years care about that argument and they selected their technological stacks. Like everybody else they care about getting features done as quickly as possible. They all run their services on a single server, make enough money to pay themselves, their employees and a bunch of consultants like me.
> The way that I write static-typed code is by imagining how I would write dynamic code to solve the problem, and then additionally imagining what types and type constraints I need to add.
Is that not how everyone writes static typed code? You have a need for a variable holding some data, you think about the bounds of that data, you pick a type. What other way would you do it?
The problem with dynamic typing isn't in the first-write. It's all in your head then. It's in the 2 year later bugfix, when you're looking at a function and wondering what kind of data is being passed into it. And then you find all the places that call that function and still can't work out what fields that object will have, or whether they're numbers or strings.
> The problem with dynamic typing isn't in the first-write. It's all in your head then. It's in the 2 year later bugfix
The 2 year later bugfix is often easier I agree. But for me by far the biggest difference is speed and effectiveness in writing new code. I'm not just much faster, but also much better with typed code probably because it fits the way my brain works better.
> Is that not how everyone writes static typed code?
For me the process is a bit opposite. I tend to write the interfaces first, usually until the entire routine of whatever I am writing is complete. So e.g. only the types accepted and returned by functions, but not the actual function itself. Then I go back and add the code itself usually at the very end.
My working memory is garbage and I do prefer working with dynamic langs, however. (I also have ADHD)
I do agree though if I have to work with spaghetti code, I'd much rather work with a language that has a proper type system (I mean a proper one, like OCaml, Standard ML, Rust, Gleam, Etc.) I think static typing in these languages doesn't get in the way, they all have varying degrees of type inference, the type system is sound and uncomplicated. Whereas a Language like Typescript's type system (of Facebook's Flow for JS) had my head spinning, It often felt like playing a mean spirited scavenger hunt with Simon from Simon says.
As far as dynamic langs go, some languages are terrible to work in, (Perl). and other languages because of strong conventions (Ruby and Elixir,) I find very very easy to work in, write tests, create libraries that use DSLs, etc.
So I guess I'm in the middle? leaning in the dynamic langs direction though.
For me personally, my new theory is its an ADD thing. I'm older but recently came to fully appreciate how ADD my mind is, and how I've basically built many habits around coping with it. More recently I learned types is one of those things. I realized with types I can off load a ton of my working memory into the type system. I can bounce between ideas the way my brain wants me to without losing any state, because I'm always setting up the interfaces to the various thoughts I had. And the IDE knows how to take advantage of course, linking everything, popping up docs and hints and etc for every little idea. And so the result is it just matches my brain's natural patterns.
I still remember being quite shocked many years ago when I took a job using Java of all things, I was so fearful coding would take forever. It was my first typed language. But within months, I was faster, by a lot. I would write a lot more code before I had to test it, and once I began testing it there were few if any surprises. Now I'm working in Ruby, and without a debugger and constant tests, I feel I can't hardly get anything complex done in a reasonable amount of time. Like you I thought this was a hurdle that would pass, but while I'm far more effective than when I started, I find that its overall just mentally taxing to write code in this language.
Yet at the same time, when I pair with people who do not dislike Ruby, and watch them work, they are constantly asking "What is this" and "how does this work" and navigating parts of the codebase by grepping. They are effective, and I know its been studied a bit. But to my eyes, they would so obviously be far more effective with types, even when they state they dislike them, I just can't accept its a generally better approach in the absolute sense. I won't judge people for preferring a non-typed language. But I'll likely not accept another job where I can't use a typed language.
Nice to see that I'm not the only one - lasted 9 months trying to adapt to Elixir and couldn't. My background is Scala/Rust with heavy use of effect systems and reliance on writing the types out and then solving the puzzle of how to make everything fit. The tooling is pretty poor due to lack of investment compared to some of the other languages, and the debugging story is not great either. But hey, it works for some.
I found the tooling to be quite nice. In my opinion, it’s better or equivalent to most popular languages (python, ruby). It’s definitely not full-on bumper rails/training wheels like a Microsoft IDE, but it’s obvious they’ve invested heavily in tooling early on.
same here, mate. though mine is haskell. I'd rather waiting for gleam to mature enough to mess around with it than to be miserable with the dynamics of the data. I just don't like to guess stuff down the line.
I believe this as well. I like dynamic typing, because it often feels to me that rigour of static typing is slowing the prototyping phase. Plus overusing types creates a lot of boilerplate. In one of the frontends I've seen adding a boolean field to a form required changing over 10 files - thank you Typescript.
On the other hand, sometimes I feel like it has a lot to do with test writing. I feel people enjoy static typing, because you can get a feedback loop catching certain things without writing any tests. If you do write tests, all the type errors get caught pretty immediately anyway, so I just don't see the benefit.
Personally, the biggest advantage of dynamic typing for me is the ability to skip formalities for a time. If I want a function that can modify any struct with field "val" in (by, let's say, setting it to zero), I can - and I don't have to do multiple definitions, I don't have to define an interface. Just a function and I am done. If I want to skip error handling for now and only code the happy path, I can - and my program will work (and crash on error, but in some cases it's ok).
As the projects get more complex and require higher safety guarantees, static typing helps in ensuring nothing got broken - but nothing beats dynamic typing for prototyping.
I do think types and tests have a lot of overlap. In an ideal system, you would have tests that result in progressively more narrow types as each test case is added, providing the benefit of both.
This is basically the entire reason I am so excited about Zig.
Edit: it's also the reason I have a love/hate relationship with typescript. Typescript almost lets you do this if you try really hard, but at the end of the day it's not designed to truly test your code and requires you to "cheat" in order to make the types properly describe the situation; every time you "cheat" in order to get a more expressive type (whether that's looser or narrower), you would have been better off writing a test instead.
> If you do write tests, all the type errors get caught pretty immediately anyway, so I just don't see the benefit.
That's a pretty big if, though. You will never catch me in my typed backyard, writing tests that say: if typeof(arg) =! string then return error("Not a string!")
An additional problem of course is that solutions that assume extra work from people, tend to be brittle ones (also thinking about "manual backups" here) aka. those tests will end not being written at all.
You will never find me in my dynamic backyard writing such tests neither - I will write normal tests and they still expose type errors.
And, if you do not write tests, there is a bunch of other problems that will surface, which will not be caught by the compiler itself. What I'm saying is static types give you feedback loop:
write code -> make the project compile -> fix bugs
while the dynamic ones give you:
write code -> fix bugs (including type ones).
Obviously, YMMV. For me it works. And to be honest, from my experience nothing is more brittle than a type hierarchy designed early in project lifetime and then fixed repeatedly until it "works", but again - to each their own.
I mostly agree with you. Just wanted to add that in some languages, for example in Typescript, there are ways to say that you do not care about the type for prototyping. For example using :unknown or :any.
:unknown is great, but the problems come in the middle area; when you have lots of very tight types and are prototyping something that makes use of those things, :unknown doesn't work, and :any can be too expressive, even for your prototype.
This is true. But also I've noticed many less experienced TS devs end up writing types that are too specific and not utilizing structural typing enough. So e.g. writing some type and demanding a function accept it, when that function only actually needs one or two properties from it. Instead that function can inline or localize the part of the type it needs, and the original type can be unaware its ever being used by some other function.
And the other thing is trying to leverage the type system too much. For my style, if the types aren't making things easier to read, write, and maintain, they should be loosened and / or duplicated. I blame a lot of libraries here, it should never be puzzling to figure out how to type something so a function accepts it, yet that comes up quite often; I end up diving through these nested and overly DRY types trying to find what the heck even is this thing? That's crazy. If you need to cmd+click more than once or twice to understand what a type is, its way too abstracted.
Sorry, but that is moving the goalposts. Quick prototyping was mentioned, but now we seem to be talking about some undefined balance between typing and no typing for prototyping. It seems :any solves the problem.
I’ve swapped back and forth between static and dynamic types a few times in my career. Java and C/C++ to JavaScript (and coffeescript) to typescript and rust. The entire time (30 years at this point) I’ve felt like whatever I was doing at the time was obviously the one true way to program. I feel that now - that what I’ve been doing lately (static typing) is clearly superior. Even for throwaway JavaScript based projects at the moment the first thing I do is install typescript.
I’m spitballing on the reason - I don’t know why it’s like this. But maybe it’s because static typing encourages you to write & plan your types first. When you know the data types, all the functions kind of flow around your types and everything feels like it obviously flows from that. With dynamic typing, it’s the reverse. I find myself writing my functions first (top down or bottom up) and then backfilling what arguments and data everything needs to pass around. I run my code a lot more as I develop it to stop bugs creeping in. And because I can run my code at any time. There’s no type checker stopping me from trying things out.
I've recently found a great middle ground for this, using esbuild to drive code as I'm writing it, while my IDE may meanwhile be complaining that all my types are broken. Getting comfortable having "broken" types while something is still in development is a nice middle ground, helping you come back to the hot spots whenever you're ready, and acting as a forcing function prior to commit or push.
I've been on both camps, so I'm hoping I can give some insight.
Lots of statically typed languages are very strict about their types and have too many of them. You have to admit, when you're trying to build something difficult and focus on getting the business logic part of your program right, the last thing you want to be thinking about is e.g. whether you need a String, ByteString, LazyByteString or any of the other types of strings, and which library decided to accept which one. At some level its definitely useful to distinguish between those, and I'm sure a lot of libraries make sensible choices. But initially in the development of the program its just unnecessary busywork that distracts you from the important bits.
In the past, typed languages also made it a bit harder than necessary to create new types, especially in situations where you have serialization and deserialization. And finally, we had to do all this work, and for what? To be unable to prevent the most basic of errors i.e. NullPointerException? You have to admit, it was a hard sell.
A lot of things have changed, however. TypeScript makes it really easy to define types, anywhere - inline on the function in the argument, or right above it. You can pretend the type is correct at deserialization time, or you can use libraries like `zod` to both define a validator and a type in the same syntax - its up to you. Rust similarly has powerful and easy to use tools like serde - structs and enums are easy to define, and easy to attach metadata to them quickly to (de)serialize them accurately. Huge differences from the old world of getters and setters and NPEs.
When using dynamic languages, there are techniques to make things easier. There is the often mentioned testing, which does help verify a large subset of code paths. Lesser known and more subtle technique is coming up with and following a strict naming convention for properties, for example, and to keep things as consistent as you can in general. This is why you often see so much focus in code reviews about conventions, linting and so on.
Nowadays I guess it mostly depends on how good your dynamic language techniques (at the team level) are, as well as what your treshold for type system annoyances is. There are still some annoyances even in the best type systems, but its gotten way, way better.
This particular example with the string types was Haskell, but Rust often makes you think about performance decisions at all times as well. I'm fully aware that that's the point of Rust, however (and yes there are ways to stop worrying and just `clone`)
My best programming experience ever has been in OCaml and I love the idea of Elixir, but I can't even bring myself to try it because I know I'm not going to enjoy not having types. Even something as allegedly shitty as Python's type system would do it for me. I'm aware that there are ongoing projects to add typing to Elixir and I'll keep watching out how they evolve.
It's really hard to prototype w/ static typing. It's not really about mechanics; annotating parameters or return types, that stuff's easy. The problem is that in prototyping you're changing a lot of stuff because you don't necessarily know what the structure or types will look like, and every time you change something or rethink your taxonomy you have tons of type updates to do.
I just moved from typescript to javascript and for me it was just that I own the database, i own the api surfaces and as such I don't need to enforce any kind of type checking as the api schemas are sufficient for my case.
I would definitely use type safety if I had a lot of external data sources. or if there are lots of people working with me. Otherwise, I am beginning to go back to dynamic languages at least for web dev.
I'm currently working on a data engineering team, unborking some things on a team where all the original developers of the codebase had left. Up until then, I wrote Ruby for over 10 years, and Elixir for 3. I didn't have an opinion about JS or Typescript before this project.
Typescript does not solve the fundamental problems of JS. I'm not convinced it really solved the issues related to ingesting data from many different data sources. The data quality issues were still there.
If I were rewriting the whole thing, I'd rewrite it with Elixir (of course), if only to have a sane way of handling errors.
> I'm not convinced it really solved the issues related to ingesting data from many different data sources
That sounds like external data validation and I don't think a type system can really solve that. Even if you're using C#, Java, Scala, or what-have-you, you're going to have that issue. If that's what you expected from TypeScript, then yea, I can see how you'd be sorely disappointed...
Like many others, the advantage of type systems for me is in how they reduce cognitive load thanks to their explicitness, even in solo projects. I literally have a hard time remembering the types returned by a function I wrote a mere few hours ago... I have come to believe that there are two broad types of dynamic typing programmers: those who don't know better, and those who have a superior working memory, or cognitive ability, such that types hinder them rather than help them.
It is not as if I expected Typescript to be promising for external data validation. I was watching what people say about Typescript, and how people actually use Typescript.
I wrote Ruby with duck-typing for 10 years and it never seemed to be a cognitive load for me. My working memory has been degrading as I age. My Ruby code became increasingly written in a functional style. Maybe it's in how I write? Small functions, minimal (or no) side effects, composed together with names that describe how data is transformed.
Explicating types hinder me. Where it had really started becoming more useful was explicit types in functional programming, such as defining monoids or lattices, or whatever, on those data types. My experience with Typescript and even Crystal is that the static typing only yields a fragment of the benefits without being able to define operators that go with those data types. Even the Just/Maybe monad would have helped with messy external data, since that would explicitly define ambiguous data that can be progressively refined into clean data types.
I'm excited to see the new static typing effort in Elixir. It's theoretical base is a set, which, I think, will work better with how people use Elixir in production. It'll be interesting if the Witchcraft (ML ideas in Elixir) will work with those.
I am not an Elixir user but I too am watching the type story develop with a lot of interest. Even something half-decent in the vein of Python types would do it for me.
That's an interesting decision, because it's the reverse of one of the big complaints about Typescript: that it only works up to the API boundary, and doesn't include external data sources (unless you use libraries like Zod to combine runtime and static type checking).
For me, Typescript is more useful, the more of the codebase I "own", because it means I can be more confident that all the types reflect the true types of the runtime data, which means I can change things more confidently and quickly. Do you find that you're refactoring and changing things less with dynamic languages? For me, I think that's the number one magic feature that I miss when I use languages without explicit type systems.
I don't really write huge complex types in TS, I basically stick to just the native data objects like string, number, boolean etc., but even then I find it so useful just for the autocomplete that you get out of it.
I jumped into some code I haven't touched in half a year the other day so had 0 recollection of any of it, thankfully cause of the types I knew exactly what to pass where and what to expect back without even having to read any of the code other than the type definitions.
I love me some dynamic languages, but damn if it isn't nice to have that kinda power available to you.
That only works for people with super memory. I already forgot what the code I wrote yesterday does. If you showed me code I wrote a month ago I wouldn't even know it was me who wrote it. I could never rely on just remembering API surfaces.
Every time an elixir discussion starts a vocal segment of people just starts complaining about types. Ok we get it you like types, can you leave the rest of us in peace
Author here... I wasn't the one who posted this article to HN and upvoted it. Hell, I don't even have analytics on my website, so I had no idea. You can't really blame me for sharing my thoughts.
But yeah, static typing in some circles is a racket. Typescript has been a royal pain in the ass because people think a type system solves everything, but it can't solve side-effects no matter how well-typed they are.
Tests are always going to be my safety harness, not a promise from the compiler that my shit is sound.
The upcoming typesystem for Elixir will be a gradual type system, so you will not be required to write any types even after it lands (and you will still benefit from the typechecker either way).
It's just a hype phase. Types are cool, but in the past few years they seem to be the panacea for all problems, by inexperienced engineers that have got their first taste of Rust and Typescript. As if writing in Typescript would produce less buggy, more stable or more maintainable applications than using Elixir.
I'll say types are cool again before I get routed by angry static typing zealots.
> As if writing in Typescript would produce less buggy, more stable or more maintainable applications than using Elixir.
I have news for you. A lot of people, including me, do know that catching an entire class of bugs at compile time makes your code less buggy.
The trade-off as usually stated is that more things are possible with more dynamic code, and statically typed code is slower to develop with.
Elixir has much more interesting features that have nothing to do with the types/no types discussion and I 100% agree it would be more interesting to discuss those instead.
> Elixir has much more interesting features that have nothing to do with the types/no types discussion
My point exactly. Sidetracking every Elixir discussion with complaints about type (especially when they are actually working on it!) gets tiring quick, when Elixir is much more than "just" a dynamic typed language. It's literally missing the forest for the trees.
I’ve worked in a lot of languages, from PHP 3 to Rust, F# and Typescript and a lot of others. So I’m relatively educated about their differences and pretty open minded about languages but I still don’t understand why there is still a debate about this.
I mean, I can understand that some language don’t implement types and that they have other advantages (Elixir is a nice example) but I still don’t understand why no typing is still seen by some people as an advantage.
What sort of code can you write in a non-typed langage that you couldn’t write with a typed one ? Newest type systems are mostly invisible and working by inference.
The greatest example is TypeScript : you can throw any untyped Javascript at the compiler and it will still infer the types allowing you to get great auto completion and to avoid logical bugs. Why would you prefer to not have this feature ?
For me, compile time checks aren’t even the real feature, I’d even say that they are useless because they come after the real features of typed languages which is your IDE proposing auto completion that is guaranteed to work. It’s an incredible way to discover the APIs when you know that something must exist but you don’t know how it’s named.
But I’m open to be proven wrong if someone can give me practical examples in which types are counterproductive.
If elixir had types, I'd use them. Right now I'm using it for functional programming aspects, coherent design across libraries, liveview, and the amazing OTP library. Those to me are much more important.
With proper discipline you can get a lot of type-like behavior if you use structs to represent your data and ensure they're being passed in function args through pattern matching.
Frankly, we really need to instill the "types are cool" concept into inexperienced engineers. That insanely long phase in software engineering where people said dynamic types are cool was a huge mistake and ruined a lot of newbie minds.
Does José need to write up an elixir-lang.org blog post every other week on the status of their type system project for HN's collective memory to know it's in the works?
While you're waiting for official support in the language, just properly document functions with @spec and change them to @spec! after adding TypeCheck to your project and viola, you get powerful type checking at runtime with almost no performance impact. The error messages it produces are so beautiful.
Nice to see you are a musical aficionado too. More seriously, how does this compare to other type checking alternatives for Elixir/BEAM were I to start a new project, including Gleam and Witchcraft (which at least seems to be unmaintained for now)?
well, you can avoid this if you try to limit the number of public functions so you don't have to do this as often. make as many defp functions as possible, really.
It does slow some of us down. It's not really about terseness. I can write code that works on all primitives that might be sent down pretty easily. That code, sometimes is longer than limiting the inputs by types would be. I can also write code such that it only runs if the structure of the data is as required for that code to run, allowing for nulls or missing nested objects.
These two patterns allow you to write most code, type free, that gracefully handles anything you throw at it while always doing the right thing.
Making changes to such a system is easy and friction free.
Not many type advocates speak of the downsides of type systems, always pitching the net win and ignoring the actual cons.
When you refactor, make a change, or try to add new functionality, and end up fighting the type checker. That's friction to change you are experiencing and that experience is optional.
I get that having discipline in code patterns and the required robustness is a difficult ask at some organizations and some devs. In that circumstance it's better to have a minder in a type system that enforces base the conventions for everyone.
> When you refactor, make a change, or try to add new functionality, and end up fighting the type checker. That's friction to change you are experiencing and that experience is optional.
I don't really see that as "fighting the type checker", I see it as the type checker doing its job and warning me of the places where my refactor broke things. The alternative isn't usually that my refactored code magically works, it's that I have to manually grep through the codebase looking for all the places where I might have broken something, and get runtime errors if I don't catch them all.
In that sense the experience of struggling to make a refactor work isn't optional—the choice you get is whether you want the compiler to assist you with it or not.
I realize there are exceptions for certain types of refactors to certain types of functions, but the vast majority of patterns I've ever seen in any codebase—static or dynamic—are patterns that are difficult to refactor without a type checker and trivial to do with one.
To be clear, there are other reasons to prefer dynamic typing, and I'm not trying to say you're wrong to prefer it, but you're not going to get very far trying to persuade a static types fan that refactoring of all things is better in the dynamic world.
> When you refactor, make a change, or try to add new functionality, and end up fighting the type checker. That's friction to change you are experiencing and that experience is optional.
I'm not sure exactly what you're saying. If your language is strongly typed, you'll get type errors no matter what. The only difference is whether the type errors happen at compile time or run time. Let's take a hypothetical example:
Let's say I have a programming language called Foo. There are two ways to run programs written in Foo, using the interpreters A and B. A and B are identical, except for that fact that on startup, A will check that the given program is well-typed (static type checking), while B defers such checks to runtime (dynamic type checking).
Given a well-typed program X, I can run X with A or B without ever encountering a type error. Now, I make some changes, like you suggest, and I attempt to run it again. If the resulting code is not well-typed, I will immediately know when trying to run it with A, but with B I have to be lucky enough to hit the specific case that isn't well-typed.
If I understand you correctly, you're saying that you can easily make changes in a dynamic language without _ever_ causing run-time type errors. If that's the case, you would have _exactly_ the same experience whether you ran your code using A or B.
Making code that works on all primitives and accounting for nulls/empty each time is way more difficult than using a type system.
That’s the same argument people always use. “If you account for every case and also have 30 billion unit tests you can avoid all the problems”. The reality is, people don’t. They cut corners, they forget, or they simply make mistakes.
Not only that, debugging a system without types is a terrible experience, and IDEs don’t offer nearly the same level of refactoring support for untyped languages bc it gets very hard or impossible to infer what’s going on.
If it’s a personal project or a small script, untyped languages are fine. Any other scenario you should be using types
For me, a huge part of it is that I find static typing to make the code a lot more self-documenting.
I can easily see what is passes or returned, and if I'm unsure about the details of the type the answer is a click away, or a short web search away at worst.
Significantly reduces my mental load, allowing me to be vastly more productive.
The lack of types is hard to defend. Yet, the failed pattern match triggered at runtime becomes immediately obvious from the stack trace. You fix the pattern and move on.
The cost of this mild inconvenience is still far less than the cost of satisfying types.
I love elixir, but I would also love it more with a sort of laissez-faire type system akin to TS. Glad to see they're working in that direction.
One of the best features of type systems is they make editor completions and navigation work better. Elixir's LS is pretty good, but editor support just isn't nearly as good as what you can have in a good (read: Jetbrains) IDE with a strongly typed lang like C# or Java.
Elixir is a bit in the middle, with the massive amount of very specific pattern matching for each function and the fact that modules are just plain collection of functions and not objects I would say it's the most static of dynamic languages.
They are working on typing it now from the blog posts I've seen and they will probably be successful because the language is well suited for that in my opinion
Static typing can replace input validation though - if you make illegal states unrepresentable. That way you bake validation into your types e.g. using refined types: https://blog.rockthejvm.com/refined-types/ in Scala
In my experience, static typing takes a different way of thinking that can take a while to get the hang of, and during that adjustment period a lot of people are going to significantly slower. I definitely felt it coming into Swift, being used to Objective-C and to a lesser extent Ruby.
Having grown accustomed to static typing, not encountering errors until runtime, or worse having errors manifest as type-related misbehavior and potentially not be immediately apparent is much more frustrating than it used to be.
An important thing to realise is that "static typing" isn't just one thing. The details matter. For instance, type inference is a huge deal in making static typing not painful. Local type inference is something that pretty much all modern languages have adopted, since it makes writing out function bodies much less painful. But I think a lot of static typings bad rep comes from older languages that required lengthy annotations everywhere.
Then you of course have global inference, where you can write code as tersely as in a dynamic language, but still benefit from type checking and add annotations later as a form of documentation for readers.
It also changes the way you code. The best experiences from static typing come IMO from doing type-driven development, where you sketch out your problem first at the type level, then fill out the implementation at the value level. In dynamic languages, you can't program like that. So if you use the same mindset you will find the language limiting.
With dynamic strong types, you can still have a tool like dialyzer figure out potential type issues before shipping, but weak typing means anything goes.
I think static typing (without escape hatches) slow you down when you're a library or framework author, or you're doing something high performance. The reason being that there are many trivial and non-trivial propositions which the field has ironed over but the core language/compiler team hasn't caught up yet.
I've seen a lot of authors do crazy type things to get around the type system (like typing out recursion to the n-th level by hand), and I think many open source projects are slowed down by the lack of a type wizard on their team.
In my experience, static typing greatly slows you down when you're prototyping, at least for a short time, then it moderately speeds you up when you're productionizing and maintaining.
I could not disagree with this more. You are aided in prototyping by forming an early opinion on your data models. I would argue there's nothing more important in conceiving of something new than understanding the shape and relationships in your data. Typing does that.
And nothing slows a developer down more than accruing technical debt as they build. It's like having tar stuck to your shoes. You will work the fastest when you have nothing to pay back because your mental model of the application is aligned with the intention of your program.
(That said, I don't think you necessarily need a strong static type system to achieve these aims.)
Maybe it depends on what you're prototyping. In web dev I see what you mean.
But in game dev I'm often prototyping something to see if it works at all, or to check its performance. Ignoring types is faster when spending 1-2 hours/days quickly hammering out something that just barely works. On version 2 or 3, add typings.
Maybe it also depends on the timeframe of the prototyping project. After a few days it can become tech debt. (Though I still suspect there are some long term advantages to untyped.)
I actually think it's pretty similar to different automated testing camps. Some people say they build faster with TDD, and others say it slows them down.
Usually it is because people have a short term view of software, but in practice software lives for much longer than most people can imagine at the time of writing and that initial burst of productivity when working on a new system is even more present in a type-free and test-free environment.
But once that honeymoon phase is over you usually realize that types and tests were invented for a reason and beyond a certain level of complexity they are absolute must-haves if you want to have high confidence it all works as advertised and to be able to effectively refactor your code.
As someone who worked with Elixir for the past couple years, and maintains multiple libraries in Elixir - nah not really. Static typing is the thing I'm missing with Elixir.
No matter how much pattern matching you do, and how many typespecs you add to get a better understanding of what's behind a variable, you'll still run into issues at runtime frequently that could have been avoided if it was statically typed.
Dialyzer is great but typespecs and pattern matching only get you so far. You'll always run into situations where the shape of data is not clear, and you have to open a REPL to do an IO.inspect somewhere
I miss static typing for two separate and quite opposite cases:
* trivial errors, that still cause a crash and waste my time ("foo(:yo, 7)" but was "foo(7, :yo)") - sometimes spotted by Dyalizer
* complex nested structures. In Java I never have surprises as to what foo.bar.baz is and I can use autocomplete reliably. Expressing the same invariant in Elixir is something less straightforward
> You'll always run into situations where the shape of data is not clear
If you're using typespecs, then I think the deficiency is with tooling. I think the language servers (like ElixirLS) were quite buggy for a while, but it's getting better at code completion and inspection. But if the type has no spec then you're in the same situation as any gradually typed language.
If by "defiency is with tooling" you mean that `dialyzer` is bad, then yes. The current type checking facilities that it provides are too lax and also sometimes demonstrably actually incorrect even in the face of the simplest examples.
> incorrect even in the face of the simplest examples
I haven't found this in my experience. Most issues arise from a lack of typespeccing. If one were to rely on type inference, then, yes, it's not going to catch much. Specifying types is a requirement of a strictly-typed language, so to make a claim of deficiency with Dialyzer's type checking, you'd be comparing code that's fully specced with that of a strictly typed language.
The unavoidable problem is when using libraries that aren't properly typespecced, but that the same story as any gradually typed language (e.g., Typescript). The only solution here is to make a PR or a feature request to the product owners.
I've used dialyzer since 2015, it's not a question of "holding it wrong". I have an example of this that I literally demonstrated live in a talk online. It's very likely you haven't used dialyzer enough if you think it's actually correct 100% of the time even with type specs. Dialyzer is an exceedingly poor implementation of static type checking and not sufficient with any level of use.
I thought Dialyzer never raised false positives at the expense of false negatives, though perhaps your example is a false negative? I’ve only used it in the last 4 years. Is it possible the bugs have been fixed by now? Or is this just an inherent to this kind of type checking?
I don't think the implementation itself is at fault (I phrased that poorly in my previous post), but yes, I do think that the design of dialyzer makes it an (at times) faulty type checker. The unfortunate reality of a type checker that fails sometimes is that it makes it mostly useless because you can never trust that it'll do the job.
To be clear, I've had it fail in a function where I've literally specced that very function to return a `binary` but I'm returning an `integer` in one of the cases. This is a very shallow context but it can still fail. Now add more functions, maybe one more `case`. Dialyzer will just never keep up over a long enough time frame.
I think an entire rethink of type checking on the BEAM had to be done and that's why eqWalizer[0] was created and why Elixir is looking to add an actual sound, well-developed type checker. Gleam[1] I would assume is just a Hindley-Milner system so that's completely solid. `purerl`[2] is just PureScript for the BEAM so that's also Hindley-Milner, meaning it's solid. `purerl` has some performance issues caused by it compiling down to closures everywhere but if you can pay that cost it's actually pretty fantastic. With that said my bet for the best statically typed experience right now on the BEAM would be `gleam`.
That’s okay. I’d rather do that once in a while to keep all of elixir’s dynamic traits. The fact that you can open a REPL, and open one in prod, do language introspection, inspect data, query DBs with ecto, do hot code swapping is amazing.
Until we have something like Set Theoretical Types, I think this is the best of both worlds.
I'm building a static analyzer for Solidity in F# and the data shape of Solidity AST nodes overlaps frequently enough that explicitly specifying types is necessary just to get things to compile.
I can't imagine building something like this in a dynamically typed language. The way I see it, static typing is like writing inline unit tests to save yourself many, many headaches later.
It's certainly possible, I've managed to make the game run playably on a base model 2019 macbook, but that was completely vanilla on the lowest settings I could manage. Beat Saber (especially with mods) can get pretty damn hard to run (it often lags pretty badly on my 3700X/RTX 3070), especially if you're playing noodle maps or with high poly custom sabres.
I haven't tried it but I expect you'd have a very bad time running a quest due to the encoding step, and also probably wouldn't have fun on anything newer than a vive/cv1 because the resolution is too high.
Bimetallic strips are pretty slow, I can't imagine them being audible. I'd guess what you're hearing is almost certainly a relay, very likely driven by the bimetallic strip
edit: Replies have pointed out that they sometimes do "snap", so i guess i'm wrong there. I'm still pretty sure anything in a kettle/coffee pot/toaster etc would be a relay to avoid arcing
It's totally possible to shape a bimetallic strip in a way that makes it bistable, thus clicking between the two states, just like an electrical switch which uses springs inside which are normally not noisy either.
One construction I've seen is with a permanent magnet for hysteresis; the strip jumps to the magnet when it gets close, and then requires extra tension to release. The thermostat in my first apartment worked this way (and it looked to me like the strip was actually carrying the current to operate the gas heater; presumably a NC valve).
A bimetallic strip making a noise when it flexes is the origin of the original blinker noise. Cars these days just emulate the sound, but that’s where it came from.
It depends on the kind: a lot of bimetallic strips are designed to have hysteresis, so they snap to a different shape at one temperature and then stay like that until the cool down further (at which point they snap back), and this can produce a click.
I've opened up my water kettle before and it uses a little bimetalic strip to shut itself off once the water is boiling. According to the label it sucks 1500 watts and it's just shoving mains voltage through the heating coil.
i think this is an extremely North America-centric view. As an Australian, the lockdowns were inconvenient at worst and were a key part in why we have such low mortality. from an outsiders perspective, US lockdowns were a shitshow and were too half-arsed to actually achieve anything
I'd be very interested to see how much of a bottleneck boarding from the lower level of these trains is, in sydney we have double deckers where you board from the middle level from high platforms[1] and the stairs are already a flow limitation in the city section, I can't imagine how bad it would be with people going up two flights of stairs
[1] here's a pic: https://railgallery.wongm.com/sydney-trains-bits/F112_6364.j...