Skip to content
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

mini julep: if x then y #16389

Open
quinnj opened this issue May 16, 2016 · 54 comments
Open

mini julep: if x then y #16389

quinnj opened this issue May 16, 2016 · 54 comments
Labels
kind:julep Julia Enhancement Proposal

Comments

@quinnj
Copy link
Member

quinnj commented May 16, 2016

In various discussions, it has been suggested to allow syntax like:

if x then y

As a short-form "if" statement and as an alternative to the common:

x && y

syntax which leverages the short-circuiting && operator for conditionally executing y (with y often containing other side effects and not necessarily returning a Bool).

The main advantages to this if-then construct being: more legible code, relying less on abusing &&, and formally including an "if" statement form that doesn't require an end keyword.

It occurred to me the other day, that this syntax would also provide a convenient means for implementing #550, which would look like:

A = [if x % 2 == 0 then f(x) for x in 1:10]

Relying on the fact that if-then doesn't require an end keyword, which we would probably need in some form anyway even if we went with python-style guards:

A = [f(x) for x in range(10) if x % 2 == 0]

To be clear, the Julia guard syntax would essentially be doing a rewrite from:

A = [if x % 2 == 0 then f(x) for x in 1:10]

to

A = [Filter(x->x % 2 == 0, f(x) for x in 1:10)]

Also as a clarifying note, this would be allowing the guard syntax at the generator level as opposed to just the comprehension level (which matches what python allows as well).

@StefanKarpinski
Copy link
Sponsor Member

StefanKarpinski commented May 16, 2016

The Perl/Ruby-style if/for modifier syntax seems like it would mix better with this. In other words:

println("positive") if x > 0         # conditional execution
x^2 for x=1:100                      # generator
[ x^2 for x=1:100 ]                  # comprehension
x^2 if x % 3 == 0 for x = 1:100      # filtered generator
[ x^2 if x % 3 == 0 for x = 1:100 ]  # filtered comprehension

It also has benefit of having precedent in other languages, which the if-then without end syntax doesn't seem to.

@davidanthoff
Copy link
Contributor

Just a side note on issue management: #6823 discussed exactly this issue. At some point it was closed without comment, I followed up asking why it was closed because clearly there hadn't been a consensus to close it, but never got a reply. Seems it would be more efficient to not close issues like #6823, otherwise these discussions will just go in circles and all the points in the original issue will be repeated again. Would probably have made sense to change the title of #6823 at some point, and then add this here.

@JeffBezanson
Copy link
Sponsor Member

Yes, good point. Indeed #6823 seems to be mid-discussion and probably should not have been closed.

@ararslan
Copy link
Member

Maybe I'm in the minority since other languages have taken this approach, but I've always found the action coming before the condition to be really weird, e.g. println("positive") if x > 0. I guess it's more concise than if x > 0 println("positive") end but at least to me it comes at the cost of readability.

I think of it like a conversation:

Julia: "I'll print a string..."
Harold: "Awesome!"
Julia: "...but only if some condition is met."
Harold: "Oh. :("

versus

Julia: "If some condition is met, I'll print a string."
Harold: "Okay, cool."

@StefanKarpinski
Copy link
Sponsor Member

@ararslan: I don't disagree, which is one of the reasons this feature isn't in Julia despite the prior art in Ruby and Perl. However, it does mix much better with the for modifier syntax for generators and comprehensions, which is why I brought it up here.

@ararslan
Copy link
Member

ararslan commented May 17, 2016

@StefanKarpinski Yeah agreed, it does mesh better with those. I'm still hopelessly attached to conditions preceding actions though. 😄 (But as I've found myself saying often recently, my opinion doesn't really matter; I'm just some guy.)

I suppose if you had something like

if x % 3 == 0 then x^2 for x = 1:100

then it isn't immediately clear that you're starting a comprehension/generator/thingamajig, because it also reads as though it can be grouped like

if x % 3 == 0 then (x^2 for x = 1:100)

i.e., if some condition then generator. I wouldn't mind

if x % 3 == 0 x^2 end for x = 1:100            # More obvious what's happening, IMO
filter(x -> x % 3 == 0, [x^2 for x = 1:100])   # Verbose, but... ¯\_(ツ)_/¯

