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.
C++ doesn't need this kind of restriction because it makes the difference between references and values explicit, so it's actually possible to explicitly ask for a vector of values, rather than only being able to ask for a vector of references and artificially restricting the language so that it can be optimised to a vector of values.
(Yeah I know you can't derive from int in C++ but that's for other reasons.)
struct MyStruct
a::Int64
b::Float64
end
Since the size of each field is known (8 bytes), the size of the whole struct, and thus the size of each instance of that struct, is known - 16 bytes. This is crucial information for inlining, loop unrolling, copy elision, deciding which register(s) to place the object in, getting rid of indirections through pointers.. If you'd allow to subtype MyStruct, the compiler wouldn't be able to know ahead of time what the size of a MyStruct object is and would either have to box every access (introducing pointers which have to be chased on access) or defer A LOT of work to runtime. With boxing, you almost immediately lose lots of opportunities to SIMD or otherwise optimize your code, because you have to keep a whole lot of extra type information around at runtime that's just not necessary when all your objects in a e.g. Vector{MyStruct} have the same size anyway.1. the JVM does not support generics as a language construct, and are simply a compiler feature of Java
2. the JVM and Java differentiates primitive and reference types, Integer being a reference (boxing) type of the primitive type int. And, generics do not support primitive types anyway.
The JVM implementations are incredible pieces of machinery, and even if they did not pack or stride their primitive arrays -- which they absolutely do -- the JVM & compilers will handle an incredible amount of runtime memory layout and packaging.
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.
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.
> TSCoding has a 1 min video on why he doesn't program in Haskell anymore
This is very interesting, and as someone who is spending a lot of time trying to bring Haskell to a wider audience it is missing a key piece of information that would help me: what is the link between Haskell being a beautiful and elegant language and it not being engineered properly? I can see (at least) two distinct possibilities:
1. Beautiful and elegant languages can simply never been engineered properly (or it's too hard to be worth trying). There is an irreconcilable tension between beauty and utility.
2. Haskell people are too interested in making something beautiful that they don't bother trying to make it useful as well, but they (or others) could.
[For the record, I don't agree with the presenter's point of view: I find Haskell both the most beautiful and the most useful language I know.]
A person unwilling to learn how to use their tools won't be able to use them with optimal efficiency. Does this have anything to do with Julia, specifically?
If you draw the distinction between Julia the language and Julia the ecosystem, then that's not a damning verdict for the language, even if we accept the premise that you can't trust the computations you mentioned.
The way that Julia composes is unprecedented. Typical examples include computing with measurement uncertainty, or big floats through functions that were not explicitly designed to support those types. This is great, but the correctness of this composition cannot be assumed.
One good proxy for how this composition occurs is in many type combinations. As Here is a concrete, made-up example. There is a function you've been using without any problem that takes an argument of type `DenseArray{Float64, 2}`. (Meaning, the function gets called with an argument of that type. The function definition is not constrained to work on that type.)
If you've never focused on the correctness (e.g. reviewing the code, unit tests) of this function to see what it does on `SparseArray{BigFloat, 2}`, I would not take it for granted that it works.
This is perhaps too big a burden on a user. Some might say they rather not have the compositionality unless it's guaranteed to be correct. But, if you're actually asking me to imagine using Julia in a situation where I really want to be sure about the correctness of a Julia program, I would at the very attempt to characterize all the expected types to go into any function. If at runtime a function is called with a new type, I would log that, and possibly error out.
Note this is related to, but not equivalent to what's necessary to do "static" compilation in Julia.
I think this is it. Or rather, I think to some people, myself included, compositionality should imply correctness. If you /can/ compose things, but the result is wrong, can you really say that you've got composition?
How useful it is really to be able to plug in your own types in other peoples libraries if you have to trace through the execution of that library, figure out which libraries they transitively use, to ensure that all of your instantiations are sound? How do you even test this properly?
It's a really hard problem, and from what I can tell, Julia gives you no tools to deal with this.
[0] is probably relevant here, although I'm not sure I share the positive outlook.
Language and ecosystem are so intertwined that I almost never do this. Maybe when there are multiple well developed ecosystems, but when there’s only one, then it and the language are inseparable to me (for any language, not just Julia).
But the original critique also mentioned several correctness issues in the core language itself, so the issue does seem to go deeper.
Bugs are everywhere - including Python and R libraries. You would not design a safety critical system in Python using libraries and codepaths that are not heavily tested. Even then, you still have to carefully design testsuites that give you confidence that what you are computing is what is expected.
See these talks on how a collision avoidance system was designed with Julia. https://www.youtube.com/watch?v=rj-WhTL_VXE https://www.youtube.com/watch?v=19zm1Fn0S9M
At the same time, correct implementation of statistics is hard and testing it to be correct is harder.
Additional note, we recently did a overhaul of SciMLSensitivity (https://sensitivity.sciml.ai/dev/, as part of the new https://docs.sciml.ai/dev/) and setup a system which amounts to 15 hours of direct unit tests doing a combinatoric check of arguments with 4 hours of downstream testing (https://github.com/SciML/SciMLSensitivity.jl/actions/runs/25...). What that identified is that any remaining issues that can arise are due to the implicit parameters mechanism in Zygote (Zygote.params). To counteract this upstream issue, we (a) try to default to never default to Zygote VJPs whenever we can avoid it (hence defaulting to Enzyme and ReverseDiff first as previously mentioned), and (b) put in a mechanism for early error throwing if Zygote hits any not implemented derivative case with an explicit error message (https://github.com/SciML/SciMLSensitivity.jl/blob/v7.0.1/src...). We have alerted the devs of the machine learning libraries, and from this there has been a lot of movement. In particular, a globals-free machine learning library, Lux.jl, was created with fully explicit parameters https://lux.csail.mit.edu/dev/, and thus by design it cannot have this issue. In addition, the Flux.jl library itself is looking to do a redesign that eliminates implicit parameters (https://github.com/FluxML/Flux.jl/issues/1986). Which design will be the one in the end, that's uncertain right now, but it's clear that no matter what the future designs of the deep learning libraries will fully cut out that part of Zygote.jl. And additionally, the other AD libraries (Enzyme and Diffractor for example) do not have this "feature", so it's an issue that can only arise from a specific (not recommended) way of using Zygote (which now throws explicit error messages early and often if used anywhere near SciML because I don't tolerate it). tl;dr: don't use Zygote.params, the ML AD devs know this its usage is being removed ASAP.
So from this, SciML should be rather safe and if not, please share some details and I'd be happy to dig in.
I'm no language expert, so I can't tell if Yuri's criticisms are just a function of the smaller number of people working on Julia vs a very popular language like Python, or whether the failings he discusses stand out because so many things Just Work in Julia, or whether it indicates some much deeper problem, so I've gone back to look at the original thread - https://news.ycombinator.com/item?id=31396861
I'm interested to hear what others think of Julia's longevity prospects. Its popularity in climate modeling and other scientific contexts suggest it will be around for the long haul, and that Julia projects needn't end up as abandonware for lack of footing. Is that accurate?
I will recommend following the discussion on the Julia discourse here that is focussed on productively and constructively addressing the issues (while also discussing them in the right context): https://discourse.julialang.org/t/discussion-on-why-i-no-lon...
Just like any other open source project, Julia has packages that get well adopted, well maintained, and packages that get abandoned, picked up later, alternatives sprout and so on. The widely used packages usually do not have this problem. Overall the trend is that the user base and contributor base are growing.
I can find you several more, if you want. In the last 2 years I've myself filed something like 6 or 7 correctness bugs in Julia itself (not libraries), and hit at least 2 dozen, whereas I've never found a correctness bug in Python despite using it daily for 5 years.
Right now, you can go to the CodeCov of Julia and find entire functions that are simply not tested. Many of those, and they are in plain sight. And it would take less than an hour to find a dozen correctness bugs that are filed, known about, agreed to be a bug, tractable, yet still not put on the milestone for the next Julia release, which means the next Julia release will knowingly include these bugs.
I just don't know how people can see these facts and still claim Julia cares a lot about correctness. It's just not true.
If you want something actionable, here are three suggestions:
1) Do not release Julia 1.9 until codecov is at 100% (minus OS-specific branches etc.)
2) Solicit a list of tractable correctness bugs from the community and put all the ones that are agreed to be bugs and that are solvable on the 1.9 milestone.
3) Thoroughly document the interface of every exported abstract type, the print/show/display system, and other prominent interfaces, do not release 1.9 before this is done.
Edit: I apologize for implying you were not being genuine. That was uncalled for.
In my opinion, better tooling to assist with such cases would help tremendously. Adding support for interfaces to `Base` would be a great start. What are your thoughts about this?
Also, it's been a while since we've seen a roadmap on what the core team is working on? What are the next big features we can expect from the language and what is the approximate timeline for that? Having answers to these questions would be extremely helpful.
Are you sure? Here are some issues from the post:
"Wrong if-else control flow" seems like a language issue? bug is still open [0]
"Wrong results since some copyto! methods don’t check for aliasing" seems like a bug in a core library. The bug, which is filed against Julia, not some third-party library, is still open [1]
"The product function can produce incorrect results for 8-bit, 16-bit, and 32-bit integers" was a bug in a core library, which was fixed [2]
"Base functions sum!, prod!, any!, and all! may silently return incorrect results" seems like a bug in a core library and is still open [3]
"Off-by-one error in dayofquarter() in leap years" seems like a bug in a core library which was fixed [4]
"Pipeline with stdout=IOStream writes out of order" seems like a bug in a core library and is still open [5]
I've been deliberately conservative here and only posted the issues from Yuri's post that are in the JuliaLang/julia repository. The other issues are filed against JuliaStats/Distributions.jl, JuliaStats/StatsBase.jl, JuliaCollections/OrderedCollections.jl, and JuliaPhysics/Measurements.jl. Since I have not used Julia very much, I don't know whether these are commonly used libraries or obscure libraries nobody uses, but they seem pretty close to the core use-cases of the language. Maybe someone who uses the language a lot more can shed some light on this issue.
Some commenters seem exhausted by what they perceive as a continual stream of lies about these topics, which has left them less inclined to post about them.
[0]: https://github.com/JuliaLang/julia/issues/41096
[1]: https://github.com/JuliaLang/julia/issues/39460
[2]: https://github.com/JuliaLang/julia/issues/39183
[3]: https://github.com/JuliaLang/julia/issues/39385
I don't agree with this as a mitigation. This line of reasoning is why despite steady hardware improvements over the past few decades, responsiveness of (PC) programs and websites have stagnated or regressed.
To the main TTFX issue - I won't consider Julia until this is taken seriously.
I have been testing some performance oriented software recently, and it's amazing how much more productive I feel when every keypress is immediately reflected on screen with no delay. We can adapt to poor performance, but it adds a constant cognitive load to deal with it.
In my mind Julia is suited for limited applications, like doing work in a jupyter notebook, but is not suited for general applications unless the TTFX issue can be fixed.
What about Julia to produce generative art? Is TTFX an issue there? Etc. If "general applications" means _all possible applications_, then I'm not sure any language is suitable for general applications.
Julia has weaknesses which limits what it's good at. Right now I think it has more weaknesses than other languages. Some of these limits will hopefully be removed or reduced in the future. But fundamentally, I don't see Julia as some kind of domain specific or niche language. Just like any language, it just has its own set of tradeoffs.
If your plan is to do bytecode verification and class-loading only once requests are coming in, and to handle the first however many requests with interpretive execution (prior to JIT), you shouldn't be surprised to see poor performance for those first requests.
Now, ahead-of-time compilation in the JVM is becoming more mainstream, and brings the expected benefits to, for instance, start-up time. [0]
[0] https://spring.io/blog/2021/12/09/new-aot-engine-brings-spri...
To be clear, in supercomputing environments people still use old versions of CentOS just to make sure that library version updates do not change their computation results. I don't think many people here would say "I am sticking to Ubuntu 16.04 because I am afraid that the updates to some library like gmplib will slightly change my computation results in a way that is hard for me to detect".
Also, just staying with the old doesn't mean it's correct. You can also introduce bugs to your libs. I think NASA thought this through long time ago and solved it by making sure critical parts of the code are implemented twice using different stacks with different programmers.
If you are NASA, CERN, LLNL, or a bank, maybe it's a good idea to implement your computations once in Python and once in Julia (by at least two different programmers) and compare the outputs. And I don't think in this situation Julia is any different from other languages (other than you may put too much trust into it and skip this dual implementation). Case in point: https://github.com/scipy/scipy/issues?q=is%3Aissue+is%3Aclos...
Doesn’t this negate one of Julia’s main selling points? That it has “solved the two-language problem”. Ironic for them to solve that in the performance domain only to then need a second language to prove correctness.
Maybe that's not what parent was going for, but I think it's like the reproducible/replicable difference in research... can you use the author's code and data, getting the same result... can you use the author's algorithm/pseudocode and data, and get the same result... can you use the author's algorithm/code and different data, and get an _equivalent_ result?
Do you have evidence for that?
I'm a mathematician at a research university, and maybe two of my colleagues are using Julia. Despite their proselytism, everyone else is using Python, C, or math-specific software such as GAP, Matlab, or Mathematica.
In terms of examples of hard sciences where it shines: It is the only tool in existence that has at the same time high-quality differential equation solvers and autodiff on them. Compare DifferentialEquations.jl to any other package in any other language. The rich capabilities of the aforementioned package depend on the multiple dispatch + aggressive devirtualization used in Julia. Python/Jax/Tensorflow/Pytorch while wonderful on their own, are nowhere near these capabilities. Matlab/Mathematica do not have these capabilities. The famous C/Fortran/C++ libraries are also far less capable in comparison.
You need deep understanding of the compiler to know what constructs will mess up performance. Especially how typeinfo is propagated.
Could you expand on this? It's not clear whether you're just talking about issues that would trip up beginner who hasn't read the relevant sections in the manual yet.
Maybe this isn’t a good example of OOP?
Does it suggest the author perhaps doesn't fully understand principles behind loosely coupled OO code?
If is_sold is an attribute of book, the class shouldn't have a sell method, but a mark_as_sold.
The action of selling is not the responsibility of a Book. Baking it into Book will create tight coupling between areas of the code (product and commerce) that should be loosely coupled. The "commerce" side may change for reasons unrelated to the "product" side. Keep'em separated.
And if we think for a minute, is_sold should not be a Book attribute in the first place. It seems to me this piece of information should be handled somewhere else related to inventory, not by the Book itself.
There should be a ProductEntry class, for instance. It could have a product_type and product_sku attributes, for example, pointing to a Book.
Even here, is_sold shouldn't be an attribute, but a method. It would have an availability_status attribute.
func is_sold(self) { return self.status == ProductStatus.SOLD }
Ultimately, the inventory code should not care that it's a Book or whatever.
The logic of whether it's available or not doesn't belong to the product itself. What if someone bought, but returned it? Maybe it will be available for shipping tomorrow and the store wants to start reselling it already? Is it the Book responsibility to track that? No way.
Your critique seems to prove the author's claim.
> "with actual capital S and C." This part was referencing the industry and not me being egotistic.
Creating mutable structs and `GC.@preserve`ing them is an effective means of getting stack allocated memory, so long as the structs do not escape.
E.g., I occasionally follow this approach:
mem = Ref{NTuple{32,Float64}}()
GC.@preserve mem begin
p = Base.unsafe_convert(Ptr{Float64}, mem)
# do things with p, e.g. pass it to a C library
endIf TFA really means "Most of the time if statements take the correct branch, and it's unusual for them to take the incorrect branch, and you should use tests to detect whether the if statements in your program are working" then I would appreciate it if TFA would come out and say that. I am sort of used to programming in environments in which if statements work 100% of the time.
This is in no way specific to Julia.
I think these features are great, but on their own they lead to exactly the situation as described.
FTFY.
Tests are code. This code needs to be maintained, since it's coupled to your architecture. This code has bugs of its own. Etc.
Writing tests is a fine choice if you have the resources, and also isn't a valid answer to a criticism of the language.
The author is not addressing the couplings in their code itself, but the code couplings between their code and all libraries in the Julia ecosystem, which is an altogether more audacious goal.
I can't imagine anyone writing a post along the lines of, "Why I still recommend SQL for querying databases", even though SQL was conceived back in the 70's. "Still", in this context more often than not suggests that a grasping-at-straws is taking place.
Julia has a lot of neat features, but at the end of the day, it's a research language rather than a getting things done language.
Can you explain the causal connection here?
It's challenging enough that Abstract Types have no interface specs (it's claimed that this is a feature, allows interfaces to develop over time), but it's even tougher when things get even looser with this new trend towards have untyped functions...even various interface package maintainers themselves have said the current state of affairs is difficult.
And if we do want to opt into abstract typing, you're going to be inheriting lots of functionality that could be better broken into smaller typeclass like components (see AbstractArray).
I think Julia is screaming for some capability to break up Abstract types and being able to inherit from multiple kinds of things, along with specified interfaces with these things.
The problem is that this is very difficult, given the complex subtyping system and multiple dispatch. Adding more complexity exacerbates already present method and typing decidability issues. People are working on this on the periphery, but I hope it can become a priority of the core team and that it's solvable.
That's not the idea. You put the code for the "internals" of a book near to its definition. In other words: The code that needs to see the private/protected members of a class. It is not good OOP - if I may be so bold as to use that phrase - to stick as much functionality as you can involving some class, into that class.
1. better database support, also need to support BI Cubes likes MS SSAS
2. fix scoping rules
I created a small feature request on github, where I suggest a fix for their scoping (inspired a lot by perl), and I would like to take this opportunity to promote my suggestion, I think its a good one, that will make Julia nicer