User Tools

Site Tools

julia> pkgchk( [ "julia" => v"1.0.2" ] )

Control Flow Notes

This chapter assumes familiarity with other programming languages. It describes some aspects of control flow statements that are not in most other Algol-derived languages. It covers conditionals, Booleans, exceptions, channels, etc.

In Julia 1.0.1, the use of global variable inside functions can dramatically slow down julia. This is not the case if the variables are declared const. (Local variables cannot be declared const.) Advice:

  1. avoid the use of global variables inside functions.
  2. if the use of globals inside functions is unavoidable, see if a const version of the global variable can do.
  3. if you need a modifiable global variable, be aware that this may badly impact performance all around.


[download only julia statements]
julia>		## this comment goes to the line end.  no multi-line comments in 1.0 afaik
julia> """
but you can define
an informative
string and then
simply ignore it
later on.


Spaces are often but not always ignored. For example,

julia> ( [ 12 ] == [ 12] )	## the first is a 1x2 matrix; the second is a 1x1 vector
  • Julia sometimes requires spacing–e.g., in the trinary (true) ? 1 : 0.
  • Julia also insists on no extra space between function name and opening parenthesis.

PS: Spaces have always had meaning in tokens, such as && or == in other languages, too.

Grouping Multiple Statements

julia> (x = 3; y = 4)		## the last answer is always stored into 'ans' (and usually printed)

julia> begin; x= 4; y= 5; end;	## suppress printing ans with trailing semicolon

julia> ans			## ans was still assigned, of course

The version with begin and end behaves the same as the version with ( and ).

Parentheses for Grouping Vs. Tuples

Parentheses serve other functions, too–specficially also for read-only lists of objects called tuples. This can cause non-descript or baffling errors:

julia> (const x= 3; const y= 4)		## grouped content, two statements

julia> typeof( ans )
Int 64

julia> (x= 3, y= 4)			## the comma instead of semi-colon
(x = 3, y = 4)

julia> typeof( ans )			## ... means that you have defined a named tuple
NamedTuple{(:x, :y),Tuple{Int64,Int64}}

julia> (  (  ( (true) )  )  )		## all parentheses here do grouping, *not* tupling

julia> (true,)				## this parenthesis is a single tuple, indicated by the trailing (otherwise useless) comma

julia> (  (  ( (true,false) )  )  )	## not nested singleton-tuplets! only innermost defines tuple, rest are groupings
(true, false)

julia> (  (  ( (true), )  ),  )		## two groupings, two tuplings

Line Continuation

There is no explicit line continuation symbol (like '\' at the line end). Instead, use

julia> (const x= 3;
         const y= 4);			## grouping automatically extends over multiple lines ##

Multiple Assignments

julia> const a, b= 10, 12		## two assignments (to const a and const b)

julia> b

julia> const (c, d)= (2, 3)		## same assignment

julia> c

Inferred Multiplication

julia> 2pi			## no ship.  this is 2*pi

julia> const x=3; 2^2x		## binds closer, so this is not 4*3, but 2^6

Symbols and Variable Names (Colon, Eval)

Symbols are a specific type. They are primarily useful when they represent “clean” strings, meaning that they could be variable names–no spaces, not starting with digits, etc.

julia> const x= 3

julia> :x			## typeof(:x) is Symbol

julia> eval(:x)

julia> dump(:x)			## dump is the universal rat (informant)
Symbol x

julia> Symbol("x")

However, symbols can also hold expressions. :a+3 is an error, because it means (:a)+3; but :(a+3) is ok.

julia> Symbol("   2 + 1")		## useless symbol.  note the ans without ':'
Symbol("   2 + 1")

julia> eval(ans)
ERROR: UndefVarError:    2 + 1 not defined

To convert to string "x", use String( :x ). To convert from string to :x, use Symbol("x"). Usefully, you can also write eval(:x) to get the content of the variable x.

Function Chaining

julia> exp( log(5) )		## Nested function

julia> (exp∘log)(5)		## Same nesting, but written differently

julia> log(5) |> exp		## Same nesting, but written differently
  • This syntactic sugar works only for functions with only one argument. If a function has other arguments, you could f(g(garg),farg) == g(garg) |> x->f(x,farg).
  • With lazy evaluation lists, you can do @> x g f(y, z) == f(g(x), y, z).

Function Semicolons

julia> function f(); 1; end#function;		## works as expected

julia> function f() 1 end#function;		## also works as expected

Booleans and Conditionals

julia> ((true) || (false)) && "proper short-circuiting"		## && is "and"; || is "or";  use can be "perl"-like
"proper short-circuiting"

julia> (1) && print("ints do not work as booleans; use (x!= 0)")	## picky: an integer is not Boolean
ERROR: TypeError: non-boolean (Int64) used in boolean context

However, unlike in perl, && has precedence over =, which causes an error in the following construct:

julia> (true) && x= 3		## means ((true) && x)= 3
ERROR: syntax: invalid assignment location "true && x"

julia> (true) && (x= 3)		## this is probably what you really wanted
  • julia has elseif.
  • It is possible to use the `isdefined()`] macro defined in [[inquiring#checking_if_an_object_exists|inquiring to establish default values (as in perl's $a= $ARGV[0] || 10). However, this is rarely useful in Julia, because variables are typed and compiled, and thus cannot be truly “empty” (as they can in perl or R).
  • For example, for command line arguments, ARGS is always defined. It starts as an x=0 x-element Array{String,1}, and the Julia test for whether there are arguments would be a= (length(ARGS)>0) || 10.

Trinary Conditionals

julia> (true) ? "10" : "20"				## works only for scalars

julia> (true)?"10":"20"					## watch for spaces
ERROR: syntax: space required before "?" operator

julia> ifelse.( [true,false], [1,1], [2,2] )		## for arrays, use ifelse.
2-element Array{Int64,1}:

Multiple Conditions (Chained Comparisons)

Julia has unusual chained comparisons,

julia> 1 <= 2 <= 3	## same as ((1 <= 2) && (2 <= 3))

julia> 1 .<= (0, 2, 4, 6) .<= 4
(false, true, true, false)

(The dot-prefix in the .<= operator denotes the element-wise comparison.)


Ordinary Program Logic

  • while works as expected.
  • Loops can use break and continue.

For Loops

There is no generic C-style three-part for(start; cond; iter). Only the following two for constructs are available:

julia> for i in ("a", "b") ; end;

julia> for i= 1:2 ; end;

More unusual, nested for loops can be represented in one statement:

julia> for i= 1:2, j= 3:4
           println( (i, j) )
(1, 3)
(1, 4)
(2, 3)
(2, 4)

Special Loops for Vectors and Tuples

Comprehensions (Generation of Vectors)

julia> y= (x for x= 1:3)
Base.Generator{UnitRange{Int64},getfield(Main, Symbol("##7#8"))}(getfield(Main, Symbol("##7#8"))(), 1:3)

julia> collect(y)		## convert the iterator into a simpler array
3-element Array{Int64,1}:

Maps (Transformation)

julia> map( x->(x+5), (1,2,3) )			## apply function in first arg to each element of second arg
(6, 7, 8)

julia> map( (1,2,3) ) do x; x+5; end#do		## internally, `do` creates an anon function as arg1
(6, 7, 8)

Filters (Removal)

julia> filter( x->(x >= 2), [1,2,3] )
2-element Array{Int64,1}:

Scope: Global, Local, Nested

WARNING Top-level global variables are different from variables one or a few levels up. This does not always make intuitive sense. Moreover, it is possible that future versions of julia may change the semantics somewhat. (For discussion, please see discourse and github and discourse.)

Newly defined variables inside a scope are local—they disappear upon leaving scope. Local scopes are introduced with

  • flow control: for, while, let, struct, do...end
  • macros, and
  • functions

but surprisingly not by begin...end.

A new variable instantiated inside a local scope can also survive the local scope if it is preceded with global. However, it is usually better and clearer to define such variables in front of the the scope to begin with.

julia> global x=0	## makes it clear that we will want this variable

julia> for i=1:2
	global x	## needed!  this is even though we are not in a function.
        x+= 1

julia> x
  • The global/local behavior does not always seem consistent. In a for loop, the inside variable is by default local. In an if statement, an inside variable is by default global. Just be careful.
  • To define a new variable with a name equal to one in an outer scope, use local. However, it is usually better and clearer to choose a different name.

A Function Modifying a Global Object

julia> x, y= –1,2;						## global variables

julia> function inner1(); x= 10; x+y; end;##function##		## x is a local variable

julia> inner1()

julia>  x1

julia> function inner2(); global x= 10; x+y; end;##function##	## x is the global variable

julia> inner2()

julia>  x

A Function Modifying A Variable In An Upper Scope

Nested functions can change up-scope values (global is not an up-scope):

julia> function outer()
	    x, y= –1,2
            function inner(); x= 10; x+y; end#function inner
	    ( inner() , x )
	end;#function outer##

julia> outer()
(8, 10)
  • PS: This “nested trick” is actually what facilitates static state variables inside functions with let. Let can “fix” the current value of a variable at function definition time, rather than use-time, too. The fancy compsci term for this behavior is closure. See functions.

Warnings and Errors

To suppress other packages' stdout or stderr output, you can use Suppressor Of course, this can be quite dangerous–your code may not work, and without any error appearing, you may end up thoroughly confused.

Julia's logging system is discussed in its documentation.


To print to stderr, use println(stderr, ...).


@info() emits cyan highlighted text to stderr, preceded by [ info.


@warn emits yellow highlighted text to stderr, preceded by standardized warning message.


@error emits a red highlighted text to stderr, preceeded by a standardized error message.

Throwing an error

The macro @error only logs an error message. It does not throw an exception. To throw an exception, use the error() function.


The macro @assert that allows you to provide a useful error message:

julia> const x= (99);

julia> (x>0) || @assert false "your variable x is $x (but x^2= $(x^2) would work!)"
ERROR: AssertionError: your variable x is –99 (but x^2= 9801 would work!)

julia> @assert( true, "everything is a-ok" )	## much better!


Exception (Error) Trapping

Try/catch/finally works as expected:

julia> f(x)= try
       catch optionalerrormsgname
           sqrt( complex(x, 0) )

julia> f(1)
DomainError(1.0, "sqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).")
0.0 + 1.0im

A finally clause can guarantee execution at the end.

Disabling Exceptions For One Function

To guarantee that a function that may throw an error (e.g., DomainError) will not do so, you can define

julia> function err2nan( fin::Function )::Function
    wrapped(x)= ( y= NaN;  try y= fin(x); catch; NaN; end; y )

julia> safesqrt= err2nan( sqrt );

julia> safesqrt(1.0 )
  • safesqrt works, but it is very inefficient compared to sqrt. For this particularly case, you could use NaNMath for better performance.

Throwing Non-Specialized Errors

julia> fussy_sqrt(x)= (x >= 0) ? sqrt(x) : error("negative x of $x not allowed")
fussy_sqrt (generic function with 1 method)

julia> fussy_sqrt(1)
ERROR: negative x of –1 not allowed
  • Many programmers writing primarily for themselves care less about the hierarchy or naming of exception types within subsets. In these cases, the generic error() exception works well. For more specialized exceptions, see below.

Throwing Specialized Exceptions

julia> f(x)= (x >= 0) ? exp(-x) : throw(DomainError())
f (generic function with 1 method)

julia> throw( UndefVarError(:x) )
ERROR: UndefVarError: x not defined

The generic error() throws an ErrorException.

Stop()ping Execution of Program

julia> struct Stop <: Exception end

julia> Base.display_error(io::IO, ::Stop, _)= println( io, "STOP STATEMENT ENCOUNTERED AT ", @__FILE__, ":", @__LINE__ )

julia> stop(msg::AbstractString= "")= println(msg), throw(Stop())
stop (generic function with 2 methods)

julia> function g(); stop("in G, sort of not an error but a stop"); println("gend"); end; function f(); g(); println("fend"); end
f (generic function with 1 method)

julia> f()
in G, sort of not an error but a stop
  • See also ?showerror.

Trapping Operating System Signals, ^C

The following code works on *nix, but not on Windows:

[download only julia statements]
julia> try
           # code that might take a while, e.g.
           println("Please type ^C")
       catch ex
           if ex isa InterruptException
               println("ok, you interrupted me")
^Cok, you interrupted me.

Setting a Program Timeout

[download only julia statements]
julia> function timeout(timelimit, func)
    t1 = Task(func)
    t2 = Task() do
            schedule(t1, "execution of closure didn't finish within the $timelimit seconds time limit", error=true)
    s = fetch(t1)
    return s
julia> timeout(2, () -> sleep(5))
ERROR: "execution of closure didn't finish within the 2 seconds time limit"
 [1] try_yieldto(::typeof(Base.ensure_rescheduled), ::Base.RefValue{Task}) at ./event.jl:196
 [2] wait() at ./event.jl:255
 [3] wait(::Condition) at ./event.jl:46
 [4] wait(::Task) at ./task.jl:188
 [5] fetch at ./task.jl:202 [inlined]
 [6] timeoutf(::Int64, ::Function) at ./untitled-0e9329369e7380cb5b6844213297ccf6:24
 [7] top-level scope at none:0
  • This function is especially useful with @async, so it may be better located in parallel.

Poor Man's Unit Test Code (at File's End)

Julia has complete testing facilities built in.

The poor man's way of unit-testing: each function sits in its own file and is accompanied at its end by a set of tests.

[download only julia statements]
if ( (PROGRAM_FILE != "") && (!endswith(@__FILE__, PROGRAM_FILE)) )
	@assert( f(0.2) == 1, "f(0.2) does not yield 1, as I thought it would, but $(f(0.2))" )
  • You can run the tests interactively by setting PROGRAM_FILE to the current filename.
  • This could become something like test(fnm)= ( op= PROGRAM_FILE; PROGRAM_FILE= fnm; try; include(fnm); finally: PROGRAM_FILE= op; end; ) and used as test("filename.jl").

Tasks and Channels

Tasks and channels do not exist in many other languages (like C or perl).


Channels are functions that act as FIFO queues. Thus, they can be used to make a “factory.” Channels can obviate the need for state variables in the factory function itself.

julia> function producer(c::Channel)
            put!(c, "start")						## caller will first stop here and return
            for n= 1:2
                put!(c, 2n)						## then caller will stop here and return
            put!(c, "no more")						## finally caller will stop here and return

julia> cnsmchnl= Channel(producer);					## the producer function will feed, Main will consume

julia> take!(cnsmchnl)

julia> take!(cnsmchnl)

julia> take!(cnsmchnl)

julia> take!(cnsmchnl)							## closes, because producer() will reach end of function
"no more"

julia> take!(cnsmchnl)
ERROR: InvalidStateException("Channel is closed.", :closed)
  • Channel functioning is nicely obvious in single-core operation.
  • Channels can often serve as C-like “static” local variables.
  • Channels do not yet support multi-core operation.
  • Channels can be closed at any time, even if they have not yet been exhausted.
  • It is possible to pass arguments to a channel with take! using a convenience macro
  • It is possible with yieldto() for a producer and consumer to collaborate. This can be further coordinated using current_task(), istaskdone(), istaskstarted(), task_local_storage().
  • Channels can also be buffered for better performance (filled with content waiting to be taken later). The above channel was unbuffered. It just created the return items when requested.


Useful Packages on Julia Repository


  • Discuss Interfaces for
    • Iteration (start, next, done; and more)
    • Indexing (getindex, setindex, endof) and AbstractArray (advancing index, etc.)


controlflow.txt · Last modified: 2018/12/05 19:52 (external edit)