I guess if the condition were to follow the action in a generator, I think I'd prefer a different keyword than if, for example where. Then it reads almost like a SQL query, e.g.

x^2 for x = 1:100 where x % 3 == 0

I think where makes it a little clearer that you're filtering the values of x produced by the for than if does.

@StefanKarpinski
Copy link
Sponsor Member

It seems nicer to me to have the data flow in a single direction, although in this case right to left rather than left to right.

@ararslan
Copy link
Member

Can you elaborate on what you mean by that?

@StefanKarpinski
Copy link
Sponsor Member

The for 1:n "generates" values, the if x % 3 == 0 filters them and the x^2 transforms them – generators already flow right to left and to keep that flow the filter needs to go in the middle. If the if clause goes to the right of the for clause then data "flows" from the middle to the far right then to the far left, which is weird. If the if clause is on the left then the data "flows" from the far right to the far left to the middle. I know SQL puts the where clause to the right of the table names and the expressions to the left – because that reads more like English – but I've always found this annoying to read and I think that reading like English is not a heuristic that should be taken too far in programming language design.

@StefanKarpinski
Copy link
Sponsor Member

Things I don't like about if x then y:

  1. It introduces the new then keyword.
  2. It's shorter to write if x y end than if x then y; x && y is even shorter.
  3. The then keyword is widely used in languages for the exact opposite kind of syntax: a multiline if syntax that requires an end keyword (or fi in bash, yuck).

@ararslan
Copy link
Member

Oh yeah, I see what you mean now about the flow. That makes sense. Thanks for the explanation!

Would it be weird to be able to do

x^2 if x % 3 == 0 for x = 1:100

and not be able to do

println("positive") if x > 0

?

@quinnj
Copy link
Member Author

quinnj commented May 17, 2016

All good points.

I actually really like the syntax @StefanKarpinski first proposed in his first comment. I'm mainly interested in having conditional generators, with a more general short-form if-form as a bonus.

@andyferris
Copy link
Member

Regarding generators, I was reading the if (or where - I have no opinion on that) to be part of the range... it's creating an iterator x = 1:10 if/where x % 2 == 0, which is then combined with the expression on the left to create an Array (or generator).

In a sense, x = 1:10 where x % 2 == 0 is itself a generator for some kind of iterable. This could be stand-alone syntax, no?

I feel that filtering is somehow a different operation to the conditional if a then b statement. Filtering acts on the iteration range. When the if a then b is combined with the expression on the left of the generator I would expect it to emit nothing in the cases a was false. Compare what logically happens if I add brackets:

[(x^2 if i % 2 == 1) for i = 1:10] # [nothing, 4, nothing, 16, nothing, 36, nothing, 64, nothing, 100]
[x^2 (for i = 1:10 where i % 2 == 0)] # [4, 16, 36, 64, 100]

Taking @ararslan's examples, if we allow

println("positive") if x > 0

then IMHO it follows that

x^2 if x % 3 == 0 for x = 1:100

should probably emit nothing, nothing, 9, nothing, nothing, 36, ...

Finally, if we were to adopt a shorthand if conditional, my vote is for the condition before the statement, for the reasons outlined by @ararslan's first post - being the principal of least surprise in reading the code. (remember, if they say code is read more than written, then if a then b is better syntax then a && b even if the latter is shorter). It also means the two possible types of if inside the generator would be distinct - on the far left - with the expression - or on the far right - with the range.

As for tradition in Ruby/Perl, I feel its better to try and come up with the best solution rather than be bogged down by tradition. If it works and feels natural people will like it.

@andyferris
Copy link
Member

andyferris commented May 17, 2016

Also, if we have if/where in ranges, do we need to be careful to make sure the range stays Cartesian for multidimensional generators?

# Make a circular array (filled with distance to center)
r = 5
[sqrt(x^2 + y^2) for x = -5:5, y = -5:5 where x^2 + y^2 <= r^2]

This is kind-of cool and kind-of awful!

@JeffBezanson
Copy link
Sponsor Member

I feel that filtering is somehow a different operation to the conditional if a then b statement.

Yes! I was about to post this same comment. Filtering operates on the iterator as a whole, and is not part of the computed expression inside.

@rfourquet
Copy link
Member

I also feel the same as @andyferris that the if between the expression and the iteration seems to "transform" the value of the expression rather than the shape of the iteration, and should produce nogthing when the condition is not met (but I may be spoiled by python/haskell comprehensions).

