-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A case/switch statement for Julia #18285
Comments
Dup of #5410 |
@bpr It seems that there is agreement that we should have a switch statement and your proposal seems valid, but to make it happen it would be best to have a PR implementing the proposal. |
I'm not certain how I feel about adding But either way I'd like to point out that this is a well-written, well-researched, and well-balanced proposal. I'd like to argue for reopening this and closing #5410, since this contains many things that are not in #5410 (an explicit API proposal and quite a few helpful links, including to #5410). Closing this effectively discards all of the careful work behind this. |
+1 On opening this and closing #5410. I have seen |
Also, please use ``` for quoting code instead of |
As @timholy said, the proposal here also doesn't address, IMHO, the most important issue for #5410, i.e. how should this be related to LLVM The LLVM
I expect that the julia Also worth noting that
Isn't accurate. The syntax can be / is already implemented nicely as macros and unless there's something we can't do with macros, e.g. if we want to have special lowering for it (directly to a switch statement with LLVM restriction) or if we want to allow special handling at surface syntax level (like |
LLVM does (or did) have a pass to convert a sequence of integer comparisons to a jump table. I remember seeing this work at some point, but the optimization has come and gone as LLVM has changed. I haven't looked for this in a while; we should see if it's working currently. |
Regarding the proposed syntax, I don't see why the case n
0 => println("zero")
1 => println("one")
2 => println("two")
else => println("too big")
end |
A switch-like statement enriches the language that a programmer can use. If there is already a pass that converts a series of
Some of these are difficult to implement via macros. (And for the sake of the discussion here, it doesn't really matter whether one writes |
@yuyichao Sorry I didn't continue #5410, I had an email exchange with @StefanKarpinski in which I'd referenced that issue, and he replied that the best way to get something like this in the next Julia is to write a Julep and once that's accepted a PR. The reason I put the LLVM restriction in there is that my reading of the previous threads and investigation into the issue revealed that the LLVM does not provide this optimization currently. I reran @stevengj 's experiment and I get a sequence of comparisons. As I mentioned, the main point of this proposal is to provide a high level syntax for Julia to access LLVM's switch instruction. I suppose in a future language spec it can spell out that the construct has to implement the branch over a restricted set via some kind of O(1) structure. @timholy That's a good point, some performance experiments are really necessary to strengthen the argument for inclusion. @carlobaldassi Syntax bikeshedding is great fun! But seriously, I'm not married to any particular syntax, but hopefully whatever is chosen looks like Julia. @eschnett I completely agree that a switch like statement enhances the language, as does pattern matching. |
You should mention that or briefly explain why a dup is necessary. Otherwise, closing duplicated issue is the default. IMHO a good proposal fits well in the comment of existing issue, especially since the original issue isn't too long to read through, unlike, e.g. JuliaLang/LinearAlgebra.jl#42. |
@yuyichao I think you are accidentally mis-reading my arguments as if I suggested to not use a macro. Instead, I am making an argument comparing a |
@yuyichao, Since this is intended as a Julep, having a separate issue about a specific proposal seems appropriate to me. A single long, impossible to follow thread on every subject isn't optimal. I like the proposed syntax – specifically @carlobaldassi's variant of it without the |
In that case 👎 for adding the syntax since AFAICT there isn't a benefit for having it as a syntax instead of a macro. |
Aside from the five things that @eschnett listed. |
No. Pasting back my comment from #5410 with slight modifications / typo fixes. Basically all of those are benefits of macro or syntax over
Can be handled with macro.
Can also be handled with macro. Having a syntax doesn't help. Will likely have runtime overhead unless we restrict the condition to constant. And this behavior may not be wanted.
Can be handled with macro.
Can be handled with macro.
Lowering to |
While my quick experiments with clang++ didn't show a significant performance win for I suppose similar questions will come up as new LLVM features are added. I just noticed that LLVM 4.0 has coroutines; I bet in a few years we'll want to see Julia able to access those LLVM intrinsics too! |
Yes, this is the single difference between a language level construction and a macro. However, the question is still that do we want to have the same set of restrictions as such low level features (i.e. guaranteed constants, bitstype, and probably more). Everyone who have commented so far (you included) seems to think that we don't want to have such restrictions. Given that, an optimization pass seems to be the way to go if we've identified cases where such transformation can lead to higher performance and if it is not done by llvm automatically. |
Are there possible any variants with ranges or intervals: @enum Dir north northeast east southeast south southwest west northwest
function to_north(d)
case d of
east:west => false
else => true
end
end or next one function to_north(d)
case d of
east <= _ <= west => false
else => true
end
end The current |
But... Isn’t this being better and more Mathematical? abstract Term
immutable Free <: Term
name :: String
end
immutable Lam <: Term # HOAS
fn :: Function # String -> Term
end
immutable App <: Term
fn :: Term
arg :: Term
end
# CBV normalization
normalize(n' = Free(n)) = n'
normalize(f' = Lam(f)) = f'
normalize(App(f, x)) = apply(context, normalize(f), (x)) where
apply(Lam(f), x) = normalize(f(x))
end
normalize(App(Lam(x -> x), Free('a'))) # ==> Free('a') |
FYI, a sufficiently efficient pattern matching has been implemented by MLStyle.jl which could be 20+ times faster than Match.jl, and the benchmark script is provided at the root dir of project. |
@yuyichao Achieving this via macros cannot be always sufficient.
|
How is this |
Well, if you feel okay with adding parentheses. P.S: Picking a syntax like |
This is the estabilished solution for exactly this kind of problem for all macros.
By picking multiple syntaxes similar to |
My point is for pattern matching is super useful for some domains, it's worthwhile to make it a special macro. I mean that we can provide macros for pattern matching in standard library and just transform
Thanks for reminding me of some useful notations. Now I have a better idea to bring pattern matching into Julia than using something like |
Now I'm too excited to fall into sleep!!! @allowpattern module M
case(target) do
pat1 => body1
pat2 => body2
...
end
end |
Hello Julia syntax extenders, To whom it may concern, i suggest you to read extension points, or how ocaml is becoming more like lisp that relates a same story that has occured with ocaml few years ago.
OCaml now has a very clever and powerful mechanism, OCaml syntax extensions that allows to create, combine, update, extend a fully featured set of syntax enhancement ... |
Can macros pick up their arguments from multiple lines? If not, that would be an interesting addition and will allow macro uses to be formatted nicely. |
Macros don't see whitespace, they see the AST. The details are in the Julia manual under "Metaprogramming". Ask on slack if you need a hand. |
Sorry, newbie to Julia here. Does it mean that I have to always install the it's surprising that such a useful feature requires an external package. |
Given that it's not used in Base and it's not a dependency of every single packages out there I'd say it's actually very easy to imagine.
There's nothing wrong with "external packages". I'm pretty sure many "useful" features are in "external packages". There's nothing hard about using an external packages so if you really can't imagine yourself ever not using it installing is a very easy solution. If anything, that is indeed something you should get used to as "newbie to Julia". Here, being in an external package doesn't mean it's not useful, it just mean a combination of not needed in Also, if out of the several proposed macro syntax and semantics |
Strongly guessing he was not talking about Base but about the parser / lowerer / interpreter / compiler etc. |
Well, there's basically nothing other than syntax here. There's basically zero chance we'll have a syntax for a I don't want to guess what he meant even though I believe he is implying that By all mean, this is only a base vs package concern. |
@yuyichao should be an integral part of the language |
I think, you never used JavaScript based apps. Each external dependency has a risk of breaking your whole application, especially if it is maintained by a single random person. Just spamming external dependencies into your project is extremely bad practice and design. ESPECIALLY if you are using a tiny lib, that only provides a switch statement. https://www.zdnet.com/article/another-one-line-npm-package-breaks-the-javascript-ecosystem/ |
I don't believe this is a proper statement for Julia. |
This isn't a proper statement for any language. That said, before people start disliking a comment, how about they check its sources, themselves... I provided 3 links for 2 great breakdowns in external dependency madness. Those are only the biggest ones. There are way more than that, because apparently too many developers think, that they should import every oneliner fart, that exists on Github, instead of just writing that one line themselves. Can anyone propose at least one rational argument against my reasoning? |
Yes, above.
So please read through the comments above and understand the difference between a language feature and stdlib as well as what's even possible first. |
This comment has been minimized.
This comment has been minimized.
The reason why I said it's not proper to Julia is not due to:
The reason for small and separate packages for Julia is quite different from languages like JS. Julia is in some degree relying on this practice to ease issues like JIT startup. The good performance of a dynamic language does not come out of thin air, intergrating too many into one package or stdlib does has drawbacks(though greatly improved by Revise.jl).
It seems that you're quite negative about the how developers manage dependencies. However being negative does not mean this point of view is valid: Several scripting langauges similar to JS just like Python or Ruby, developers of which do not regularly create or import one-liner library, and sometimes even get crazy for reducing/cleaning the dependency. Besides. The 3 posts are not convincing in this scope: One-liner library is more likely to be inlined and self-maintained in Julia community. |
Structural pattern matching, more like that found in ML or Erlang than a C-like switch, is being added to Python |
It isn't, and should Match.jl be made a stdlib (sooner rather than later)? If it's trivial to do, is there an exception to be made for 1.7 (in case it ends up LTS), at least merge for 1.8 right away (if only done for 1.8 we could back out later)? I see this was moved to 2.0 milestone, then back to 1.x, presumably because this doesn't need to be a breaking change, and I suppose than means in macro form rather than syntax. Another possible change (alone) would be to recommend Match.jl in the docs. I tried to search for case/switch, and [pattern] matching and found nothing, at least not here: https://docs.julialang.org/en/v1/manual/control-flow/ I see Match.jl is stable for several years now, so that doesn't seem like a hindrance.
At least MLStyle has slower startup (while not bad). It's unclear to me if its "syntax"/"API" is better, and if we choose Match.jl over some other package, we've painted ourselves into a corner. Could MLStyle's "20+ times" faster code later by applied to Match? [For Julia itself, I see plenty of |
Well, you can just do it via short-circuit operators instead: function print_if_lt_3(n)
n == 0 && return println("zero")
n == 1 && return println("one")
n == 2 && return println("two")
println("too big")
end Same for enums: function degree_of_dir(d)
d == north && return 90
d == northeast && return 45
d == east && return 0
d == southeast && return 315
d == south && return 270
d == southwest && return 225
d == west && return 180
d == northwest && return 135
end Or more exotic, but little slower: degree_of_dir2 = begin
pattern = Dict(
north => 90,
northeast => 45,
east => 0,
southeast => 315,
south => 270,
southwest => 225,
west => 180,
northwest => 135
)
(d) -> pattern[d]
end No elseifs Though it might be useful to do some clever pattern matching, but in my opinion simple C-style |
Proposal
This is a Julep suggested by @StefanKarpinski during this julia-users group discussion.
Julia lacks a C-style switch statement. This issue has come up before on various fora. Unsurprisingly, Julia also lacks pattern matching, a useful generalization of case and switch, which has been implemented as a macro for Julia.
This proposal is concerned with using switch statements over integral (isbits) types to exploit the ability of LLVM's switch instruction to provide a branch table implementation of switch, which is more performant than a sequence of
if-else
conditionals. It should be amenable to supporting extended switch and pattern capabilities in the future; for that reason I'll suggest using adding thecase
keyword to introduce the form. If it's desired that the statement only be used as a C style switch and that pattern matching be introduced separately, perhapsswitch
is the better choice.I'll skip the arguments in around whether to include such a feature at all; interested readers can review some of those arguments in the context of Python or Lua, as the arguments on both sides would be similar for Julia.
Syntax
If we'd like to subsume this in a future pattern matching Julia extension, I suggest a syntax influenced by the Match.jl macro, replacing
@match
with the keywordcase
, and adding a new keyword(Objection sustained).of
to use instead ofbegin
. I choseof
because that's used in many other languages that usecase
, and because Julia constructs likeif/for/while
don't require abegin
for theirend
The optional
else
at the end could be replaced by_
or evenotherwise
ordefault
, at the cost of using more keywords.Each case above could be preceded by an
of
if that syntax is preferable. It strikes me as a little wordy but it's a syntax used in other languages and perhaps people find it more readable.Examples
Should be semantically equivalent to
An example with enums
The intent is that
@enum
should work well with thiscase
statement, which means that the conversion to the enum's Int value should be implicit, as above.The text was updated successfully, but these errors were encountered: