Why I still recommend Julia
huijzer.xyz
huijzer.xyz
At the same time, correct implementation of statistics is hard and testing it to be correct is harder.
One of the working groups is dedicated toward submissions: R Submissions Working Group [1]. They recently announced: "The R Consortium is happy to announce that on Nov 22nd, 2021, the R Submissions Working Group successfully submitted an R-based test submission package through the FDA eCTD gateway! The submission package has been received by the FDA staff who were able to reproduce the numerical results." [2]
[0] https://www.r-consortium.org/all-projects/isc-working-groups
[1] https://rconsortium.github.io/submissions-wg/
[2] https://www.r-consortium.org/blog/2021/12/08/successful-r-ba...
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.
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.
Also depends what you count as correct, see undefined behavior.
I feel that should be the job of the authors to think about what correctness means for their application, and under what circumstances they can give a correct answer. Ideally, also warn the user when the inputs don't fall within that domain or give them options to handle such cases.
Let's talk about an example: say you have a neat algorithm for fast multiplication of sparse matrices, approximately correct up to a defined Frobenius error. Say we have some reasonable treatment of dimension mismatch and NaN, inf,... with option flags already. Now if I wanted to know whether your algorithm has the same numerical guarantee for a dense matrix, I'd have to read your paper, run some tests, ask around, and I might never be quite sure.
It seems much more sane to put the onus on the authors to be explicit about the behaviour in that case. Your and others' comments seem to put the onus mostly on the user for thinking about the behaviour of others' code. That does not seem like a good practice, especially in the context of complex math and nested composition.
plus_one(x::Number) = x + 1
function plus_one(x)
@warn "untested type usage of plus_one"
x + 1
end
That's cumbersome, but a macro could make it less annoying.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.
- easy unrestricted composability
- lack of well-defined interfaces
- lack of an effective way to use the strong typing system to validate correctness
is not a very good idea.
Some formalized notion of "interface" is certainly important, but it seems no clear formulation has emerged.
I think it's fairly consistent with the dynamic nature of Julia and fairly inconsistent with the static nature of Julia. I don't know what a good solution to the interface problem is.
I think one can get pretty far with writing tests to check interfaces. If you are a library that expects user-defined types, you can expose an interface to these tests so that a user can check if they've implemented everything.
This is a very generic approach, and aside from the key limitation of only giving results at runtime, is much more powerful.
If you don't want composition, then there's no issue and Julia can be as weak/strong as other languages.
If you do want composition, then I see two ways (but I'm sure there are others): you do the "typical" thing with glue code or you use the more "automatic" way that Julia provides. Which one is better? If this is too subjective: Which one is more correct?
Yes, Julia can propagate errors in unexpected ways, but how would you implement this in another language? You'd probably have to spend X hours writing glue code and Z hours writing tests to make sure your glue code is working. This also raises issues with maintainability when one of the two packages you're connecting /composing changes.
Julia offers a reduction on the X hours for writing glue code (sometimes with X = 0) and maybe a similar time Z writing test code. The maintainability, I'd argue, becomes easier.
The cost is the unknown unkowns that might creep up when doing this composition. My (extremely) subjective sense is that this doesn't happen that often to me (I don't usually pass approximation intervals to sparse matrix to auto differentiation to neural networks), which means the benefits outweight the cost in this regard. YMMV
Edited a few things for clarity
Two points
1) the glue code you HAVE to write to make the composition in other languages and the mantianability issues you get also may introduce wrong results 2) MAY doesn't mean it will and we trade-off speed / convenience and risk in many other areas. Surgeons (or the health system in general) do trade off % of success for cost / speed, just because they don't have the resources to do everything / spend 100 hours on everyone
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.
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
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?
Then when someone's discovered something new and useful, the focus turns towards making fast and readily available implementations in commonly used frameworks. Like Torch, which you can use both from PyTorch and from Julia.
sciml.ai/
More broadly, I’d say Julia has more potential than Python when it comes to automatic differentiation. JAX is really cool, but Julia’s automatic differentiation tools are targeting the whole language without modification and restriction (outside those imposed by calculus). Potentially some really fancy stuff could get embedded in neural nets beyond differential equations and with relatively little effort.
And while SUNDIALS does have an adjoint implementation, it does not construct the vjp through reverse mode so direct (naive) usage is really lacking in performance. Yes I know that there is a way to define the vjp function in the docs, but it's buried enough in there that all of the examples I've looked at of SUNDIALS in applications in the wild (like in AMIGO2) don't make use of it so there's a real performance loss seen by end users for many cases. DiffEq of course automates this.
Don't get me wrong, SUNDIALS is really good, still bests the Julia solvers on the distributed case, and there is a small performance advantage to CVODE in the one doc example on the 2D Brusselator after adding iLU preconditioning (https://diffeq.sciml.ai/dev/tutorials/advanced_ode_example/ and the low tolerance case of the Filament benchmark (https://benchmarks.sciml.ai/html/MOLPDE/Filament.html, though the medium tolerance case is bested by ROCK4). For years DifferentialEquations.jl used wrappers to SUNDIALS as a crutch for the cases where SUNDIALS still outperformed it, so nowadays it both has a fairly complete wrapper of SUNDIALS but also has benchmarks of different algorithms outperforming it.
We've been benchmarking against it weekly for years now, and within the last two years we've really seen a reversal so now it's a rare sight to see SUNDIALS perform the best. And there's a lot of momentum too: new parallel solvers, new GPU solvers, etc. are coming out from both crews so there's a fun arm's race going on right now, so we will see how it evolves (I definitely want to wrap those multirate methods ASAP and play with the theory a bit)
> Btw. several "research" papers at machine learning conferences on "Neural Differential Equations" presented "discoveries" covered in the documentation of Sundials https://sundials.readthedocs.io/en/latest/cvodes/Mathematics....
Of course the Julia folks know this because we were the ones mentioning this at conferences, since we would routinely choose to benchmark against SUNDIALS implementations instead of random unstable ML ones.
That’s the moat Julia has here. A full rewrite of the differential equation ecosystem wasn’t necessary in order to support automatic differentiation.
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.
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.
Edit:
It looks like abhimanyuaryan is blindly spamming suggestions to write tests in this thread (???). This is a case where I agree in principle but their comments are completely beside the point and not relevant to the context of the comment or the post.
Maybe one approach would be for the community to create some certification tests. Although this wouldn't help with corner cases it would allow packages to run some testing that (if the tests are good) might throw up problems with constitutionality? Also if there were bugs from composition the certificate could be suspended until they were fixed.
In general, we've not had a formal roadmap - but we present a "State of Julia" talk at JuliaCon every year. But very broadly, the list (of the top of my head) includes: improving a lot of the underlying compiler infrastructure overall, improving support differentiable programming, improving garbage collection, support for GPUs from multiple vendors (too many of those now), supporting apple silicon, type system support for tools like JET.jl.
NEWS.md is generally updated during the course of a release cycle, which eventually becomes release notes, and then post release, we put together a highlights blog post. https://github.com/JuliaLang/julia/blob/master/NEWS.md
regarding "improving a lot of the underlying compiler infrastructure overall"
Is the compiler plugin project still active/ planned?
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
From the linked stuff, only [0] is a true correctness problem of the language and not a bug in a library.
> 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.
How is differentiating bugs in the language (parser, compiler, type system, ...) from bugs in libraries implemented in the language "lying about these topics"? It's not like julia is claiming to prevent logic bugs, or am I missing something.
Edit: "certain libraries when composed with common operations had bugs" also does not seem like a completely on-the-level way to describe bugs in the stdlib that do not require any particular composition to encounter.
println(flag)
if flag
println("flow for true.")
else
println("flow for false.")
end
prints false
flow for true. false
flow for false.
How did you get your output? Your post implies that if statements are broken in Julia. Untrue.To me, this is a long and complex discussion to be had by those that understand general programming language design and the case for Julia itself and frankly statements like “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.” are bloody cheap, unfalsifiable, and adds little to nothing to the discussion.
For things like "The majority of sampling methods are unsafe and incorrect in the presence of offset axes" I agree that this is just some unfortunate combination of library code and concerns that the library authors had not considered, but the numerous correctness issues in the language and stdlib seem like they would often make it hard to figure out what exactly the problem is.
> bloody cheap, unfalsifiable, and adds little to nothing to the discussion.
Sorry about that. I'm not sure how to highlight the recurring nature of these less-than-factual posts and their effect on some members of the community while respecting the wishes of those people.
I am not sure what we are arguing here. You asked what Yuri originally wanted to highlight and I answered based on my own insights. I have re-read what he wrote and I still stand by my conclusion and I see no point in arguing back and forth as I have stated here and in a comment when Yuri’s post was originally submitted [1] that all the issues are derived from how Julia as a language handles types and dispatch. Everything beyond this is arguing minute semantics and frankly very uninteresting compared to a discussion as to whether it can be resolved and how.
[1]: https://news.ycombinator.com/item?id=31398040
> Sorry about that. I'm not sure how to highlight the recurring nature of these less-than-factual posts and their effect on some members of the community while respecting the wishes of those people.
Not sure if I can consider this an apology when you in your very next breath make exactly the same kind of vague statement that I called out in the first place. Since you apparently know these people, how about collecting their opinions and presenting them anonymised? Likewise, if there are lies spread across so many posts, why not simply collect and refute them? This all seems obvious to me, but maybe I am missing something? In fact, I find posts such as yours in very stark contrast to what Yuri wrote and accomplished with his post, so perhaps you can find inspiration there?
> Since you apparently know these people, how about collecting their opinions and presenting them anonymised
This sounds like the sort of claim that would get called out as unfalsifiable online.
> Likewise, if there are lies spread across so many posts, why not simply collect and refute them? This all seems obvious to me, but maybe I am missing something?
Is the question really "Why don't you just do a lot of work for me, for free, since I will not use a search engine?"
How can solving – or failing to solve – a technical issue that could then lead to insights shared between communities ever be a bad thing? Maybe I am just too thick to see it? But I am ending my efforts at this point as I am not getting any closer to understanding what you are trying to convey.
As for your “comebacks”, we clearly have very different standards and expectations when it comes to discourse. Where I come from, those that bring claims are expected to also bring adequate evidence – or at the very least attempt to do so and not spread claims just to later throw their hands into the air when called out for it.
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.
I know you have yourself filed dozens of issues, many of which have been fixed. I feel it is unfair to characterize years of work by a community of people as: "Julia does not care about correctness". There's an open triage meeting that happens every other week, where all new issues are discussed and triaged. There is a fairly detailed and well-defined release process. I don't believe people are holding back on filing bugs, because they are waiting for us to solicit.
I feel there is this weird disconnect between what is being said by e.g. Yuri, me or Dan Luu, and what is being heard by some of the core devs, and I don't understand where this disconnect is happening precisely. That is very frustrating.
I think delving into the issues with communication will only turn sour with no benefit, so let's not go there. Instead, let me be much more concrete. When I say that Julia does not put correctness as a high priority, what I mean is:
* Julia is not well tested, as can plainly be seen from the code coverage. Having all functions covered by the test suite is the absolute minimal standard of testing - I would argue that is not sufficient to consider something well tested considering Julia's generic methods. But even covering all methods with tests is still not done for Base Julia.
* When I file a bug that is eminently fixable e.g. #43235 or #43245 (or several others), it is not being fixed after months, it is not being milestoned, and new releases are being pushed out that contains the bug.
Do these two points not illustrate that more could be done to reduce the bugginess of Julia? I legitimately don't understand that one can hold the view that these two issues are not a reflection of correctness not being prioritized in Julia.
There is a broader point here about how Julia's language design and lack of safety features or tooling makes it very difficult to write correct code and enforce the correctness of it. But I feel if we can't even agree that Julia ought to have all its exported methods covered by tests, and all its bug reports fixed, then I can't see how we can have a discussion about the more complex and nuanced topics like how to enforce interfaces or contracts.
It is easy to pick a subset of bugs and weave a particular narrative. You have filed several issues, many of which are still open, and many have been addressed. Thousands of bugs are fixed for every release, including many you have filed - and Julia is better as a result.
Should Julia be better tested, yes it should be. Should we have better tooling, of course we should. Can the triage process be improved, yes. Should code coverage get to 100% - it has steadily increased over time. None of Julia's dependent libraries have 100% code coverage. Many of those projects are even larger than Julia itself. Can every possible bug identified be fixed - we would like to - but eventually there is limited developer time and everything has to be prioritized.
I will once again mention that the triage process is not a secret process. I welcome you to join some of the triage calls to give higher visibility to issues that you feel should be fixed (but are unable to provide PRs yourself for).
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.
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.
The problem with ignoring performance is, many applications can become a dependency of a performance relevant application even if that was not the original intent.
Julia is not not only fine, but my go to language for performance problems.
There are a great many use-cases/language comparisons you could find where Julia would be the worse performing language.
It's being very actively worked on however.
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...
If 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.
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?
Guess what also has unknown bugs? Old C code.
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.
That's a known problem with dynamic typing.
For example with Python, there is also dynamic typing, but not dynamic multiple dispatch. There do not seem to be correctness problems of the same sort as in Julia in Python. So dynamic multiple dispatch, or maybe just Julia's version of it, seems to be the culprit. But an in-depth analysis of this is needed before making a final verdict.
But as I said before, it seems inevitable: dynamic multiple dispatch leads to an explosion of possible combinations, how do you make sure that all of these combinations work as expected? And what is "expected" in the first place?
However, what Yuri pointed out is that there are some packages (specifically in the statistics area) which implemented functions like `f(A::AbstractArray)` but used `for i in 1:length(A)` to iterate through x's values. Notice that the AbstractArray interface has interface functions for "non-traditional indices", including `axes(A)` which is a function to call to get "the a tuple of AbstractUnitRange{<:Integer} of valid indices". Thus these codes are incorrect, because by the definition of the interface you should be doing `for i in axes(A)` if you want to support an AbstractArray because there is no guarantee that its indices go from `1:length(A)`. Note that this was added to the `AbstractArray` interface in the v1.0 change, which is notably after the codes he referenced were written, and thus it's more that they were not updated to handle this expanded interface when the v1.0 transition occurred.
This is important to understand because the criticisms and proposed "solutions" don't actually match the case... at all. This is not a case of Julia just letting anything through: someone had to purposefully define these functions for them to exist. And interfaces are not a solution here because there is an interface here, its rules were just not followed. I don't know of an interface system which would actually throw an error if someone does a loop `for i in 1:length(A)` in a code where `A` is then indexed by the element. That analysis is rather difficult at the compiler level because it's non-local: `length(A)` is valid since querying for the length is part of the AbstractArray interface (for good reasons), so then `1:length(A)` is valid since that's just range construction on integers, so the for loop construction itself is valid, and it's only invalid because of some other knowledge about how `A[i]` should work (this look structure could be correct if it's not used to `A[i]` but rather do something like `sum(i)` without indexing). If you want this to throw an error, the only real thing you could do is remove indexing from the AbstractArray interface and solely rely on iteration, which I'm not opposed to (given the relationship to GPUs of course), but etc. you can see the question to solving this is "what is the right interface?" not "are there even interfaces?" (of which the answer is, yes but the errors are thrown at runtime MethodNotFound instead of compile time MethodNotImplemented for undefined things, the latter would be cool for better debugging and stacktraces but isn't a solution).
This is why the real discussions are not about interfaces as a solution, they don't solve this issue, and even further languages with interfaces also have this issue. It's about tools for helping code style. You probably should just never do `for i in 1:length(A)`, probably you should always do `for i in eachindex(A)` or `for i in axes(A)` because those iteration styles work for `Array` but also work for any `AbstractArray` and thus it's just a safer way to code. That is why there are specific mentions to not do this in style guides (for example, https://github.com/SciML/SciMLStyle#generic-code-is-preferre...), and things like JuliaFormatter automatically flag it as a style break (which would cause CI failures in organizations like SciML which enforce SciML Style formatting as a CI run with Github Actions https://github.com/SciML/ModelingToolkit.jl/blob/v8.14.1/.gi...). There's a call to add linting support for this as well, flagging it any time someone writes this code. If everyone is told to not assume 1-based indexing, formatting CI fails if it is assumed, and the linter underlines every piece of code that does it as red, (along with many other measures, which includes extensive downstream testing, fuzzing against other array types, etc.) then we're at least pretty well guarded against it. And many Julia organizations, like SciML, have these practices in place to guard against it. Yuri's specific discussion is more that JuliaStats does not.
In a way that is what I said: The problem is not that the types did not fit, the problem is that the code did just not behave as expected according to the interface specification. And combining many different implementations which each other through multiple dispatch increases the chances that the misbehaviour of one of them impacts overall correctness.
But my emphasis on "dynamic" seems to be wrong. As you describe, this would happen in a static language as well.
Nevertheless, I don't believe the solution is linting. That's just a bandaid. The solution is to prove your code correct. That way you make sure that the code implementing an interface behaves as demanded by the interface. There are areas of computing where that does not make much sense. Numerical computing isn't one of them.
The client will have its own spec, and when it uses the array, you need to prove that it does so in a way that its own spec is fulfilled.
You need to know the entire semantics of the interaction. You need to know what the array represents for the client, and that may depend on the client of the client.
But with multiple dispatch you are constantly tempted to pretend that correctness just depends on the type, because that's what you dispatch on. So that's the problem.
In general, I like types for organising things. I don't like them for correctness.
PS: Edited the above a few times to make my point more clearly.
So you cannot loop over a subset of the indices? That seems restrictive.
I don't think the Array indexing issue can be dealt with by (just) improving its interface specification. I think the proper fix for this is beyond what can reasonably be done in any language that doesn't come with a notion of correctness and proof.
See: https://github.com/google-research/dex-lang/pull/969 and https://github.com/google-research/dex-lang/blob/5cbbdc50ce0... for examples
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.
> "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
endIt'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.
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.
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.
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?
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.
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.
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.
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.
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.
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.)
I agree C++ makes it too easy to accidentally copy data, but at least you can copy data!
> 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?
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
Going by popularity, 1-based indexing is the established convention.
Any shred of evidence for that? Listing four languages for each won't cut it.
Given Excel’s massive success and nontechnical user base, which again is larger than all languages combined, it’s hard for me to see 1-based indexing was a mistake. I have experience teaching novices how to program, and 0-based indexing is always a sticking point of confusion. So from my perspective, 1-based indexing is the right choice for excel given the user base and programming style.
When someone wrote “Going by popularity, 1-based indexing is the established convention” and you replied “Yes, but not so much in scientific computing” I understood that as “1-based indexing is less used in scientific computing compared to general computing”.
The argument for 1-based indexing with inclusive upper bounds is that binary heaps are easier to write this way. Edit: And that people who are not primarily programmers may find 0-based indexing weird.
Alas, if just our forefathers had called it "offset" instead of "index".
Nope. The argument for 1-based indexing with inclusive upper bounds is that, to express ranges, exclusive indexes require you to have a bogus just-one-beyond-the-largest-valid-index element and a way to get the successor for every index. This is a giant PITA for anything but integers (MAX_INT, and x+1), which is why every single programming language I can think of also has end-inclusive ranges in some contexts. If you don't believe me try writing regexps with end-exclusive ranges.
On the other hand, there is no natural way to express empty ranges with 1-based indexing.
[a-zA-Z_][a-zA-Z0-9_]*
Here's what the same regexp would look like if regexp ranges were end-exclusive: [a-{A-[][a-{A-[0-:_]*
Do you see the issue now? Of course if regexps really worked that way, you'd be better off doing this instead: [a-zzA-ZZ][a-zzA-ZZ0-99_]
But that specific trick only works because regexp ranges occur in a set union context.0-based languages are in the minority as far as installed base and usage goes. Being 1-based is a decision dependent on target audience and application.
Fortran, one of the earliest languages, is 1-based, so there’s plenty of historical precedent.
Matlab, one of the most commercially successful languages in history is 1-based, so it’s not exactly a barrier to adoption.
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.
> Compare DifferentialEquations.jl to any other package in any other language
If you do not know how to do such a comparison for yourself, this thread has details https://news.ycombinator.com/item?id=31883793