@StefanKarpinski
Copy link
Sponsor Member

Obviously x if false would evaluate to nothing when not in the context of a generator expression. It would be perfectly reasonable to support [x^2 if x % 3 == 0 for x=1:100] but not x if y by itself.

@Tetralux
Copy link
Contributor

Tetralux commented May 18, 2016

Personally, I'd rather be able to omit end if there is only one statement.

if length(A) != length(B) throw(ArgumentError("..."))

If there is no end, then it could assume that there was only one expression. I'm guessing that's been discussed at length before?
I note that I would prefer not to have a semicolon after the condition - after the expression... maybe... I wouldn't like it though.

I'd much rather that, than have two different syntaxes for a similar form of if statement;
if x; ...; end, and if x then; ....
(Edit: if condition then action has grown on me.)

It would be perfectly reasonable to support [x^2 if x % 3 == 0 for x=1:100] but not x if y by itself.

@StefanKarpinski I am totally on board with this. However...

If the if clause is on the left then the data "flows" from the far right to the far left to the middle.

I read x^2 if x % 2 == 0 for x in 1:10 as, x^2 where x % 2 == 0 for each x in 1:10, which, since it's comprehension, makes sense to me; you're most interested in the transformation.
In SQL queries, the same point is true, no?

I therefore do not think that having the "generator" be the starting point for reading it, is quite right, for a comprehesion. A for loop on the other hand... That makes more sense.

@StefanKarpinski
Copy link
Sponsor Member

Personally, I'd rather be able to omit end if there is only one statement.

I proposed this a long time ago but it did not get any traction: #1657. This would actually be a relatively non-disruptive change since it would be weird and rare to see some write code like this:

if cond body
end

@diegozea
Copy link
Contributor

What do you think about using do blocks instead of a then ? i.e.: if x do y end

@carlobaldassi
Copy link
Member

Just a small side-comment: I don't see any way that if x y by itself (i.e. outside of a generator) could be handled correctly within an editor without a full-blown julia parser, since you need to be able to determine how many tokens follow the if. I'm thinking of vim, but I suppose other editors may be in the same situation. The same could be true about y if x, but I'm not sure (full disclosure: I also really dislike y if x by itself; I'm not against if x y in principle but think it may be slightly difficult to parse for humans as well, not only editors).

@Tetralux
Copy link
Contributor

@diegozea Same problem, I think. I still would prefer if x y.

@carlobaldassi There are some things you could do to make it easier for humans to parse.

You could force that single-statement ifs would not have a newline between the condition and the expression.
I'm not sure I'd like being forced to do that for long expressions, though.

Alternatively, you could force there be a newline after the expression if there was a newline after the condition; so, the following would not compile, but give a clear error message of course:

