3 ms·
I'm copy pasting my usual comment about types in Ruby: if I'd want static types I'd be using one of the many static typed languages out there. One among the man
by pmontra 4y ago
I'm copy pasting my usual comment about types in Ruby: if I'd want static types I'd be using one of the many static typed languages out there. One among the many reasons I like Ruby and Elixir is that they are dynamically typed.
I'm happy to see that the official type annotation system for Ruby is basically hiding them under the carpet by writing them into a separate optional file where they hopefully go to die in the dark. The @spec line in Elixir is just not DRY and inconvenient. Sometimes you fail to match it to the actual function definition because typos happen.
I add an anecdote from the largest Elixir project I've been working on. One of the original developers added @specs to all the modules he developed. The other two of us did that too for many reasons. We're all senior developers but he's more senior and we'd like to see how it turned out. That developer left the project pretty soon, with some appearances now and then. New developers joined the project, mostly junior. After five years no new code has @specs AFAIK.
I remember a couple of cases where static typing would have spared us a half hour of debugging. No type declaration saved us hours of typing and thinking though. I can't think about a single test that we wouldn't have to write if we had static typing because we're testing for functionality, not for input and output types.
Finally, pattern matching in function definition is a great substitute for static typing because it limits the shape of the data that code can work on. There is no way not to have two Person arguments in function with a declaration of
def marry(%Person{name: name1}, %Person{name: name2}) do
IO.puts("#{name1} and #{name2} are married")
end
You can't marry a dog and a cat there, or an int and a string.
- nagasadhu 4y ago> if I'd want static types I'd be using one of the many static typed languages out there. Some of us want to have the concurrency and resilience of the BEAM with ADTs for data modelling and other static type advantages. Caramel, gleam and purerl are worthy efforts in this direction. A more production ready half-ass alternative is to use elixir with whatever type advantages you can get.
- pmontra 4y agoIn an ideal world you would be using a static typed language on the BEAM. Maybe there are some. I'm not saying that you should look for them or create one, because there are obvious advantages in using a language with a large community and creating a new community is a years long process, apart from the technical complexity of designing and implementing a language. You and I are probably paid for something else.
- conradfr 4y agoThere's https://gleam.run/ https://gleam.run/
- nagasadhu 4y ago>obvious advantages in using a language with a large community and creating a new community is a years long process, apart from the technical complexity of designing and implementing a language All projects including elixir started as one man's itch and turned to large communities. It has to start somewhere... Much of what you and I use today wouldn't exist if people only worked what they were paid to do.
- throwawaymaths 4y agoI am pessimistic on all three of those projects because they are applying h-m (or similar) type systems to the BEAM. The BEAM needs it's own type system. Dialyzer is basically a half-assed project, it's so frustratingly close to what is needed (subtractive types, and better support for maps and multipart function headers)
- dmitriid 4y ago> @spec line in Elixir is just not DRY and inconvenient. This. So many times this. If specs could be inlined it would be so much less hassle (but might hurt readability because of pattern matching)
- throwawaymaths 4y agoYou can use @spec as a public API declaration at the top of the module by bunching them together away from the functions themselves.
- dmitriid 4y agoI prefer to have my specs as close to the function as possible :)
- throwawaymaths 4y agoOpen your mind.
- conradfr 4y ago> @spec line in Elixir is just not DRY and inconvenient. I'm guilty of regularly copy/pasting @spec from another function and forgetting to change the name, it's very inconvenient. I wonder how much work it would be to allow type hints and for Elixir to transform them to specs (and maybe guards) at compile time. Having made the transition in PHP years ago it always feels a bit archaic to write specs again. Especially as you kind of doing it already when pattern matching a struct parameter.
- ashton314 4y ago> The @spec line in Elixir is just not DRY and inconvenient. I disagree. The spec is part of the documentation of the function. I find code with specs is 10x easier to read and safely modify/refactor when you better understand the function contract. See also: Typed Racket, Haskell where the types are often specified just above the actual implementation. If you don’t like it, I doubt I’ll change your mind. :) I don’t think it’s that bad though. There are plenty of times when you can make the specs really expressive, much more than just pattern matching (which is great, especially when combined with guards) eg: (on phone, might be a like off on syntax) @spec find_if([t1()], (t1() -> boolean())) :: t1() | nil when t1: any() Function types are opaque to pattern matching and you can’t express return types in patter matching either.
- pmontra 4y agoI had to look at that spec for a while but I got it. Of course I'm out of training. More than that, there is the self selection bias: the main reason for I'm using dynamically typed languages is that they don't have type declarations so I'm never happy to see static typing get close to them. BTW I definitely lean towards strong typing, so 1 + "1" is an error (Ruby) and not 11 or "11" (Perl and JS.)
- ashton314 4y agoWell, Perl actually would return 2 in that case because the + turns it’s arguments into numbers… ;-) Yeah, I’m all for strong typing too. Most specs aren’t as complex as the one I just threw up there. What I really like specs for is to tell me whether or not the function is meant to return a maybe tuple ({:ok, val} | {:error, “err”}) or if it will throw an error. Sometimes that’s not obvious from bigger functions, and it makes refactoring harder. My ideal would be something like Typed Racket’s gradual typing, which would let you get static types around eg core modules that see a lot of use and where you use the types to check your assumptions but non-core modules can be untyped.
- brightball 4y agoI share your perspective here. I use dialyzer on my Elixir projects but only to let it check what it can infer on its own without me having to add specs. It can find a ton on its own and I appreciate the automated set of eyes. But I get so much from Elixir pattern matching without needing to bother forcing types everywhere that I have a hard time understanding why people feel like they need them so much?
- bmitc 4y ago> I have a hard time understanding why people feel like they need them so much? How do you figure out types in a code base you didn't write or don't know? Because when you're working in a code you wrote or have worked on extensively, you have the typespecs in your head, but everyone else doesn't. And I'd even wager there's holes in what you have in your head and thus potentially in the code. I've worked on Erlang and Elixir code bases, and without typespecs, you spend an inordinate amount of time learning and tracing what the expected types of arguments are and what return types are. That time and effort is completely saved if there are good typespecs. When I read a function, I do not want to spend anytime understanding the types. Typespecs are an absolutely necessary evil in a dynamically typed language. It's part of the tradeoff.
- brightball 4y agoI've spent 20 years working in dynamically typed languages and I've never needed to spend that much time understanding the types of functions. 90% of the time the types are simply an extension of the database and enforced strictly at the database layer while everything else is just data pass through anyway (in Ruby, Python, PHP, etc). You understand the database, you understand the application data. With Elixir you get so much added clarity just from pattern matching and guards that you end up with a Strongly typed code base, just not Statically typed. For an application backed by a relational DB, strong typing is more than enough for almost every use case I've ever come across. Now, if there's a lot of data work happening inside the application that's not a direct extension of a database then the value of static typing goes up significantly. I really think it's just one of those areas where people come at problems differently. Some people love it and swear by it. For those people, I think the lack of static typing causes a real gap in their entire approach to programming which translates to frustration and seeing a lot of "this wouldn't have been a problem with static typing..." experiences. For the people used to dynamic typing, static typing adds a lot of unnecessary complications and headaches without much benefit. Suddenly you have to ensure your types are in sync with your database at all times. You have to enforce them at many different locations in your code base. You've got to define a lot more structure just to translate arguments from web forms, APIs, JSON, etc. If all that overhead saves your bacon later, great...but for a lot of people used to dynamic typing the benefits just haven't been there to outweigh the negative tradeoffs.
- bmitc 4y agoPattern matching in a function definition is a great way to get good runtime error reports (I'm thinking of Elixir here, not sure how Ruby does it), but it does not help the developer during development. I don't want to look at a function's definition to understand how to use and shouldn't have to. It totally breaks the point of having a function in the first place. When using a function, I want the name, the name of the arguments, and the typespec, whether it's in a statically or dynamically typed language. Anything more is cognitive overhead that is unnecessary and even likely to create improper use, two things that introduce bugs in a system. In a dynamically typed language, I'd wager that 95% of the code doesn't need to be dynamically typed. In that, almost all of the time, you have code that could easily be done in a statically typed language and modeled well with custom types and type aliases like what Elixir provides with `@type`. The choice of using a dynamically typed language provides some freedom at the boundaries of programs. In functional languages like Elixir and Erlang, these happen when side effects take place like file I/O and also in message passing. The choice to use a dynamically typed language does make things a little easier here, but the tradeoff should be that you should think and program the rest of the system as if it's a statically typed language. Given that something like Elixir is not statically typed and especially does not have type inference, the tradeoff is that now you need add typespecs to your code. This is to help prevent type-related bugs and to help communicate the code to yourself and other developers. > New developers joined the project, mostly junior. After five years no new code has @specs AFAIK. I'm not sure what your point is here, but that sounds like a negative direction. Dynamically typed code without typespecs increases developer cognitive load, increases the likelihood of bugs, and increases the difficult of on-boarding new developers.