5 ms·
The example using Measurements.jl only works as a good example for composibility if you use `Number` as the type for the argument. If you happen to use Int then
by blindseer 4y ago
The example using Measurements.jl only works as a good example for composibility if you use `Number` as the type for the argument. If you happen to use Int then it doesn't work:
using Measurements
plus_one(x::Int) = x + 1
plus_one(measurement(1))
ERROR: MethodError: no method matching plus_one(::Measurement{Float64})
Closest candidates are:
plus_one(::Int64) at REPL[2]:1
Stacktrace:
[1] top-level scope
@ REPL[3]:1
This is perhaps my biggest annoyance with Julia. Concrete types cannot be subtyped, and hence cannot be extended by other packages. And explaining to researchers and scientists that they need to use abstract types to make their code more composable is exercise in frustration. I think computer scientists with experience programming in C++ may have good intuition for when to use concrete or abstract types in the function signatures, but research scientists in ML and optimization (in my experience) just don't do a good job of that. And just end up having awful Julia code to work with, that isn't really extendable. In my opinion, readability suffers greatly when you have to understand the type hierarchy in order to find out if your code will MethodError or not. For example, if the author use `plus_one(x::Rational) = x + 1` instead of `plus_one(x::::Number) = x + 1`, would the code that uses Measurements.jl have worked? Who knows just by looking at the code. It turns out it doesn't work and there's a MethodError. The built in type hierarchy is great, but third party packages are hit and miss.
Honestly a simple solution to this would be to allow concrete types to be supertypes of other types. My understanding is that there's no compiler related reason for this, and this is just for "good practices" but for the life of me I don't understand why this limitation was made. I've seen Julia code that creates a supertype abstract type for every concrete type they create, and it is just awful to deal with. Fortunately, it is somewhat easy to write a macro to make this happen, but still very annoying that one has to do this at all.
Combine that with the lack of a file system package modules (like in Python or Rust), the lack of any kind of doctests, the built in testing being so lackluster, you end up with really poorly organized code.
TSCoding has a 1 min video on why he doesn't program in Haskell anymore [1] and I can't help but feel his reasons directly apply to Julia too.
For example, in order to run that example for this comment, I had to run `add Measurements` and it took 7 minutes to download the package. Why is the "registry" for packages in Julia a giant git repository? It takes SO long to update every time. This is an example of a poor software engineering decision in an otherwise elegant language.
[1] https://www.youtube.com/watch?v=SPwnfSmyAGI https://www.youtube.com/watch?v=SPwnfSmyAGI
- oivey 4y agoIf you drop the type annotation in your example then it works fine. There's not really a good reason to provide a type annotation to that function. I just installed and used Measurements.jl in <10 seconds. Newish versions of Julia (1.6? the LTS?) don't distribute the package registry as a git repo.
- leephillips 4y agoExactly. There’s no reason to write the function that way unless you’re trying to break things, or to write a function that can’t be used with other functions.
- blindseer 4y agoMy approach has been by default to never use types in functions, and if I use types, use the most abstract type possible, only in order to trigger dispatch. 1) It is just hard to explain this to people that work under me. 2) types in functions are no longer useful to understand the code or improve readability, since the purpose of types is only for dispatch. I guess my dream request would be an alternative syntax for typing for concrete and abstract types. Abstract types would be used for dispatch and composibility. Concrete types should be used because the function is being specialized for that concrete type. There's lots of nuance here though, and I've really struggled to communicate this to colleagues and peers.
- oivey 4y agoFair enough. I'm not sure what fueled this idea that marking variables with types will make the code faster. Cython? I think part of the issue, too, is that in Python type annotations are really just fancy comments. If you mark a function as taking ints in Python and stick a double in, at most your editor or mypy will complain. If you do it in Julia, it's a compilation error. Tangent: JET.jl might catch these sorts of issues for you and I think has linter support in VS Code now or imminently. I'm not sure being able to subtype concrete types will help with the specific issue you're showing here. Really the right thing to do is make the function take Numbers. If you allowed Int to be subtyped, things would still be semantically wrong, and you would have introduced a parallel inheritance tree that will break a lot of the interoperability multiple dispatch enables.