10 ms·
I've used Elixir since 2015 and I find Elixir to be unusable for any kind of intelligent domain modelling, but that's primarily because it's dynamically typed a
by 59nadir 6y ago
I've used Elixir since 2015 and I find Elixir to be unusable for any kind of intelligent domain modelling, but that's primarily because it's dynamically typed and has no concept of real sum types, etc., not necessarily because it's any worse at this than Ruby.
Any codebase beyond fairly small will be harder and harder to work with to an unreasonable degree, in my experience, and any perceived "velocity" gained from the dynamic nature of it is paid for doubly so by the lack of safety you get beyond toy projects.
I'm only slightly more for Erlang as a choice mostly because it's simpler than Elixir and doesn't have as much obfuscation of logic added to it that Elixir does, but in reality it's also a bad choice for any bigger system. The runtime is absolutely great, but the languages on it are generally not good enough.
- kerkeslager 6y agoI'm a bit confused by this comment. Having used both Ruby and Elixir (more Ruby) both languages have type systems which are quite capable of modeling domains[1]. They lack static type checking, but I find that a thorough test suite catches most type errors anyway. And while creating a thorough test suite is costly, static types in most languages[2] don't reduce this burden much: there isn't an alternative to thorough testing. [1] This looks fine to me? https://elixir-lang.org/getting-started/typespecs-and-behaviours.html#defining-custom-types https://elixir-lang.org/getting-started/typespecs-and-behavi... [2] I have limited experience with Haskell, but my impression is that the type capabilities do actually allow you to forego testing in a lot of cases. The next best static type system I've used is C#'s, which I used professionally for years, and in C# I think automated testing is still very necessary. EDIT: I regret even talking about type checking here--that's not my point. My point is that for modeling domains, Elixir's type system is totally adequate. I'll agree that Elixir's type checking could be better, but that's not what the comment I was responding to said.
- ed25519FUUU 6y ago> They lack static type checking, but I find that a thorough test suite catches most type errors anyway. Maybe ruby is different than python in this regard, but with python I see a lot of “assert isinstance(arg, dict)” in functions which would not be necessary with type checking. Feeding an unexpected type into a function, and having it carry on as normal, is really scary.
- neilwilson 6y ago"Feeding an unexpected type into a function, and having it carry on as normal, is really scary." That is how duck typing works. Generally you shouldn't use "is instance" but more use a set of "callable" checks to make sure the structure has the required functions. Things like Go Interface types are, of course, a formalisation of duck typing and is what has helped static typing come back into fashion.
- kerkeslager 6y agoI've seen this too, but I tend to think of it as an antipattern. The fact is, in most cases a Python function won't carry on as normal if you feed it an unexpected type, because while Python's type system isn't static, it is fairly strong. In general I am a fan of strong types and I would like it if Python's type system were a lot stronger. But ultimately this isn't what my post was about: I'm talking about modeling your domain with types, not type checking.
- alexilliamson 6y agoWhat do you mean it's fairly strong? Not challenging you, I'm just trying to get my head around all the type stuff.
- hwayne 6y agoMost of the time Python doesn't do implicit type coercion.
- kerkeslager 6y agoAs hwayne said, most of the time Python won't do implicit type coercion. What this means is, if you try to do something that doesn't make sense based on the types, it will throw an error rather than go on. For example: ~/$ python Python 3.7.4 (default, Oct 12 2019, 18:55:28) [Clang 11.0.0 (clang-1100.0.33.8)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> hello = 'Hello, world' >>> one = 1 >>> hello + 1 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can only concatenate str (not "int") to str To put this in context, type systems can be roughly divided into 4 categories along the strong/weak and static/dynamic axes: 1. Strong static types (makes a lot of assertions about type, checks these assertions at compile time; Haskell, ML, OCaml, Rust, C# are reasonable examples--Haskell folks would probably laugh at me including C# here, but it's the only example in widespread non-academic use). Example (C#): var hello = "Hello, world"; var one = 1; var result = hello + one // Doesn't compile 2. Strong dynamic types (makes a lot of assertions about type, checks these assertions at run time; Python is the best example I have, but to be honest, it's not a great example--I think the type system could be a lot stronger). See the Python code above for an example. 3. Weak static types (makes few assertions about type, checks these assertions at compile time; C, C++ are good examples, although it's arguable that C actually does more checking at run time than at compile time). Example (C): char* hello = "Hello, world"; int one = 1; char* result = hello + one; // Happily compiles 4. Weak dynamic types (makes few assertions about type, checks these assertions at run time; JavaScript is a great example of this). Example (JavaScript): >>> var hello = "Hello, world" undefined >>> var one = 1 undefined >>> hello + 1 "Hello, world1" In general, I have a slight preference for static types over dynamic types, but I think that difference is overrated. I care a lot more about my preference for strong types over weak types.
- james-mcelwain 6y agoWhen people refer to "typing", it is almost certainly reasonable to assume they are referring to static typing, as implemented by most languages. I don't think this should be confusing. Specs/contracts are cool but ultimately don't afford the same kind of descriptive and expressive power that a static type system does. > static types in most languages[2] don't reduce this burden much: there isn't an alternative to thorough testing. However, there definitely is a burden about how much testing you have to write. I generally don't want to have to test every branch of my program to make sure a string doesn't slip through where an int should be or that variables are initialized and not null, etc.
- alexbanks 6y agoI see that language parroted all the time - "With thorough enough testing a dynamic language shouldn't be a problem", and I have never understood it. Arguing to build what is essentially a build-time type checker in the form of automated tests seems twice as cumbersome for half the benefit. Instead of building tests that check each branch of a program's types, why not use a language that forbids dynamic typing? You should still have tests, but IMO tests that are just checking that a string is a string are A.) Time consuming, and B.) largely useless beyond type validation.
- kerkeslager 6y ago> You should still have tests, but IMO tests that are just checking that a string is a string are The correct completion to this sentence is "irrelevant.", because that's not what anyone is proposing. The fact is, behavioral tests catch a lot of type errors even without intending to, and more to the point, if you test all the behavior you care about, then you don't care if there are type errors, because they only occur in situations where you don't care.
- _t0du 6y agoMy personal real life experience with Ruby developers disagrees with you, but I accept that I could just have experienced a set of developers that weren't very good at testing.
- 59nadir 6y ago> I'm a bit confused by this comment. Having used both Ruby and Elixir (more Ruby) both languages have type systems which are quite capable of modeling domains[1]. They lack static type checking, but I find that a thorough test suite catches most type errors anyway. Most of this is incorrect, both for and against your argument. Elixir has a very incapable and incomplete type system. It's optional but can be checked via `dialyzer` but never confidently and exhaustively checked because `dialyzer` will sometimes plain not work, sometimes be foiled by bad library specs and sometimes fall back to "Everything must be OK because I can't prove this incorrect". All in all it's not a useful substitute for a real type system used to model things. Type specs aren't useful for domain modelling but serve mostly as extra documentation more than anything. Testing is important no matter what language you use (except maybe dependently typed ones, I don't know?). It's not a substitute for a good type system. Even disregarding type-directed property tests with automatically generated test cases, there's a lot of value to be had in tests that actually just test logic, not basic types.
- deleted 6y ago[deleted]
- mapcars 6y agoErm, you can setup dialyzer to be as strict as possible. This might not catch all the errors because of the nature of messages, but this is a pretty small part of a system usually and can be covered by tests. >All in all it's not a useful substitute for a real type system used to model things. >Type specs aren't useful for domain modelling Can you give me some examples?
- 59nadir 6y ago> Erm, you can setup dialyzer to be as strict as possible. No. This is just plain false. Having used dialyzer since 2016 I can tell you it's not useful as a substitute for statically checked types via a compiler, with real strictness (which includes "When I don't have enough info, that's a type error"). > This might not catch all the errors because of the nature of messages, but this is a pretty small part of a system usually and can be covered by tests. Not only will it not catch obvious type errors, it will also report false types when the core team doesn't use dialyzer in their libraries (because it doesn't work). These will bubble up to you instead and while you're mad they're not using dialyzer you'll still have to admit you understand why they don't. I'm not sure what you mean by give you examples of how Elixir type specs aren't useful for domain modelling. They're ad-hoc, serve as documentation at best and even if they had structural power enough to express basic things they aren't checked at all, so you're getting none of the guarantees you would get in a statically checked language where your model changes and you can safely go forward by seeing what needs changing additionally to adapt.
- neilwilson 6y agoType systems are really a set of comprehensive tests you don't have to write yourself that cover a subset of the things you need to test for. Static languages fell out of favour last time round the dynamic/static merry-go-round because the typing started to get in the way. You ended up writing more type boilerplate than actual code - and not breaking your methods/functions down into smaller functions to avoid having to write out even more type boilerplate. I often wonder whether type annotations that allow a processor to generate tests that can integrate into your test suite would be a useful thing to have. Sort of like a C preprocessor. Then you could add ML type headers to Elixir functions and get a set of ExUnit tests back.
- 59nadir 6y ago> I often wonder whether type annotations that allow a processor to generate tests that can integrate into your test suite would be a useful thing to have. Sort of like a C preprocessor. We already have this in things like `Arbitrary` type classes, `QuickCheck`, `Hedgehog`, etc.. Hundreds of automatic test cases generated so that you don't focus only on "I thought of these 5 test cases" type of testing. There is a PureScript compiler called `purerl` for the BEAM which can be used to write BEAM code in a ML derivative. It works and I personally would use it if I had to deliver BEAM code next week. For the most part it's the answer to the "you can only write BEAM code in bad languages" issue.
- kerkeslager 6y ago> Type systems are really a set of comprehensive tests you don't have to write yourself that cover a subset of the things you need to test for. Maybe in a very strongly-typed language, but having used a pretty wide variety of statically-typed languages, I can confidently say none of them had strong enough type systems that the assertions made by the type system could be considered "comprehensive". You still need a test suite.
- crazydoggers 6y agoI agree, but I think it’s a common misconception. Having used many languages, both for very large and small projects, languages with compile time static type checking and run time dynamic type checking, I find it’s easy to rely too much on static type checking. I think what happens for a lot of programmers is that static type checking uncovers a lot of basic bugs and typos, so they feel like it has a major advantage over dynamic. The problem though is that it’s a false sense of security because you’ll always end up with complex logic bugs that no compile system is ever going to catch, so you end up needing test suites anyway. Static type checking does have some advantages, it can ease some of the low level testing burden. For instance, using a tool like Dialyxir (dialyzer) is helpful in elixir to give you that extra security of static type checking. For me it’s always ended up being that I’m more productive with dynamically typed languages and the flexibility they offer.
- 59nadir 6y ago> Static type checking does have some advantages, it can ease some of the low level testing burden. For instance, using a tool like Dialyxir (dialyzer) is helpful in elixir to give you that extra security of static type checking. Dialyzer isn't an answer to static type checking. It barely works half the time and cannot be relied on to check things exhaustively. It's so bad that even libraries created by core contributors have type spec errors in them regularly. Why? Because they don't use it, because it's not reliable. Both `Ecto` and `StreamData` had these issues several times and I can only assume you could find many more if you were even more invested in using larger parts of the eco-system.
- eropple 6y agoThis may sound like a dismissal, and in a way it is, but: get a better type system, and really understand it. The kinds of logic problems that you describe can be greatly limited—without wasting one’s time—with a more expressive type system. This is one of the reasons I lean so heavily on TypeScript today; I can express my data in the forms it actually should be expressed in, pretty easily, and use that to constrain the logic involved. I can then use features like assert guards (“if this method returns then the listed argument must be type T”) and type guards (if returns at all, the single passed argument must be type T”) to strengthen my understanding of the processes resulting in that data. I can then use a much thinner and much faster to write set of unit tests to proof cross-cutting concerns and integration tests to prove the end-to-end case. It’s not a false sense of security; it’s automation to cut out the stupid bottom 75% or so of boring-to-write, disastrous-to-screw-up test garbage that we all have better things to do with our time than duct-tape together. (Also, Dialyzer is pretty bad by comparison to a real type checker. At best it’s spotty and inconsistent, it fails open in weird ways, and libraries rarely implement it. Elixir has an argument for “the least worst dynamically-typed language in common use” but Dialyzer ain’t it.)
- dnautics 6y ago> Any codebase beyond fairly small will be harder and harder to work with to an unreasonable degree, in my experience, and any perceived "velocity" gained from the dynamic nature of it is paid for doubly so by the lack of safety you get beyond toy projects. How does pagerduty deal with it, I wonder.
- AlchemistCamp 6y agoOr Discord.
- joelbluminator 6y agoOr Gitlab
- cplanas 6y agoOr Shopify. Or Zendesk. Or GitHub.
- bitwize 6y agoUnit tests. Lots and lots and lots and lots of unit tests.