Usage rules for bare underscores

I’ve seen a few examples where one can use a bare underscore in an expression, e.g.,

julia> map(_ -> "hello", 1:4)
4-element Array{String,1}:
 "hello"
 "hello"
 "hello"
 "hello"

julia> ["hello" for _ in 1:4]
4-element Array{String,1}:
 "hello"
 "hello"
 "hello"
 "hello"

julia> for _ in 1:4
           println("hello")
       end
hello
hello
hello
hello

But of course the following doesn’t work:

julia> _ = 2
2

julia> _
ERROR: all-underscore identifier used as rvalue

However, I haven’t seen anything in the docs that describes when you can use a bare underscore and when you can’t. Does anyone know where this is documented, or if it’s not documented, can someone spell out the rules regarding bare underscores in expressions?

4 Likes

_ is used when you syntactically need a variable, but never use it. This is because there are lots of times (like your examples above) where you want a loop, which syntactically would need a variable to be defined. The reason for this convention is that knowing that a variable won’t be used makes code easier to read often.

4 Likes

Yeah, I get that. My question is, exactly when does the Julia parser allow a bare underscore and when does it not? Perhaps there are some interesting use cases that I’m missing! :slight_smile:

1 Like

The error message gives a pretty decent description. You can never use _ as an rvalue, ie as something that goes on the right half of an equal sign. https://stackoverflow.com/questions/9406121/what-exactly-is-an-r-value-in-c#9406672 does a pretty good job explaining (for c, but the terminology is the same).

5 Likes

Thanks. That SO post is helpful. That makes sense for the array comprehension and the for loop, since those can be written using assignment syntax:

julia> ["hello" for _ = 1:4]
4-element Array{String,1}:
 "hello"
 "hello"
 "hello"
 "hello"

julia> for _ = 1:4
           println("hello")
       end
hello
hello
hello
hello

However, the anonymous function syntax is still a little hard to understand:

julia> _ -> "hello"
#31 (generic function with 1 method)

Why is the underscore in that example considered an lvalue?

1 Like

You don’t ever use its value. By using the name _ for an argument, you are saying “I will never use this”.

6 Likes

To be honest, I don’t see how that has anything to do with whether something is an lvalue. Presumably, in the expression x -> 2x, the x on the left is an lvalue, but it’s not because “You don’t ever use its value.” My guess here is that function arguments can be considered lvalues in some sense? Furthermore, is it true that a bare underscore can be used anywhere that an lvalue can occur? Which begs the question, which expressions or parts of expressions in Julia are considered lvalues? The above SO post partially answers that question, but I think we’re still left with the question of what exactly counts as an lvalue in Julia and what exactly counts as an rvalue.

EDIT: I don’t think the answer to these questions is particularly urgent or important, but I am curious…

when I write

f(x) =  ... blah blah blah ... 

the variable x is an l-value because it get’s assigned to when I do a function call. That is, when I say f(2), a function scope will be created and inside that scope, there will be an assignment x = 2. So long as I don’t use x inside the function body, it’s perfectly legitimite to write

f(_) =  ... blah blah blah ... 

instead.


Edit: r-value -> l-value

3 Likes

Thanks! That makes sense. Though I’m guessing you meant “the variable x is an l-value”.

2 Likes

Yes, you can think of them as being assigned the arguments when the function is called.

Whether something is an lvalue or not is a syntactic property orthogonal to being _.

Personally, I don’t think in terms of lvalues/rvalues for _ (that’s the compiler’s job :wink:). I would just go with this simple heuristic: if syntax requires a variable which you would otherwise not use, feel free to rename it _.

7 Likes

I’m wondering about that as well—when does the Julia parser allow a bare underscore.

I have found an example that (I think) is not explained by what has been discussed so far, and it so happens it’s something I’d like to use in a module I’m working on.

Say a physical property c is usually a function some other T, i.e., c:c(T), but for a specific model x, c degenerates to a constant. As to prove a concept, I’m coding the model x as a custom type, say: myModel.

I’ve defined a working c(x::myModel) which returns the proper constant, but for the sake of interface, I’d like to have a c(x::myModel, _) = c(x) version of it—the _ argument would be the other T that will never be used.

As for the breaking example, my original c(x::myModel) accepts keyword args. And I’ve found out that the existence of keyword arguments causes the julia-1.2.0 parser to complain about the _ argument:

A minimum example to illustrate keyword args causes complaints with _ args follows:

julia> c(x) = 3x # Fallback version (just a silly example)
c (generic function with 1 method)

julia> c(x, _) = c(x) # This works fine
c (generic function with 2 methods)

julia> c(x, _; double::Bool=false) = double ? 2c(x) : c(x) # _ is never used but parser complains
ERROR: syntax: all-underscore identifier used as rvalue around REPL[21]:1
Stacktrace:
 [1] top-level scope at REPL[21]:1

julia> c(x, _; double::Bool) = double ? 2c(x) : c(x) # This also fails
ERROR: syntax: all-underscore identifier used as rvalue around REPL[22]:1
Stacktrace:
 [1] top-level scope at REPL[22]:1

I’m wondering whether the existence of keyword args invalidating the use of _ arguments might be a bug.

EDIT: I’ve found out that c(x, ::T_Type; whatever) works fine and solves the case issue; however, it doesn’t answer OP’s question.

1 Like

Yes, you are running into the bug

2 Likes

Oh, yes. Thank you!!

1 Like

For any beginners who may find this post, I just wanna point out that the underscore can also be used as a digit grouping delimiter, to help readers visually parse big numbers. For example:

julia> 10_000 + 20_000
30000

Or you may prefer using the underscore to make it harder for people to read your code:

julia> 3_4.5_6_7_890_123 + 0_1
35.567890123
5 Likes