Is there a Julia equivalent for this Python syntax

I like how in python I can set a variable in a function as None if I don’t always need to use it. And if the function does end up being called with that variable passed in I can check in the function body by doing:

id = id or str(uuid.uuid4())[0:8]

In Julia however I do:

if id === nothing
     id = string(UUIDs.uuid4())[1:8] 
else
     id = id
end

Is there a better way to do this?

1 Like
id = (id === nothing) ? string(UUIDs.uuid4())[1:8] : id
3 Likes

This one?

(id === nothing) ? string(UUIDs.uuid4())[1:8] : id
2 Likes

id = (id === nothing ? string(UUIDs.uuid4())[1:8] : id)

Edit: You don’t even need the parantheses, they’re just for readability.

That works thanks!

A (less clear) alternative would be to use short-circuit evaluation:

(id === nothing) && (id = string(UUIDs.uuid4())[1:8])

Edit: added parenthesis - thanks @GunnarFarneback

2 Likes

There is also something

id = something(id, string(UUIDs.uuid4())[1:8])

though it can have worse performance: it will also generate a uuid when id is not nothing :frowning:

That one requires parenthesis to the right of && though:

id === nothing && (id = string(UUIDs.uuid4())[1:8])
2 Likes

In Julia 1.7, there is an @something macro, which does not have these problems, so you can write this as

@something(id, string(UUIDs.uuid4())[1:8])

without worrying about performance or side-effects.

9 Likes

It is probably safer to use the isnothing(id) method, in case the behavior of === or nothing ever changes.

1 Like

Going back to the code in the question, you can just do

if id === nothing
     id = string(UUIDs.uuid4())[1:8] 
end

(no need for else id = id).

But depending on your use case you might want to do simply

function f(id = string(UUIDs.uuid4())[1:8])
   ...
end

In Python this doesn’t work because the same UUID would be used as default for all calls to f.

In Julia it works because the default expression is evaluated each time f is called without a value.

5 Likes

That seems overly defensive, there is definitely no chance that will change within 1.x. Using === is actually preferable prior to Julia 1.7, because it let the compiler reason better about type constraints.

1 Like

Intersting. That begs the question, why isnothing() exists.

1 Like

That’s a reasonable question. It’s mostly useful for higher-order functions like in filter(isnothing, x), but now that we have ==(nothing), it might not be that useful anymore.

4 Likes

That one must be marked as the solution, I guess.

The usual python x = x or something / if x is None idiom is an ugly workaround existing only because the default argument for a function is a value in Python (while Julia inherits and improves upon Lisp behavior where a default written as a function call is an expression).

Out of curiosity, what is the improvement that Julia adds to the Lisp behavior?

In Common Lisp, the behavior of mutable default value written as literals is implementation dependent.

(defun foo-literal (x &optional (y #(0)))
  (incf (aref y 0) x)
  (aref y 0))

in SBCL, when called with one argument, mutates the array set as literal (and gives a warning about that on compilation).

(defun foo-literal (x &optional (y (make-array 1 :initial-element 0)))
  (incf (aref y 0) x)
  (aref y 0))

when called with one argument, creates a new array each time.

In Julia, the expression for the default value is always treated as if it is a function call which is a plus IMO.

1 Like

Lots of answers here, but unless I’m misunderstanding this is a classic situation for multiple dispatch:

check_id(id::Nothing) = nothing
check_id(id) = string(UUIDs.uuid4())[1:8]

Now just use check_id wherever you want. If id is nothing it returns nothing. If id is anything else it returns your UUIDs construct. This will run really fast and is type-stable.

The condition ? (x = x) : (x = y) construct on the other hand may result in the compiler being unable to infer the type of x until run-time which depending on how your code is structured could really slow things down.

2 Likes