What does a function argument preceded by a colon do

I have started to notice that some packages (like Makie and Plots) require passing arguments to functions that are preceded by a colon.

For example, the Makie.jl basic tutorial https://docs.makie.org/stable/tutorials/basic-tutorial/ gives the following code to change the background color of a figure

f = Figure(backgroundcolor = :tomato)

As another example, under the Plots.jl documentation for setting attributes https://docs.juliaplots.org/latest/attributes/ they provide the following example to show how to set attributes corresponding to a series marker.

scatter(y, marker = (:hexagon, 20, 0.6, :green, stroke(3, 0.2, :black, :dot)))

From the Julia documentation about punctuation doing something like :a implies that a is a symbol. But this doesn’t really help me understand what is happening or if it even applies here.

Any further insight to what is going on here would be really helpful!

Yep, that’s a Symbol. Symbols can represent variables in code but here it’s just a name. Symbols are interned strings, so it helps to save memory if a name is representing something that’s always going to be around, like a color or shape setting.

You’re also passing those arguments into the function calls with keywords backgroundcolor or marker, if you were wondering about that.

1 Like

Additional to what @Benny said, consider the following snippet:

function myfun(; color::Symbol=:blue)
    @info "color is: $color"
end

myfun(color=:red)
# the above call is identical with the following:
myfun(color=Symbol("red"))

Using symbols can help when there are a limited number of values for your argument. For example, colors and shapes have a limited number of valid values (obviously, especially for colors, there are other kinds of notations, but using color names is more human-readable).

Also, although symbols have a special meaning in Julia’s language, apart from their usage in expressions and naming things (variables, constants, modules, etc.), Symbol is ultimately just another type that developers can use as they see fit inside their projects/packages.

I am not sure what language you used before, but in scenarios like the ones you presented, you can see symbols being used similar to an enum.

Julia also has enum - however, many developers will just use Symbol instead:

Imagine the following, using the enum approach:

@enum Color red = 1 green = 2 blue = 3

function myfun(;color::Color=blue)
    @info "color is $color"
end

myfun(red)

The above can be a legitimate approach, but I think many of us feel that it is just not worth the few extra lines of code. So, instead of that above approach, in many scenarios, you’ll encounter the symbol usage (myfun(color=:red).

2 Likes

Thanks for adding to what @Benny said. Both yours and their information helped clear things up for me and also provide context for understanding the use.

Just to make sure I understand what is happening. Do the packages assign some value to the symbol being used? For example, the symbol :red might evaluate to "red"? Or to reuse your enum example, if we did

function myfun(;color=:green)
    @info eval(:color)
end

myfun(color=:red)

would we get back 1?

1 Like

eval executes in the global scope (and it must because of limited fixed space in a function call’s stackframe), so it’s not going to access the color inside myfun, that’s what color itself is for.

julia> myfun(;color=:green) = eval(:color)
myfun (generic function with 1 method)

julia> myfun()
ERROR: UndefVarError: color not defined
...

julia> color = "hello"
"hello"

julia> myfun()
"hello"

This is not how it works - instead of using eval and trying to retrieve some value associated with a global name called :red, the :red is likely used as it is (depending on the package) - to retrieve a value from a dictionary mapping symbols to some color encodings and then use the retrieved value.

3 Likes

Perhaps this deserves another topic but is this a better method then using a dictionary?

Right now I have a function which uses a dictionary with the keys “cpu”, “threads”, “gpu” and depending on which key is passed (as a string) to the function, will process things on the appropriate device. Would it be better if I had the user pass a symbol (like :cpu') where cpu` stores a function?

1 Like

As long as you properly document things, a string can also be legitimate. However, the symbol usage would more clearly transmit the idea that we are talking about some classes/categories (although it is hard to miss that anyway when people are seeing cpu / gpu / threads as the available options).

However, I think is more idiomatic to go with the Symbol approach in your scenario.

1 Like

Thanks to both @Benny and @algunion for all the help with this. There isn’t one solution, but I feel you both have given me enough that I had no further questions with this topic, so I marked what I thought captured what I wanted to know best as the solution (though it couldn’t be understood outside of the context of this entire post).

1 Like

Basically, both strings or symbols can be used as names. The main difference being that strings are also a sequence of characters, i.e., a data structure in its own right, whereas symbols are just opaque names, i.e., can essentially only be compared for equality which allows some optimizations. Further, symbols are also used by the compiler itself as names of variables, operators etc. As you can try in the REPL, when defining a global variable its name and assigned value are stored in a module:

julia> x = 10
10

julia> getproperty(Main, :x)  # same as Main.x
10

For configuration options consisting of a few literals, e.g., naming colors as in your example above, I usually prefer symbols to strings as this communicates that only their name is of interest. Symbols are also (slightly) easier to type and read in Julia.

2 Likes