+6  A: 

Let me focus on

What is bugging me is that I can't work out why this code works—I was amazed that it doesn't just throw an error.

Why do you think so? What happens is that we have

  1. y being assigned an expression
  2. that expression being an if if() ...
  3. leading to either a TRUE or FALSE in the test
  4. leading to either one of the two branches being entered
  5. leading to the respective code being evaluated
  6. leading to its final value being the value of the right-hand-side
  7. leading to this value being assigned to y

Seems logical to me.

Dirk Eddelbuettel
Perhaps I'm over (or under) thinking this, and we can put it down to very clever parsing. For the record, when you try something like this in MATLAB, you get an error: `Illegal use of reserved keyword "if".`
Richie Cotton
R is not MATLAB though-- there is no reason why it would have to follow MATLAB's rules. Here the value of the last statement evaluated in the if block is being assigned to y. That statement was the scalar 4.
Sharpie
Python also breaks, as does C# (though you can use the `?` operator, as Ken pointed out). I think the critical bit is that the right hand side of the statement is considered as an expression and evaluated **before** the assignment is made.
Richie Cotton
+1  A: 

It's not really very deep - many languages use the "val = x ? y : z" construct for this. In R, that's just folded into an if/else construct, so you write "val = if(x) y else z" instead.

Ken Williams
Good point – I suppose it does parse in more or less the same way as a ternary operator.
Richie Cotton
+3  A: 

Control structures such as if(....) ... else ... are equivalent to function calls with the relevant (lazy-evaluated) subexpressions as the argument:

`if`(TRUE, "yes", "no")

Basically, if is a special primitive function:

R> sapply(ls("package:base", all=TRUE), function(x) is.primitive(get(x)))["if"]
  if 
TRUE 
R> sapply(ls("package:base", all=TRUE), function(x) typeof(get(x)))["if"]
       if 
"special"

The relevant section (Section 3.2.1) in the R Language Definition manual says:

Because if/else statements are the same as other statements you can assign the value of them. The two examples below are equivalent.

R> if( any(x <= 0) ) y <- log(1+x) else y <- log(x)
R> y <- if( any(x <= 0) ) log(1+x) else log(x)
rcs
@rcs: Thanks for the reference. I should probably work my way through the manual to get a clearer idea of what R is doing.
Richie Cotton
I can also recommend Chapter 13 (How R Works) in Software for Data Analysis - Programming with R (John Chambers)
rcs
+2  A: 

About second part of your question - {} is used to group expressions, it works diffrent than in a function definition. As you can read in help("Paren"):

For ‘{’, the result of the last expression evaluated.

But all expressions are evaluated in the current environment:

y <- if (TRUE) {
    print(paste("Current frame:",sys.nframe()))
    y <- 3
    z <- 5
    4
}
# [1] "Current frame: 0"
# result:
y # [1] 4
z # [1] 5

# compare to use function:
v <- if(TRUE) (function(){      
    print(paste("Current frame:",sys.nframe()))
    v <- 3
    w <- 5
    4
})()
# [1] "Current frame: 1"
v # [1] 4
w # Error: object 'w' not found

Conclusion is blocks don't behave like functions.

edit: If you want use blocks as function you could use local:

a <- if (TRUE) local({
    print(paste("Current frame:",sys.nframe()))
    a <- 3
    b <- 5
    4
})
# [1] "Current frame: 6"
a # [1] 4
b # Error: object 'b' not found

Then you could use return too:

a <- if (TRUE) local({
    print(paste("Current frame:",sys.nframe()))
    a <- 3
    b <- 5
    return(7)
    4
})
# [1] "Current frame: 6"
a # [1] 7
b # Error: object 'b' not found
Marek
@Marek: An interesting analysis. I hadn't considered that ( and { would be functions.
Richie Cotton