if length(A) != length(B)
    throw(ArgumentError("lengths must match")
if some_condition(A, B)
   N *= 2

But, this would:

if length(A) != length(B) throw(ArgumentError("lengths must match")
if some_condition(A, B) N *= 2

and this would:

if length(A) != length(B)
    throw(ArgumentError("lengths must match")

if some_condition(A, B)
   N *= 2

This would also prevent you from making this kind of mistakes:

if is_present(x)
    y = 2 * x[]

    return y * 2

@quinnj
Copy link
Member Author

quinnj commented May 18, 2016

I think the biggest argument for if x then y that still wins over any other syntax is readability. That if x y end, if x y or x && y are shorter is irrelevant in my book (beyond the fact that we're talking about a trivial 1-5 character difference) because none of those are nearly as clear as if x then y. It also avoids the editor parsing problems or possible breaking of past code. It's just a nice, clean, short syntax for short if statements.

I do, however, recognize the potential confusion of the use of if-then in the generator case; given that, I agree that

x^2 if x % 3 == 0 for x = 1:100      # filtered generator
[ x^2 if x % 3 == 0 for x = 1:100 ]  # filtered comprehension

is clearest, recognizing that the expression structure is

generated_value_expr  value_generator_expr  =>  generator_expression

[generator_filter_expr]  for_generator_expr  => value_generator_expr

i.e. the generator_filter_expr is applied directly to the values generated from for_generator_expr before passing non-filtered values to the generated_value_expr.

I think the distinction is important here, because it's not that same logic that would allow

println("positive") if x > 0 

TL;DR: We should have x^2 if x % 3 == 0 for x = 1:100 for filtered generators, but the same syntax logic doesn't apply to println("hey") if x > 0, hence, we should still consider if x then y for short-form if-syntax. Though obviously the two ideas here are now completely unrelated.

@ararslan
Copy link
Member

ararslan commented May 18, 2016

@H-225 That seems weird and ultimately un-Julian to me, especially with the action on a new line with no end.

It would still be a nightmare for an editor to parse because editors (like Vim) don't care what is and isn't a condition or action--indeed, they have no way of knowing without a Julia parser--but rather they care about the placement of things like if and end. I think it would be exceptionally difficult to get Vim to recognize that end is not needed in that scenario. I realize that making it easy for editors isn't a good argument for a design decision, I'm just sayin'.

Definitely 👎 for if x y from me. I'd prefer if x then y over that, as it's easier for my little brain to parse. 😜

@ericproffitt
Copy link

Is there a reason not to simply use the ternary question mark operator for the if-statement as well? You can already mimic an if-statement by doing

condition ? if_condition_true_eval_expr : nothing

why not just make it so that if you don't include a colon then it's an if-statement, then you would just have

condition ? if_condition_true_eval_expr

this way you don't have to introduce any new keywords, would this work?

@andyferris
Copy link
Member

andyferris commented Jun 8, 2016

@esproff Funnily enough, I was going to suggest the opposite: extending the if-then idea in this julep to a single line if-then-else expression

if cond then a else b

where the else clause is optional. It might even lower identically to cond ? b : c. For me, this is all about getting away from the C-style ternary and toward more human-readable code. (I have definitely used condition ? a : nothing before - it seems like a hack ('cause it was, the nothing was important for some obscure reason) and it's confusing for other people to read).

But of course, why can't we have all of these ideas simultaneously?

@ericproffitt
Copy link

ericproffitt commented Jun 8, 2016

@andyferris Yes that's the other, Pythonic, way to go. I guess it depends on how terse you want to be, mathematicians generally seem to prize terseness, but honestly I'd be happy with either, as long as I don't have to use the clunky

if condition; eval_expr; end

@andyferris
Copy link
Member

but honestly I'd be happy with either, as long as I don't have to use the clunky

if condition; eval_expr; end

Indeed!

@davidanthoff
Copy link
Contributor

The ? idea was discussed quite extensively in #6823, might be worth re-reading that discussion.

@amellnik
Copy link
Contributor

amellnik commented Jun 8, 2016

I got here from that discussion, but figured this was a better place to comment. I think that if a then b and using just ? without : would each be somewhat confusing, but wouldn't complain as long as they provide the same functionality.

@ararslan
Copy link
Member

ararslan commented Jun 8, 2016

I agree with @EricForgy. Maybe I'm just used to them at this point, but to me, using && and || in this way feel idiomatic for Julia, particularly for error checking (e.g. x || throw(y)). They don't seem that ambiguous or unreadable at all. Also, I don't really see why it would matter that x && y returns false if x is false because it probably shouldn't be used at the end of a function anyway, where it would affect the function's return value.

C-style ternaries are pretty ubiquitous; I'd argue that it isn't just mathematicians who value this syntax. The only language I can think of offhand that has ternaries but doesn't support C-style is Python, and FWIW I despise Python ternaries. condition ? action1 : action2 is both compact and readable as long as you use whitespace and respect line lengths. For longer conditions and actions, one should use

if condition
    somelongaction1
else
    somelongaction2
end

anyway for readability.

Regarding then, the point of this discussion, FWIW I probably wouldn't use it even if it were implemented because, again, && and || feel idiomatic to me. Though I'd take then in a heartbeat over if x y, x ? y, or x ? y : nothing.

@nbaum
Copy link
Contributor

nbaum commented Sep 17, 2016

Personally, I much prefer y if x. It might seem like the wrong way around for it to go, but there is precedence beyond Ruby and Perl: mathematical case syntax has it this way around

image

I don't like to use && or || for this, since I never remember that x && y = z doesn't parse the way I expect. I think it has a higher cognitive load than y if x; that goes for if x y too.

IMO, x ? y smells like a syntax error, regardless of whether it's actually valid.

x ?? y perhaps? x ?: y is a GNU C extension meaning x ? x : y, so ?? and ?: could be low-precedence versions of && and ||, maybe.

My order of preference: y if x == x ?? y < if x then y < x && y < x ? y < if x y.

@tbreloff
Copy link

FWIW. I prefer the terseness and clear separation of the code with ?, &&, and ||, though cond ? expr syntax might be more clear than &&.

I do like to frequently do goodcondition || return type of flow, which isn't easy with if-statements. I wouldn't want to lose that (or be bullied into not using it)

For ternary without the :, it might be nice to allow terse if/elseif blocks without an else condition:

x==5 ? f1() :
x==6 ? f2()

and one could imagine extending this to switch-type statements:

match x:
    5 ? f1() :
    6 ? f2()

In terms of readability, it depends. For many short conditions/actions it is much more readable (with clean spacing!!) to be able to line up conditions/actions on successive lines with clear separators.

For generators, I would prefer where or when over repurposing if, as it's a filter, not a conditional:

x = [i^2 for i in 1:10 when i%2 == 0]

I think it's more clear that this means x = [i^2 for i in filter(..., 1:10)].

@kshyatt kshyatt added the kind:julep Julia Enhancement Proposal label Sep 21, 2016
@Tetralux
Copy link
Contributor

I'll note that since discussing this, I would no longer object to if condition then action - it's rather grown on me.
Julia does tend to lean on the more "wordy" side, with function and end, etc, so I think it would fit right in. It's also a little irritating to have to add brackets around expressions if you go back and forth between the condition && action syntax.

@perrutquist
Copy link
Contributor

perrutquist commented May 18, 2018

Regarding the Perl/Ruby syntax "action if condition": I really like this syntax, and while I agree that it can reduce readability when used incorrectly, I also believe that it sometimes increases readability.

For example:

throw(DomainError()) if some_condition || some_other_condition && so_on

After reading "throw domain error if" you already know that what follows is going to be a sanity check on the input. You can then skip to the next line if the details of the conditional are not important to you.

The previous sentence is an example of how this construction frequently occurs in natural language. The action "skip to the next line" comes first, and the less important "if you're not interested" comes in a subordinate clause. Again, the reader might be able to guess approximately what will follow the word "if" without actually reading to the end of the sentence.

(I will also give a self-referential example sentence, if I can think of one.)

@StefanKarpinski
Copy link
Sponsor Member

As an old Perl hand, I'm sympathetic to this syntax but I think it's going to be a hard sell to others who don't come from a Perl background. One argument in favor is that it matches the filter syntax in comprehensions. An argument against is that it's weird to have a construct where the evaluation order is not left-to-right. Of course, comprehensions have precisely that, so 🤷‍♂️

@andyferris
Copy link
Member

andyferris commented May 19, 2018

To me a related question is then: are we generalizing branching or filtering? This is a generalization of the branching if semantics (and syntax), so perhaps it would be unfortunate to borrow the syntax from comprehensions / generators for this purpose where that syntax already indicates filtering.

(By the way, I'd love some nice syntax for filtering, like our nice . syntax. To illustrate my point above perhaps a map-filter could look like f.(a) if g.(a)

f(x) for x in a # lazy map
f(x) for x in a if g(x) # lazy map - filter
x for x in a if g(x) # lazy filter where `f` is `identity`

f.(a) # map / broadcast
f.(a) if g.(a) # like a map/broadcast - filter operation
a if g.(a) # like the above where `f` is implicitly `identity`
[1,2,3] if [true, false, true] == [1, 3] # or something... here we simply make `if` an infix operator for filtering

Sorry for going wildly off-topic and I'm not sure the above is a good idea at all, but just pointing out there might be another potential usage of the comprehension filtering if syntax that has filtering semantics)

Finally - I kind of agree with @tbreloff's observations above... binary ? is the most compact syntax (and the whole point of this thread is to make a compact syntax), and I always found if a somewhat surprising choice for generator filtering.

@sighoya
Copy link

sighoya commented Mar 19, 2019

@StefanKarpinski wrote:

The Perl/Ruby-style if/for modifier syntax seems like it would mix better with this. In other words:

println("positive") if x > 0         # conditional execution
x^2 for x=1:100                      # generator
[ x^2 for x=1:100 ]                  # comprehension
x^2 if x % 3 == 0 for x = 1:100      # filtered generator
[ x^2 if x % 3 == 0 for x = 1:100 ]  # filtered comprehension

It also has benefit of having precedent in other languages, which the if-then without end syntax doesn't seem to.

And it would be better for the grammar, too.

I think [x^2 if x % 3 == 0 for x = 1:100] should be:

[(x^2 if x % 3 == 0) for x = 1:100]

Then for stay's in infix position which is currently an error. Of course we can change its meaning because of leading [ but it would not work as generator:

x^2 if x % 3 == 0 for x = 1:100

@JeffBezanson
Copy link
Sponsor Member

I think [x^2 if x % 3 == 0 for x = 1:100] should be:

That would not be a filtered generator. 2 if x returns 2 when x is true, but it also has to return something when x is false; typically that would be nothing. So that would give an array of numbers and nothings. That's partly why if has to come after for in filtered generator syntax.

@sighoya
Copy link

sighoya commented Mar 19, 2019

Yes, you are right. Maybe it should be written like that:

[for x = 1:100 x^2 if x % 3 == 0]

Afaics, this would be valid parseable without the use of parenthesis, cool!

@RaulDurand
Copy link

RaulDurand commented Mar 25, 2019

Just thinking...

[ for x = 1:100 if x % 3 == 0 push x^2]
for x = 1:100 if x % 3 == 0 push x^2  # other keyword could be used, e.g. yield

This is more similar to the natural construct

for x=1:100 
    if x%3==0 
          push!(somearray, x^2)
    end
end

@c42f
Copy link
Member

c42f commented Aug 30, 2019

I just saw this julep again while looking at something else.

I still wish for something like the if a then b single line form; I do find the julian replacement a && b to be fairly unreadable even after using it for several years: there's just no way to read it directly as an English sentence.

I also feel that the need to explain the a && b idiom to newcomers is kind of embarrassing when we could have an alternative syntax which is self evident and only a little longer.

@StefanKarpinski
Copy link
Sponsor Member

there's just no way to read it directly as an English sentence

a && b reads as "a and b" as in "if a then also do b"
a || b reads as "a or b" as in "a must be true, otherwise do b"

@c42f
Copy link
Member

c42f commented Aug 30, 2019

I know what it does and I use it on occasion for brevity. But try as I might (as mentioned, for several years now) I can't see "a and b" as a sentence. It's just a mild cognitive dissonance every time.

In contrast I've always found "a or b" to be fairly readable.

@andyferris
Copy link
Member

andyferris commented Aug 30, 2019

Funnily, different aspects of this came up twice today at work.

In the morning, we had a Julia PR which used && instead of if ... end and I pointed out that, as a code reader (PR reviewer), it took extra effort (and was easy to miss) branches that might or might not be executed. The example was of the form a() && b!() where b! is extremely mutate-y. (In this case, b!() moved or deleted files in the filesystem, which seemed immediately dangerous, but my brain couldn't figure it all out that this was only problematic when !a() and that this case was actually correctly guarded against).

In the afternoon, another software engineer (who doesn't know Julia) pointed out that, when speaking English with friends, sometimes he would answer the question of the form "Is it a or b" with the response "yes". Only his software engineer friends would understand - everyone else would not understand at all. Normal people just don't think that way. 🙂 This somewhat relates to my next point (EDIT: and I should have said, relates to Stefan's response above, which for many people I don't think that would occur to them).

My stance on this issue all along is that using && (or ||) is a short syntax for branching that is readable only by people with a strong PL background, and even then adds a bit of cognitive/visual load (simply to distinguish & vs &&). I feel that when working in multidisciplinary teams (mixture of scientists and software engineers) it is actively confusing to half the team. Even as a software engineer, I feel we have a strange disconnect between the logical &(::Bool, ::Bool) --> Bool and the branching &&(::Bool, ::Any) --> Any (yes, the latter isn't actually a function, but you hopefully get my point). Apart from the types themselves, in Julia I typically expect the former to be "functional" while the latter form often involves potential side-effects - especially in the second expression.

EDIT: thinking about this more, the problem here is entirely about side-effects and program flow. It's pretty easy for everyone to understand that "functional" usage of && is an optimization of &. It's relatively difficult to reconcile that they are entirely different for non-pure expressions.

@GregPlowman
Copy link
Contributor

GregPlowman commented Aug 31, 2019

In some cases, I think "precedence" is clearer with if a then b:

guard && c += 1    # probably an error because it's parsed as (guard && c) += 1
guard && (c += 1)  # parentheses required 

if guard then c += 1  # no ambiguity here

Also syntax highlighting in editors would help mark if at beginning of expression.

@CameronBieganek
Copy link
Contributor

It also has benefit of having precedent in other languages, which the if-then without end syntax doesn't seem to.

It should be noted that there is a lot of precedent for the if-then-else syntax:

And that syntax can be used for one-liners in all of the above languages.

@KronosTheLate
Copy link
Contributor

I have been aware of this discussion for all of an hour, so forgive me if this has been suggested.

It seems to me like the short-circuit options of '&&' and '||' are used because we want simple if-statements in one line, without semi-colons. But short-circuiting seems like a fix that creates another problem: As a humam, it is hard to comprehend and parse that syntax without having seem it before, or knowing that the second expression is only evaluated when nessecary. The un-intuitive reading (as a human) seems to be due to both visual and logical imperfections with short-circuiting. It even seems that after knowing what it means, it can be harder-than-should-be-nessecary to read.

If I am not mistaken, both problems could be fixed with a macro:

@if condition result

Negation could be handled with a ! before condition, or alternatively an @ifnot macro. It is just me, or is that free of ambiguity for the computer, easy to read for the human, and all in one line?

@CameronBieganek
Copy link
Contributor

CameronBieganek commented Aug 23, 2020

It even seems that after knowing what it means, it can be harder-than-should-be-nessecary to read.

^ I strongly agree with this.

@if condition result

You can already do the following, which looks nearly the same:

if condition result end

Below is a macro for the full if-then-else syntax. However, since if and else are reserved keywords, the macro uses If, Then, and Else instead.

syntax_error() = error("Valid syntax is either `@If cond Then ex` or `@If cond Then ex1 Else ex2`")

function If(exprs...)
    n_args = length(exprs)

    if n_args == 3
        if exprs[2] != :Then
            syntax_error()
        end

        ex = quote
            if $(exprs[1])
                $(exprs[3])
            end
        end
    elseif n_args == 5
        if ( exprs[2] != :Then ) || ( exprs[4] != :Else )
            syntax_error()
        end

        ex = quote
            if $(exprs[1])
                $(exprs[3])
            else
                $(exprs[5])
            end
        end
    else
        syntax_error()
    end

    return esc(ex)
end

macro If(exprs...)
    If(exprs...)
end

foo(x) = @If x > 0 Then println("greater than zero") Else println("less than zero")

And here we can see foo in action:

julia> foo(3)
greater than zero

julia> foo(-3)
less than zero

@RaulDurand
Copy link

I would prefer something like

x0 = 1
x1 = 2
x3 = 3 when y>0  # y>0 is evaluated first
x4 = 4

when would have less precedence than = and operated from right to left.

@KronosTheLate
Copy link
Contributor

You can already do the following, which looks nearly the same:

if condition result end

I did not know that! I always though that one needed the semicolons where there is normally a newline. So in my case, the syntax

if condition result end

completely makes the use of && redundant, and so there is no problem! I see that one can even do the following:

if condition result_1 else result_2 end

In this case, adding the keyword end is probably easier than making a macro. But thanks for taking the time to make it anyways ^_^ Is it a good idèa to add the possibility to one-line if-statements in the documentation? I see that if one does ?If<enter> the result is already quite a few lines... But I feel like this feature should be advertised more.

In reguards to the original discussion, I am in favor of adding "If x then y", even though I find it a bit reduntant with the one-line version of "if". But hey, write code however it makes sense to you, right?

@lawless-m
Copy link

As someone posted a link to this discussion in Discourse

Julia: "If some condition is met, I'll print a string."
Harold: "Okay, cool."

I have a soft spot for

Julia: "I'll print a string."
Harold: "Okay, cool."
Julia: "Unless some condition is true"
Harold: "Fair enough."

println("a string") unless reason > 0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:julep Julia Enhancement Proposal
Projects
None yet
Development

No branches or pull requests