Semicolons inside function declaration

I am picking up a stale package that reads a specific type of text file (IGC file if anyone out there flies gliders).

I have seen a few instances of including a semi-colon inside the function declaration, like

function read(fname::AbstractString, ::Type{IGCDocument}; parsing_mode=ParsingMode.DEFAULT, store_all_records=DEFAULT_STORE_ALL_RECORDS)
    stream = open(fname)
    return parse(IGCDocument, stream, parsing_mode=parsing_mode, store_all_records=store_all_records)
end

I can’t find any reason for that documented. The code base is about 4 years old. Is this an obsolete syntax?

1 Like

I appreciate the help, I tried to separate to independent questions. One question was the “type annotation with no name”, the other was “semi colon in the function declaration”. I am seeing how the first works. Can you clue me in on the semi colon?

This separates positional arguments from keyword arguments

5 Likes

Sorry about that - I think I was biased by the previous one :slight_smile:

@gdalle provided the answer.

julia> f(a, b) = a + b
f (generic function with 1 method)

julia> g(a; b) = a + b
g (generic function with 1 method)

julia> f(1, 2)
3

julia> g(1, 2)
ERROR: MethodError: no method matching g(::Int64, ::Int64)

Closest candidates are:
  g(::Any; b)
   @ Main REPL[2]:1

Stacktrace:
 [1] top-level scope
   @ REPL[4]:1

julia> g(1; b=2)
3

@algunion I appreciate the help, the “Holy Trait” was new to me and didn’t come up in any of the searching I did.

1 Like

@gdalle Tricky, thank you that explains something else i saw in that code base.

2 Likes

You can find documentation for this syntax here: Functions · The Julia Language

2 Likes

@dylanxyz. Thank you.

Is there a case when calling a function via g(1; b=2) and g(1, b=2), i.e., using or not using semicolon in calling a function, matters?

1 Like

IMO the best practice is to use ; explicitly to avoid syntactic ambiguity. Consider this:

julia> sum(i for i = 1:2, init = 2)
3

julia> sum(i for i = 1:2; init = 2)
5

Source:

3 Likes

Can you explain the difference between comma and semi-colon here? In @Xing_Shi_Cai’s question, I would expect g(1; b=2) to work the same either way, and the semi-colon seems mostly useful for the human reader.

But in the sum, I don’t understand how it’s parsed. If I use an empty collection the init seems to be treated the same both ways:

julia> sum([]; init=1)
1

julia> sum([]; init=0)
0

What is going on with sum(i for i = 1:2, init = 2)?

1 Like

The whole of i for i = 1:2, init = 2 is parsed as a comprehension with two loops. You can use a comma there if you separately ensure the comprehension doesn’t gobble it up — it’s effectively a precedence issue:

julia> sum((i for i = 1:2), init = 2)
5

The explicit semicolons are useful to ensure that everything that follows is a kwarg, and they also solve this particular precedence problem. They’re required to separate optional args from kwargs in function definitions. And at call sites they enable the special variable-name-is-kwarg-name syntax:

julia> init = 2
2

julia> sum(i for i = 1:2; init)
5
4 Likes

In addition to the above reply, it’s possible to visualize the parse tree using JuliaSyntax.jl:

julia> using JuliaSyntax

julia> parsestmt(SyntaxNode, "sum(i for i = 1:2, init = 2)")
line:col│ tree                                   │ file_name
   1:1  │[call]
   1:1  │  sum
   1:5  │  [generator]
   1:5  │    i
   1:10 │    [cartesian_iterator]
   1:10 │      [=]
   1:11 │        i
   1:14 │        [call-i]
   1:15 │          1
   1:16 │          :
   1:17 │          2
   1:19 │      [=]
   1:20 │        init
   1:27 │        2
2 Likes

Thanks, I never would have guessed two comprehensions. I can now kind of understand it, since it is kind of like this:

julia> sum(i for i in 1:2, j in 1:2)
6

Part of my discomfort is that init = 2 looks like a left assignment, just like a statement x = 2 except it’s not. I don’t expect init = 2 to somehow affect i, because who said so?

Also, this interpretation seems to be particular to sum and not to comprehensions in general. For example, this errors: [i for i in 1:2; init=1] but it’s fine in sum.

I find BenchmarkTools.@btime more sensible, with setup = (i = 2). I suppose this is for macros, so I might prefer something like @sum(i for i in 1:2; setup = (i = 2)), which I find much clearer. Maybe the @ is ugly but I think it helps signal that we’re only initializing (EDIT: the dummy variable) i within the comprehension but not outside, and that setup is merely a keyword/container for that info.

Or maybe there is a better way to think of init = 2 so it feels more natural.

Ehhh, that’s still pretty confused. There are three very different concepts happening here:

  • You can specify multiple loops in a single generator.

    The generator (i,j) for i in 1:2, j in 3:4 is the same as:

    for j in 3:4
        for i in 1:2
            (i,j)
        end
    end
    

    And that’s the same as:

    for j in 3:4, i in 1:2
       (i,j)
    end
    
    julia> foreach(println, ((i,j) for i in 1:2, j in 3:4))
    (1, 3)
    (2, 3)
    (1, 4)
    (2, 4)
    
    julia> for j in 3:4
               for i in 1:2
                   println((i,j))
               end
           end
    (1, 3)
    (2, 3)
    (1, 4)
    (2, 4)
    
    julia> for j in 3:4, i in 1:2
               println((i,j))
           end
    (1, 3)
    (2, 3)
    (1, 4)
    (2, 4)
    

    And then, of course, you can exchange ins and =s in all three.

  • The sum function can take an init keyword that is the “initializer” of your sum. t’s not affecting the i in your generator at all. It’s just the thing that the first element from the generator gets added to. (As an aside, it’s actually required to be an additive identity as it may get incorporated more than once with a parallel algorithm.)

  • Macros can do whatever magic they want.

3 Likes

In case it’s not clear, Julia’s syntax allows in, or = to be used interchangeably in for loops and generator expressions. As documented here: Repeated Evaluation: Loops. I agree with you though, seeing = used for this purpose makes me uncomfortable, too :laughing:

1 Like

I think this is probably disambiguated the wrong way. The parser should prefer the keyword argument interpretation here. Passing a generator with nested loops to a function is weird and if you really meant that you can either parenthesize it or use multiple for keywords.

4 Likes