I don't think it's the AST: I think it's the BEAM itself.
The intermediate representations used in (and even accepted as input formats by) the compiler are relatively unstable, with the exception of the Erlang Abstract Format.
This makes it a great target for other compilers because they don't have to track breaking changes to the intermediate formats, they don't have to worry about generating BEAM bytecode, and they get to take advantage of all optimisations already implemented in erlc (the Erlang compiler).
The BEAM provides an unmatched concurrency model and related infrastructure. Targeting a language at it gets you all the benefits of that language plus all of the fundamental work done to build distributed systems.
Another way of looking at it: almost nothing that is important or wonderful about writing Erlang is the language itself. Most of what people rave about is the BEAM. This means you can fairly easily throw out the language and use a different one and get all the benefit.
Now that I'm thinking about it - that last point is a spectacular achievement of design and abstraction.
Perhaps it's a lot more than the AST that's attracting them? Like maybe the actor model (genservers)? Or OTP overall as a great platform for building self-healing, fast, distributed, functional systems? Or Immutability? There's lots of things to like about building on a foundation like Erlang.
One thing I'd really love to have on BEAM was a strong type system. Does this help achieve that? Is there a way to interop with Erlang/Elixir libraries?
This is the one I'm watching. Besides the docs not being done towards the end of the getting started syntax guide, it seems to be among the most developed of the make Erlang static typed competitors.
I'm mainly a C# dev, but I'm also watching Gleam with a keen interest. I spent a little time with Elixir a couple of years back and really liked it, but I just couldn't get over the lack of static typing - I don't mind for small things, but I absolutely want static types for larger programs. Gleam looks a little like Elixir, but with static types, which sounds great to me.
Why do you want that? Interestingly, Joe Armstrong said that people always asked about a static type system, but he couldn’t figure out the value of them. They were able to build extremely reliable systems with Erlang’s existing type system. And he mentioned at least one project that was 2 million LOC, so these weren’t just toy projects.
So what are you unable to accomplish in Erlang that makes you feel like it’s missing something?
"Is possible to do" isn't the same as "helps you to so".
You can write extremely reliable code in C (e.g. medical devices and car ECUs) but you have to be vary careful with any concurrency and usually dynamic memory allocation is banned. An enormous amount of testing and other design rules are needed. Maybe Rust would help to automatically check some of those things that otherwise require a lot of work to achieve.
You can write highly concurrent server applications in C. It's a lot of work and bug prone. If you write them in Elrnag or Elixir your life will be much easier. That's why we use them.
Maybe a static type system would help enforce some invariants about your code. Maybe it saves you from witting some classes of test. Basically, maybe you get to the same endpoint but with less work.
> "Is possible to do" isn't the same as "helps you to so"
The things that help you usually also limit you
See: training wheels or armbands
I've seen much code written in static typed languages (mostly JVM languages) that supposedly help the programmer to write better code or code with less errors just by playing tennis with the compiler
Thruth is that most of the code I've seen written "with type aids" is terrible from any other point of view
The types are correctly passed to each function, but what purpose those types serve it's a mistery
When you only have numbers, binaries, lists and tuples,you are forced to think about data structures in a simpler and more elegant way
IDE generated "type safe" code too often is simply "a class for everything" and bad abstractions
Most of the time when I review code my remarks is: it should be a 10 lines function, why did you write 10 classes with 10 getters and setters each?
> IDE generated "type safe" code too often is simply "a class for everything" and bad abstractions
> Most of the time when I review code my remarks is: it should be a 10 lines function, why did you write 10 classes with 10 getters and setters each?
Java is an extremely poor example of a helpful type system. Consider OCaml, Haskell, Elm, and others which avoid much of the verbosity you describe.
Static typing is really helpful when doing refactoring. The fact that a statically typed program compiles gives you important assurances - especially post-refactor. It's not a silver bullet but it's also certainly not "playing tennis with the compiler" as you describe.
> Java is an extremely poor example of a helpful type system.
With generics added 15 years ago, lambdas with argument type inference 6 years ago, local variable type inference two years ago, and ADTs and pattern-matching [1] gradually rolling out already, Java is practically becoming an ML. I say that as someone who likes both ML and Java. The two are becoming so similar that I think it's a stretch to claim one of their type systems is more helpful than the other.
But I think people overlook type systems' biggest benefits. Empirical observation has not found any significant benefit to correctness in general (although there seems to be some in particular cases like JavaScript vs. TypeScript and I wouldn't be surprised if we find it in Rust vs. C), but the main benefits are code organisation and tooling support (refactoring, code completion).
In my experience, Java's generics were horribly confusing and not quite type-safe, in contrast to the dead-simple polymorphism in Haskell. But feel free to correct me here.
> ADTs and pattern-matching gradually rolling out already
What a language gives you is less important that what it makes easy, and I cannot imagine a more cumbersome syntax for ADTs than the one in the linked proposal.
> In my experience, Java's generics were horribly confusing and not quite type-safe, in contrast to the dead-simple polymorphism in Haskell. But feel free to correct me here.
Oh, they're typesafe (except for a bug in the implementation found a couple of years ago) but I agree they're more confusing than in languages without subtyping. I'd still pick Java over Haskell any day (but I'd take Haskell over Scala). Pick your poison, I guess.
> What a language gives you is less important that what it makes easy, and I cannot imagine a more cumbersome syntax for ADTs than the one in the linked proposal.
Nah, the syntax is fine. Somewhat more verbose than ML's or Haskell's, but it fits well with the rest of the language. So instead of Haskell's,
data Expr = ConstantExpr Int
| PlusExpr Expr Expr
| TimesExpr Expr Expr
| NegExpr Expr
deriving (Show, Eq);
You write,
sealed interface Expr {}
record ConstantExpr(int i) implements Expr {}
record PlusExpr(Expr a, Expr b) implements Expr {}
record TimesExpr(Expr a, Expr b) implements Expr {}
record NegExpr(Expr e) implements Expr {}
Not quite as succinct, but it's not really any more tedious (and you get component names, too).
Yeah, I guess I overreacted to the verbosity — it's definitely a huge improvement over the current state of Java. But is there a reason not to add NonNull annotations to the fields of type Expr?
It’s much more common nowadays to have SpotBugs/Checkstyle treat everything as non-null by default. Then you use Optional where necessary (or maybe @Nullable).
Also given that is one of the few languages that can speak fluently with technologies that are not very well known anymore, but still rocking many boats like SOAP webservices or AS400 mainframes
The bulk of the Java applications are inside banks and insurance companies
> are code organisation and tooling support (refactoring, code completion).
Agree.
Even though code completion has never been a problem for me in the past 20 years, not even with bash lately
If you're talking about programming style, I don't see the resemblance (have you seen COBOL code?), and if you're talking about usage, then Java is much more popular and used in many more domains than COBOL ever was. If you must compare it to anything, then the C of large server-side software would be a more apt comparison, I think.
> The bulk of the Java applications are inside banks and insurance companies
And Apple and Google and Netflix and Amazon and Alibaba and governments and militaries and telcos and cedit card companies
and airports and power plants and factories; wherever you'd find big, mission critical server apps, really.
> Java is much more popular and used in many more domains than COBOL ever was
It was merely a comparison on the number of line codes deployed that never changed over decades and nobody wants to touch anymore because they drive critical systems, that move a lot of money
The same reason why much of the COBOL in existence is still around
I wasn't comparing the capabilities or the qualities of the two
> And Apple and Google and Netflix and Amazon and Alibaba and governments and militaries and telcos and cedit card companies and airports and power plants and factories
Then C is definitely a better comparison. Code like that exists in huge quantities in any popular language used to write long-lived software for more than a couple of decades, but COBOL wasn't really such a language, like C and Java are. It was used a lot in some rather narrow domains and then it faded. It became popular in the late sixties, and by the mid eighties it was already falling out of fashion and became very niche. It barely had twenty years of dominance. If you insist, then a more modern language with a trajectory similar to COBOL would probably be VB.
> They rely on COBOL code too...
And quite possibly VB, too. But those companies write much of their new software in Java. They don't do it in COBOL (or in VB).
The same code from 20 or 30 years ago compiles today with little or no modifications
Try to run a servlet from 1998 or a struts web app from 2001 with a recent JVM
And yet you still find them in a lot of places, that you don't see, because you don't work with them, but that probably manage some of the transactions you make
They are legacy because they are being maintained for clientst hat have been using them for at least 2 decades and are virtually untouchable, but are still being updated, slowly, to keep them running
I'm not insisting, it's what it is.
VB is not legacy, VB is dead
No large company ever relied on VB to keep going
__ever__
COBOL is still being developed, because it's still used
To the point that COBOL can interface to Java or C# because companies prefer to update thei COBOL programs to call outside than rewrite them
The final approved ISO (of Object oriented COBOL) standard was approved and published in late 2002.
It's easier to run (and even revive) a 25-year-old applet than a 25-year-old OS/2 C application. Java has a much better backward compatibility story than C because C relies so heavily on OSes, many of which are effectively defunct.
Still, there are probably billions of legacy lines of code in C, C++ and Java, and hundreds of millions of lines of code in those languages are still being written every year (in Java more than in C/C++). In fact, more new lines of code are professionally written in new Java codebases than any other language with the possible exceptions of JS and Python (although probably not).
Like COBOL, existing VB code is still maintained, but very little new code is written. I'm not saying VB is exactly like COBOL, but I don't see any resemblance between Java and COBOL any more than I would between C and COBOL or Python and COBOL.
> It's easier to run (and even revive) a 25-year-old applet than a 25-year-old OS/2 C application
Are you sure?
Most of the APIs have been deprecated.
Builds for modern systems (64 bit) are non existent.
OS/2 has been dead for 20 years and there isn't much code being written for it anymore, I remember the Italian train company using OS/2 terminals, they have been dismissed more than a decade ago because IBM ended support for it.
Ironically you can run OS/2 in your browser, but not Java applets.
Now, to explain you what I mean when I say old Java code is as legacy as COBOL I will start with a story: it took Github a year and a half to upgrade from Rails 3.2 to Rails 5.2, on a less than 10 years old code base, to some of the best Ruby/Rails experts in the World.
Imagine that there are services that government agencies use, written 20 years ago in Java, by average former COBOL programmers that wanted to try new things, that will not be rewritten soon, and if they will it will take years for them to be adopted, that are interfacing with even older code running on mainframes.
Management bought the story that Java programmers were easy to find (they still are), being a Virtual Machine it would run easily (write once, run everywhere) and that was Enterprise (it was in the name).
It wasn't completely true.
The path Oracle pushed Java to make it compelling for new generations of programmers left behind a lot of already written and deployed code that can't be run directly on modern systems, can't be touched 'cause companies rely on that code and even if it could be upgrade there are no Java programmers that wants/are able to do it (it doesn't boost your career to work on it) if the hardware won't fail in the meantime.
2000 Java code is harder to write/maintain/upgrade for a Java programmer of 2020 than 2000 COBOL was for a COBOL programmer of the 70s.
In 5-10 years, when the first generation of early Java adopters will retire, there will be billions of lines of Java code that nobody will know how to keep running.
To coin an old programming phrase "The determined Real Programmer can write COBOL programs in any language"
Most of Java 1.0 is not deprecated, and runs unchanged today. Also, deprecated doesn't mean removed. The browser plugin is no longer developed and is not supported by contemporary browsers, but it's easy to turn an applet into a desktop app that runs on the latest version of Java (the Applet classes are still shipped with JDK 15, you can still compile that code, and you can still run it if you wrap it with some desktop runner).
> OS/2 has been dead for 20 years and there isn't much code being written for it anymore
You're missing the point. A lot of C code out there targets platforms like that. It is much harder to maintain than Java code.
> Ironically you can run OS/2 in your browser, but not Java applets.
> The path Oracle pushed Java to make it compelling for new generations of programmers left behind a lot of already written and deployed code that can't be run directly on modern systems, can't be touched 'cause companies rely on that code and even if it could be upgrade there are no Java programmers that wants/are able to do it (it doesn't boost your career to work on it) if the hardware won't fail in the meantime.
Except this is more true for C and C++ than for Java, and I still don't see the connection with COBOL. It's easier to run old Java code than old C/C++ code because the compatibility is so much better.
> 2000 Java code is harder to write/maintain/upgrade for a Java programmer of 2020 than 2000 COBOL was for a COBOL programmer of the 70s.
I don't know if that's true or not, but I do know that 2000 Java code is much easier to maintain than 20-year-old C/C++ code. Seems like you're not familiar with legacy C/C++: the situation there is much worse. I've seen systems running on ancient hardware because the C software was written for OS/2 or a particular version of the AIX compiler that comes with an AIX version that doesn't run on more modern hardware. I don't think there's any language with a better situation than Java when it comes to old codebases.
> Like a lot of Java code targeting Android is harder to maintain than C++, after just 6 months or depending on the brand of phone it runs on...
Android isn't Java, and it's never been. It's an incompatible fork that has never conformed to the Java specification of parts of an old version of Java. Its compatibility story might be very different from Java. It is also much smaller in its reach than Java, and even of COBOL, and its domain is also much smaller than either.
> Java is an extremely poor example of a helpful type system
My point exactly!
Most of them are.
Java is not special in this regard.
C? bad
COBOL? bad
C++? bad
Pascal? bad
Rust? better, but somewhat confusing
Go? not particularly good, but at least it's very simple
C#? same defects Java has
etc. etc.
Statically typed languages IMO are like turbo charged engines, if you are an expert driver you could go way faster, granted the road is in good shape, but if you are not, you'll crash, possibly in a horrible way.
> The fact that a statically typed program compiles gives you important assurances - especially post-refactor.
The only assurance in most of the statically typed languages is that code compiles, not that it works.
How about OCaml, Haskell, Scala, or F#? All have pretty decent type systems, with Scala and Haskell even supporting higher kinded types.
I'm at the point where I refuse to write production code in a language that doesn't have an expressive type system. Dynamically typed languages are even worse.
Have you considered maybe your problem is simply that you don't understand Scala?
I used to be a professional Scala dev and loved the language. Sure, there's some complexity, but I still strongly believe it is the most powerful and expressive static language around.
I know Scala is complex. Many languages are. No language starts out with the goal of being complex. They all want to be simple and elegant but the clash with real-world use-cases makes them complex. Like Bjarne Stroustrup said–there are languages that people complain about and there are languages that no one uses.
Seeing your messages, I really think you don't mean the same things as the others when you say "strong type system".
Generally what people here mean by strong type system is
All those tools are made to help the programmer write better, safer programs more easily, as well as lay out his thought
It is generally associated with FP, and are fond in languages such as Rust, Ocaml, Typescript, Haskell, F#
From what your examples are ( C, java, C#...) and the drawbacks you see (too much getters -> linked to an OOP style of programming), what you mean by "strong type systems" looks like it is the old C-style type systems (+ OOP though I don't really get what oop has to do with type systems). These systems are not mainly intended to help the programmer but are really meant ro help the compiler.
When in C you write
`int a;`
What you're doing is indicating to the compiler that for operations involving variable a, it should use the machine instructions for operation on integer (you can replace C with java and machine with jvm...).
These are necessary in order to have low-level control, but are not really hepful (apart from, maybe, documentation purposes. It is always useful to know that that ipv4 variable is a 24-bit int and not a string...)
These two types of systems are tremendously different things. I can really understand that you don't like static type systems if you only know the second type of system, which basically only adds constraints.
I think you should take a look at ocaml, or perhaps haskell, it would probably expand your horizons
I think you're conflating "statically typed languages" with "object oriented languages" (they do go together a lot).
Static typing with functional programming languages works really well because the types are so simple. If you look at OCaml, or Haskell, or F# you simple types, but with a lot of benefit of type checking.
> I think you're conflating "statically typed languages" with "object oriented languages"
No, I'm not.
I'm simply pointing out that most statically typed languages, the most used ones especially, don't particularly benefit from static typing.
> Static typing with functional programming languages works really well because
IMO it is because they are mostly used by more experienced programmers, that can already layout the types in their head.
Try to teach the average Java programmer (I'm not saying I'm a better programmer, Iḿ just intending it as "they are trying to get through the day with the tool they already know") how to effectively use types "like in Haskell" and they will be confused at best.
> OCaml, or Haskell, or F#
I do look at them but together they make probably less than 5% of Java programmers, even less if we measure lines of code
EDIT:
My position is based on the time spent reading studies about the subject.
Let's not be emotional and downvote what we don't like to hear and be more rational and look at the facts.
If you like statically typed language because they make you feel safer, use them.
Is it an universal property of statically typed languages? Id it a measurable phenomenon?
Are statically typed languages inherently safer for everybody?
The problem, at the time of writing, is undecidable.
> Other than cherry picking studies to confirm a long-held position, the most common response I've heard to these sorts of studies is that the effect isn't quantifiable by a controlled experiment
> I'm simply pointing out that most statically typed languages, the most used ones especially, don't particularly benefit from static typing
That's an opinion presented as fact. You are outright discounting the opposing view. That's why you're being downvoted, not because anyone is being emotional.
Some benefits:
- performance. compiler can make huge optimizations if it knows the types.
- refactoring. changing functions might change data expected in another function. you could remedy this with unit tests, or just use static typing.
- onboarding. new developers on a project, regardless of experience, don't "layout the types in their head", static typing helps.
> I know people are irrational, especially when they feel that their beliefs are being questioned.
do you think I haven't used dynamic languages? I started using elixir in 2016. I started my career in Ruby, JS, and PHP.
Just because you can conceptualize the entire codebase's type system in your big brain, doesn't mean other don't have a rational reason for preferring static typing.
> IMO it is because they are mostly used by more experienced programmers, that can already layout the types in their head.
> Try to teach the average Java programmer (I'm not saying I'm a better programmer, Iḿ just intending it as "they are trying to get through the day with the tool they already know") how to effectively use types "like in Haskell" and they will be confused at best.
Counterpoint: I don't use any ML/Haskell languages regularly, but I do use a couple of projects written in them[1]; my preferred language is Common Lisp, the It dynamic language. When I needed a feature, it was dead simple to do so.
1: matterhorn, the Mattetmost client and bower, the E-mail client, which are written respectively in Haskell and Mercury.
I am a counterpoint myself, I don't consider myself a particularly experienced programmer, but I rarely encounter type errors, most of them are semantic or logic errors.
I'm faster at finding and solving the issues with languages I know best (not surprisingly) regardless of their static or dynamic nature.
Experience, in my case, is knowing where to look, being able to quickly parse an error message, from the compiler, the linter or the runtime, but most of all knowing the code base at heart.
When I confront a code base that is new for me, I feel lost, whatever paradigm has been used.
I simply don't know where to put my hands before I spent time reading and analyzing it.
The time required to understand it depends exclusively on the complexity of the project.
There hasn't been a single time where types have helped me (more than being able to grep the function signature) or their absence slowed me down.
JavaScript and TypeScript are great because you can compare them directly and they are essentially the same language, one with types.
And as someone who has written JS for over a decade, TS is hands down better, it’s not been close how much nicer it is to work in the latter. The bigger the project is the more it helps.
> Try to teach the average Java programmer (I'm not saying I'm a better programmer, Iḿ just intending it as "they are trying to get through the day with the tool they already know") how to effectively use types "like in Haskell" and they will be confused at best.
I have had the opportunity to teach java programmers the basics of Haskell, and indeed, it was not great. Trying to tell java programmers to write haskell-flavored java is not a great idea.
On the other hand, I have also had the opportunity to work with C#, JS, C programmers that were taught to work in a haskell codebase, and the result was much better.
I guess using a language explicitly made for this helps a lot.
Not because of their Java background, but because the complexity of developing in Java is hidden behind tooling not available in other languages, that make it manageable
It is almost unimaginable for Java programmers to use vim or a non Windows setup (thanks to Java IDE like Eclipse, Nerbeans or Intellij that's less of a problem nowadays)
> Most of the time when I review code my remarks is: it should be a 10 lines function, why did you write 10 classes with 10 getters and setters each?
This is the fault of the language or programmer itself instead of its type system.
I am programming in Dart and sometimes I feel that I am writing some code just to workaround some limitations in the language (the language itself is nice 90% of time, just to make it clear).
Now, I also program in Python using mypy, and I never needed to write "10 getters and setters" to make the compiler happy. Sometimes I do need to write additional code to make mypy happy, but generally it is in overly clever code that mypy doesn't understand. So generally fixing the issues makes the code better.
> This is the fault of the language or programmer itself instead of its type system.
Not that I disagree, but I argue that bad programmers are the result of bad languages enabling them and bad use of types by the programmers is the result of a bad implementation of types in the language.
Most of the statically typed languages around are used to enforce conventions inside large teams of coders, they are not used to "model the problem elegantly".
If type driven development really produced bug free code while requiring the same cognitive load as hammering tens of lines of code one after the other, we will all be using something like Idris.
> Most of the statically typed languages around are used to enforce conventions inside large teams of coders
This is not the fault of the type system though. It is the fault of things like enforcing access via private/protected/public keywords. While most people confuse this with a static type system, this is not part of the type system.
> [..] they are not used to "model the problem elegantly".
I concur, but let me make my point more clearly: this is either the fault of the language for not allowing the programmer to express itself clearly, or it is the fault of the programmer by using patterns from a less expressive programming language in a more expressive programming language.
This is not the fault of the type system, that doesn't (by itself) require the user to create a Class with getters and setters for every attribute, for example. If the language you're using makes you do those kinda of things, it is the problem with the language itself, the type system is just the point of the iceberg (that is making the problem worse).
> If type driven development really produced bug free code while requiring the same cognitive load as hammering tens of lines of code one after the other, we will all be using something like Idris.
It doesn't produce bug free code (because there is still logic bugs), but it helps to reduce the number of bugs. But only in expressive type systems that are also sound. The Java type system for example doesn't help much (however I think they implemented nullables at some point, so the situation should be better).
The biggest use of the static type system to me is development efficiency. I can write type safe code much faster than I can write duck typing code.
Examples:
- I accidentally pass in 3 params instead of 4 to a function (with static typing this does not compile)
- I forgot to handle an edge case (I can use a discriminated unions for example to handle all cases explicitly)
- I did not check if something was null and pass it in to a function (does not happen in most ML languages)
I know that you can have editor plugins to handle these, still. I can develop reliable code much faster in F# than in Clojure, even though I love Clojure to death.
You can do many things in Erlang that helps with these, still it would be really good to have a language that unifies Erlang and ML (any ML with Hindley–Milner type system would work).
- I accidentally pass in 3 params instead of 4 to a function (with static typing this does not compile)
In Erlang this will also not compile, unless you have a 3 parameter version of the function, in which case static typing will not help you.
- I forgot to handle an edge case (I can use a discriminated unions for example to handle all cases explicitly)
You also forgot to write a test it sounds like. And if it affects your production code, you also forgot to think through your supervisor restart strategy.
- I did not check if something was null and pass it in to a function (does not happen in most ML languages)
Null isn't a special thing in Erlang. You can have the atom 'null' or 'undefined' or whatever, but that's an intentional assignment you create. And given the single assignment immutable nature of Erlang, you won't accidentally be setting/leaving things set to 'null'.
> unless you have a 3 parameter version of the function, in which case static typing will not help you
I half disagree. Let's say we go with the above scenario. If you have a three parameter function, static typing adds a check that the input parameter types need to match, if they don't, the program will not compile. Additionally, the returned type of the function may also not match, which would also be caught by the compiler, and depending on when the developer is writing the code, maybe immediately by the developer noticing that the type signature of the function does not match what they expect.
With dynamic typing, those checks have to happen at runtime or as a human written test. But if the compiler is smart enough to do it automatically, why not use that?
> You also forgot to write a test it sounds like. And if it affects your production code, you also forgot to think through your supervisor restart strategy.
Programming languages like F# have exhaustive pattern matching, which obviates the need for testing on whether all cases of a union type have been matched, because if they haven't, the compiler will emit a warning or error depending on one's language server settings. Whichever setting is used, the developer immediately knows they are missing a pattern match case at compile time.
>ngiven the single assignment immutable nature of Erlang, you won't accidentally be setting/leaving things set to 'null'.
This is similar to ml languages like F#. They are also immutable by default. F# calls it "unit".
I was specifically responding to "calling a 4 parameter function with 3 parameters". In the case you have a 3 parameter function, and pass in the wrong type, static typing -may- help you...but most languages that allow that that are statically typed allow for function overloading (since functions are then defined not by name and # of args, but name and types of args). If you overload the 3 param variant, static typing can't help ensure you pick the right one, just one that exists. That was my point; you still have to write proper tests, and that not only has the benefit of ensuring you're calling the right function, but that it also does what you want.
Re: testing - regardless of whether you have static typing or not, you should be exhaustively testing every pattern you match on, to ensure you're doing the right thing. If you don't, you don't care about the behavior in those cases - in most languages that's a problem, in Erlang it's a "if that happens, supervisor restarts", and you focus your effort on ensuring restarts put you back into a good state (which you do anyway, ergo, no additional work unless you choose to invest it).
Erlang is a little unique in this regard; it's not that it doesn't have error handling and that static typing might not be a benefit, it's that proper focus should be on the supervisor strategy and "what happens when things go wrong". Of course things that reduce how often things go wrong are helpful (for log consumability if nothing else), but they don't buy you that much if you're doing things correctly; the whole 'correctness' mechanism is separate and distinct from those concerns (whereas most other languages it is not).
Running Dialyxer in VSCode usually tells me if I pass in the incorrect arguments. Sometimes it’s a chore figuring out precisely what isn’t working given the complex typing dialyzer uses (essentially unions). In F# I spent a lot of time trying to make the compiler happy. Some odd invariant that the error didn’t explain well. So there are trade offs both ways. I like the Elixir way, just enough typing to help catch dumb mistakes but dynamic enough that I don’t have to convince the compiler to do something.
> You also forgot to write a test it sounds like. And if it affects your production code, you also forgot to think through your supervisor restart strategy.
With a statically typed language I (or my teammates, or people who write libraries I use) cannot forget to write a test for something like that because the type system ensures that it is caught. I don't have to worry about someone forgetting to write tests, and I don't have to wait until they've got a suitably large test suite to be certain of the guarantees the code can make.
Passing the wrong type in in a dynamic language doesn't matter -unless it leads to the wrong behavior-. If you aren't testing the behavior, dynamic or static language, you're missing a test case. If you ARE testing the behavior, then dynamic or static doesn't matter; the function works when passed that type of data.
> Passing the wrong type in in a dynamic language doesn't matter -unless it leads to the wrong behavior.
At which point, it really matters and then you spend more time and effort checking and fixing incorrect types. My application crashing because some deep dependency wanted an integer instead of a string, and neglected to tell me is an annoying experience that slows me down, and having to write messy code to validate everything ends up getting in the way of actual business logic. Wildly sending variables into functions and just hoping it won't crash or behave incorrectly and then relying on devs writing comprehensive-enough tests to cover everything produces fragile code in my opinion.
Writing and maintaining boilerplate tests just to ensure basic behaviour (before we even get to testing the actual functionality) instead of relying on a type system to simply prevent those issues from ever occurring in the first place seems like a mistake to me.
>> I accidentally pass in 3 params instead of 4 to a function (with static typing this does not compile)
> In Erlang this will also not compile, unless you have a 3 parameter version of the function, in which case static typing will not help you.
This is only true if the function you are calling is part of the same module (and is not called in module:func(Args)-form). If it's not, Erlang will happily compile it and you'll have to rely on xref to catch the error.
This is one of the compromises it takes to get hot code reload.
Have you written code in an ML flavored statically typed language? If not then I highly recommend experiencing it. Your post (especially that second reasoning) reminds me of my pre-ML days when I used to be rabidly anti-static typing.
In theory what you say is equivalent, but in practice, thinking about these things at the design phase is far more easier than to handle them with testing (which is pretty forgiving if you miss things).
I'm not rabidly anti-static typing. If anything, I -prefer- it. I use Dialyzer and type specs when writing production Erlang code. I actually -wish- Erlang had better static typing options.
But I recognize that as -personal preference- when it comes to Erlang. It allows me (and my teammates) to be explicit, and the machine helps me (us) be consistent in my explicitness, even when, again, via experience, I know it doesn't really effect how reliable the code is. Reliability in Erlang comes from testing, and proper supervisor patterns and reasoning. Static typing might reduce the number of supervisor restarts, but it doesn't reduce my test surface, nor the need to ensure arbitrary restarts get my system back to a good state. Even when using Dialyzer, it didn't push the needle on how reliable the system was. It just changed the characteristics of writing/reading the code; a bit more work to write, a bit less work to read.
I am totally with you. I was just saying that for me personally some static type checking would really help, not particularly with Erlang but with other duck typing languages.
How does one avoid type errors in Erlang applications? If the answer is 'testing' then types are indeed an obvious gap in the language. If it's something else, I'm genuinely interested to know. What makes Erlang so special that a lack of types actually aids (or at least does not at all hinder) in long-term maintenance/refactoring or enforcing type-level constraints on values?
After many conversations on the topic with good to great Erlang programmers (I also have 7 years of experience under my belt) I come to the conclusion that they just "don't get it".
Probably there is a subset of extremely smart people who can write good and very reliable software in a dynamic language without going mad, but I am not among them. In the end I just left the language and now I am much happier with a mix of TypeScript, OCaml and Rust
Also the reliability they are talking about is quite different from the reliability that most people (especially ones who have not worked on erlang-y systems) think of when they hear the word. The Erlang reliability is about finding bugs and patching them (often in production) and not about avoiding them in the first place. Also it's about avoiding total system failure because of some bug in a part in it. This is a nice to have thing in all systems, but Erlang makes it easier to implement.
Of course, they also like to avoid bugs, but for that they use testing. And of course, we have to mention dialyzer which is something like TypeScript for Erlang but a lot less polished and with practically no tooling support. It is used in every significant Erlang project but the process is IMO quite sub-optimal and the price you pay (in suffering) is quite high.
'Of course, they also like to avoid bugs, but for that they use testing.'
This is kind of the key bit.
To caveat this - I'd like better typing in Erlang, and when i last used it in production I added type annotations to everything and ran Dialyzer as part of the build. But this is personal preference, and I'm not convinced it was the best use of my time; just the most conservative way to approach things for a system I needed extremely high uptime on.
You have to write tests anyway. Just because a statically typed system compiles doesn't mean your test load is reduced. While it eliminates certain classes of bugs, those same classes mostly get covered just by writing functional tests of sufficient coverage.
Between that, and Erlang's supervision tree (so that if you DO get a type error at runtime, it would be in a path you haven't adequately tested, and so not your expected 'happy path', and as such, can reasonably be restarted), it strikes me as a reasonable position to take.
Regarding dialyzer: Yes, it's sane to use it although it's a massive PITA, especially when you have to collaborate with people who do not write type annotations or use such libraries.
Regarding testing - I think it depends on the type of application you are writing. If you need 99.99% uptime, probably you have to have almost full test coverage.
But for the kind of software that I write (at least lately), I rely on "let's make illegal states unrepresentable" to a large degree, then write a small number of regression tests (or fix the types) when we find bugs... This has proven (at least to me) to be good enough, especially compared to my clients' previous experience, which is usually very poor: extremely buggy software in a dynamic language with almost no tests and docs. Perfect quality is usually not needed or at least not worth the price.
I've never worked in Erlang, but I did use Objective-C for a long time, and switched to Swift almost 5 years ago. Strong types makes it massively easier to ship high quality applications, while I could do so in the C variant it took a lot more effort and testing. In Swift I can make the language make it almost impossible to create erroneous code.
It's not that you can't make good code in any language, but the cost is often not worth it; anything that makes it easier and more reliable saves you time and effort in the long run.
Your last paragraph is why I enjoy F# and rust as much as I do. If the computer can do more to help you make illegal States unrepresentable then why not let it?
Hence I am waiting on Gleam before I invest in Erlangy stuff. In the meantime I will explore the actor model of concurrency with F# Mailboxes.
Yes, for me the big revelation was when I learned OCaml (coming from Erlang) it was such a massive relief, it felt like half of my brain got freed to think about things that matter. After that, once it clicked, most languages of this caliber feel "at home" and I can use them quite easily - F#, Rust, Swift, TypeScript.
Erlang doesn't lack types. It's also strongly typed. You can get quite far with properly chosen guard expressions (`is_binary`, `is_number`), general pattern matching and Dialyzer to check what's checkable.
Static typing severely hinders hot code-reload, which was deemed more important for long running systems.
Just to be clear, I intentionally never said anything about static types. The type system is "strong" in the sense that variables never change their type or are "interpreted" based on context, they don't even change their value after assignment.
And whether your types are runtime-checked or statically checked doesn't really matter with regard to the invariants that hold for your code. In the former case, you'll have to be able to handle type errors at runtime as well, which is reasonably easy in Erlang due to the strong isolation it provides.
Absolutely. Somebody, somewhere used to say that Erlang is neither static, nor dynamic typing, but somewhere in the middle. I can agree to a large degree.
> How does one avoid type errors in Erlang applications?
When I write Erlang, I'm not necessarily concerned with avoiding errors. The philosophy of the system is that errors will occur, and the focus is on making sure errors do not compound into system failure (supervision tree helps with this), that they're easy to see (appropriate logging of crashes), and that it's easy to deploy fixes (hotloading).
I'm sure there are many ways to avoid some errors in production, but more focus is on responding to errors than on preventing them. At least in my experience.
> If the answer is 'testing' then types are indeed an obvious gap in the language
Will you not be testing your code anyways? Will you not have unit tests and integ tests and have it run in a devo environment first? Before going to production?
If so, then what would the gap be? If you catch those same type errors as you test, and you will be testing anyways?
Is your argument for it being a benefit that there are lots of languages doing so? Cause there's just as many dynamic languages not doing so, so I'm not sure this argument takes us anywhere.
From the conversations that I have seen, it seems to just be a comfort level thing. I haven’t seen many strong arguments for it other than “I want it because it helps me a lot in other languages.”
The benefit just isn’t that prominent in a system built for the BEAM from what I’ve seen. And there are a bunch of negative trade offs that make distribution and hot reloading a lot more complicated.
The desire for a strong type system in Erlang/Elixir for me is more about tooling. Getting the types leads to better tools and that will lead to a better developer experience.
Ugh I hope this isn't going to be another php->hack project
Static analysis is great and required for modern development in dynamic languages and CPU/memory intensive to do well
These big companies should write their own LSP server since all editors support the LSP protocol and mandate their use in employee editors
Waiting till compile time to find that typo is rubbish, at runtime and compile time there should be as little checks as possible to keep performance up and the application running but on every keystroke in the editor it should be check city and again in CI
Instead Facebook keeps lagging up their dynamic languages by building incompatible languages with static analysis into the compile step fracturing communities for a worse solution
In the linked slides they say "We are working on a [static typing implementation (what exactly it will look like is still unclear afaik)] prototype, open-sourcing in November"