User Tools

Site Tools


controlflow

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

controlflow [2018/12/28 11:18] (current)
Line 1: Line 1:
 +
 +~~CLOSETOC~~
 +
 +~~TOC 1-3 wide~~
 +
 +```juliarepl
 +julia> pkgchk.( [ "​julia"​ => v"​1.0.3"​ ] );
 +```
 +
 +
 +# 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.
 +
 +## Comments
 +
 +```julianoeval
 +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
 +
 +Spaces are often but not always ignored. ​ For example,
 +
 +```juliarepl
 +julia> ( [ 1 -2 ] == [ 1-2] ) ## the first is a 1x2 matrix; the second is a 1x1 vector
 +false
 +```
 +
 +* 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
 +
 +```juliarepl
 +julia> (x = 3; y = 4) ## the last answer is always stored into '​ans'​ (and usually printed)
 +4
 +
 +julia> begin; x= 4; y= 5; end; ## suppress printing ans with trailing semicolon
 +
 +julia> ans ## ans was still assigned, of course
 +5
 +```
 +
 +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 [[arraysintro#​tuples|tuples]]. ​ This can cause non-descript or baffling errors:
 +
 +```juliarepl
 +julia> (const x= 3; const y= 4) ## grouped content, two statements
 +4
 +
 +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
 +true
 +
 +julia> (true,​) ##​ this parenthesis is a single tuple, indicated by the trailing (otherwise useless) comma
 +(true,)
 +
 +julia> (  (  ( (true,​false) )  )  ) ## not nested singleton-tuplets! only innermost defines tuple, rest are groupings
 +(true, false)
 +
 +julia> (  (  ( (true), )  ),  ) ## two groupings, two tuplings
 +((true,),)
 +```
 +
 +
 +## Line Continuation
 +
 +
 +There is no explicit line continuation symbol (like '​\'​ at the line end).  Instead, use
 +
 +```juliarepl
 +julia> (const x= 3;
 +         const y= 4); ## grouping automatically extends over multiple lines ##
 +```
 +
 +
 +## Multiple Assignments
 +
 +```juliarepl
 +julia> const a, b= 10, 12 ## two assignments (to const a and const b)
 +(10,12)
 +
 +julia> b
 +12
 +
 +julia> const (c, d)= (2, 3) ## same assignment
 +(2,3)
 +
 +julia> c
 +2
 +```
 +
 +
 +## Inferred Multiplication
 +
 +```juliarepl
 +julia> 2pi ## no ship.  this is 2*pi
 +6.283185307179586
 +
 +julia> const x=3; 2^2x ## binds closer, so this is not 4*3, but 2^6
 +64
 +
 +```
 +
 +
 +## 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.
 +
 +```juliarepl
 +julia> const x= 3
 +3
 +
 +julia> :x ## typeof(:x) is Symbol
 +:x
 +
 +julia> eval(:x)
 +3
 +
 +julia> dump(:​x) ##​ dump is the universal rat (informant)
 +Symbol x
 +
 +julia> Symbol("​x"​)
 +:x
 +```
 +
 +However, symbols can also hold expressions. ​ `:a+3` is an error, because it means `(:a)+3`; but `:(a+3)` is ok.
 +
 +
 +```juliarepl
 +julia> Symbol(" ​  2 + 1"​) ##​ useless symbol. ​ note the ans without ':'​
 +Symbol(" ​  2 + 1")
 +
 +julia> eval(ans)
 +ERROR: UndefVarError: ​   2 + 1 not defined
 +Stacktrace:
 +
 +```
 +
 +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
 +
 +```juliarepl
 +julia> exp( log(5) ) ## Nested function
 +5.0
 +
 +julia> (exp∘log)(5) ##​ Same nesting, but written differently
 +5.0
 +
 +julia> log(5) |> exp ## Same nesting, but written differently
 +5.0
 +```
 +
 +* 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](https://​github.com/​MikeInnes/​Lazy.jl/​blob/​master/​README.md#​macros),​ you can do `@> x g f(y, z) == f(g(x), y, z)`.
 +
 +## Function Semicolons
 +
 +```juliarepl
 +julia> function f(); 1; end#​function;​ ##​ works as expected
 +
 +julia> function f() 1 end#​function;​ ##​ also works as expected
 +```
 +
 +
 +
 +## Booleans and Conditionals
 +
 +```juliarepl
 +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
 +Stacktrace:
 +
 +```
 +
 +However, unlike in perl, `&&​` has precedence over `=`, which causes an error in the following construct:
 +
 +```juliarepl
 +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
 +3
 +
 +```
 +
 +
 +* julia has `elseif`.
 +
 +* It is possible to use the [[inquiring#​Checking_if_an_Object_Exists|`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
 +
 +```juliarepl
 +julia> (true) ? "​10"​ : "​20"​ ##​ works only for scalars
 +"​10"​
 +
 +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}:​
 + 1
 + 2
 +```
 +
 +
 +### Multiple Conditions (Chained Comparisons)
 +
 +Julia has unusual chained comparisons,​
 +
 +```juliarepl
 +julia> 1 <= 2 <= 3 ## same as ((1 <= 2) && (2 <= 3))
 +true
 +
 +julia> 1 .<= (0, 2, 4, 6) .<= 4
 +(false, true, true, false)
 +```
 +
 +(The [[funother#​dot-prefix_operators|dot-prefix]] in the `.<=` operator denotes the element-wise comparison.)
 +
 +
 +## Loops
 +
 +### 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:
 +
 +```juliarepl
 +julia> for i in ("​a",​ "​b"​) ; end;
 +
 +julia> for i= 1:2 ; end;
 +```
 +
 +More unusual, nested for loops can be represented in one statement:
 +
 +```juliarepl
 +julia> for i= 1:2, j= 3:4
 +           ​println( (i, j) )
 +       ​end#​for^2##​
 +(1, 3)
 +(1, 4)
 +(2, 3)
 +(2, 4)
 +```
 +
 +
 +### Special Loops for Vectors and Tuples
 +
 +#### Comprehensions (Generation of Vectors)
 +
 +```juliarepl
 +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}:​
 + 1
 + 2
 + 3
 +
 +```
 +
 +
 +#### Maps (Transformation)
 +
 +```juliarepl
 +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)
 +
 +```juliarepl
 +julia> filter( x->(x >= 2), [1,2,3] )
 +2-element Array{Int64,​1}:​
 + 2
 + 3
 +```
 +
 +
 +
 +## 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](https://​discourse.julialang.org/​t/​scope-and-global-in-repl-julia-0-7/​10233) and [github](https://​github.com/​JuliaLang/​julia/​issues/​28750) and
 +[discourse](https://​discourse.julialang.org/​t/​new-scope-solution/​16707).)
 +
 +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.
 +
 +```juliarepl
 +julia> global x=0 ## makes it clear that we will want this variable
 +0
 +
 +julia> for i=1:2
 + global x ## needed! ​ this is even though we are not in a function.
 +        x+= 1
 +       ​end#​for##​
 +
 +julia> x
 +2
 +
 +```
 +
 +* 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
 +
 +```juliarepl
 +julia> x, y= -1, -2; ## global variables
 +
 +julia> function inner1(); x= 10; x+y; end;##​function##​ ##​ x is a local variable
 +
 +julia> inner1()
 + 8
 +
 +julia> ​ x
 +-1
 +
 +julia> function inner2(); global x= 10; x+y; end;##​function##​ ##​ x is the global variable
 +
 +julia> inner2()
 + 8
 +
 +julia> ​ x
 + 10
 +```
 +
 +
 +### A Function Modifying A Variable In An Upper Scope
 +
 +Nested functions can change up-scope values (global is not an up-scope):
 +
 +```juliarepl
 +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 [[funothers#​persistent_local_variables|functions]].
 +
 +
 +
 +## Warnings and Errors
 +
 +To suppress other packages'​ stdout or stderr output, you can use [Suppressor](https://​github.com/​JuliaIO/​Suppressor.jl) ​ 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](https://​docs.julialang.org/​en/​v1/​stdlib/​Logging/​index.html).
 +
 +### stderr
 +
 +To print to stderr, use `println(stderr,​ ...)`.
 +
 +### @info
 +
 +`@info()` emits cyan highlighted text to stderr, preceded by `[ info`.
 +
 +### @warn
 +
 +`@warn` emits yellow highlighted text to stderr, preceded by standardized warning message.
 +
 +### @error
 +
 +`@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.
 +
 +### @assert
 +
 +The macro `@assert` that allows you to provide a useful error message:
 +
 +```juliarepl
 +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!)
 +Stacktrace:
 +
 +julia> @assert( true, "​everything is a-ok" ) ## much better!
 +
 +```
 +
 +
 +## Exceptions
 +
 +### Exception (Error) Trapping
 +
 +Try/​catch/​finally works as expected:
 +
 +```juliarepl
 +julia> f(x)= try
 +           ​sqrt(x)
 +       catch optionalerrormsgname
 +           ​println(optionalerrormsgname)
 +           sqrt( complex(x, 0) )
 +       ​end;##​try##​
 +
 +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
 +
 +```juliarepl
 +julia> function err2nan( fin::​Function )::Function
 +    wrapped(x)= ( y= NaN;  try y= fin(x); catch; NaN; end; y )
 +    wrapped
 +end;##​function##​
 +
 +julia> safesqrt= err2nan( sqrt );
 +
 +julia> safesqrt( -1.0 )
 +NaN
 +```
 +
 +* `safesqrt` works, but it is very inefficient compared to `sqrt`. ​ For this particularly case, you could use [NaNMath](https://​github.com/​mlubin/​NaNMath.jl) for better performance.
 +
 +
 +### Throwing Non-Specialized Errors
 +
 +```juliarepl
 +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
 +Stacktrace:
 +
 +```
 +
 +* 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
 +
 +```juliarepl
 +julia> f(x)= (x >= 0) ? exp(-x) : throw(DomainError())
 +f (generic function with 1 method)
 +
 +julia> throw( UndefVarError(:​x) )
 +ERROR: UndefVarError:​ x not defined
 +Stacktrace:
 +
 +```
 +
 +The generic `error()` throws an `ErrorException`.
 +
 +
 +### Stop()ping Execution of Program
 +
 +```juliarepl
 +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
 +STOP STATEMENT ENCOUNTERED AT none:1
 +```
 +
 +* See also `?​showerror`.
 +
 +
 +## Trapping Operating System Signals, ^C
 +
 +The following code works on *nix, but not on Windows:
 +
 +```julianoeval
 +julia> try
 +           # code that might take a while, e.g.
 +           ​println("​Please type ^C")
 +           ​sleep(1000)
 +       catch ex
 +           if ex isa InterruptException
 +               ​println("​ok,​ you interrupted me")
 +           else
 +               ​rethrow()
 +           end
 +       end
 +^Cok, you interrupted me.
 +```
 +
 +
 +## Setting a Program Timeout
 +
 +```julirepl
 +julia> function timeout(timelimit,​ func)
 +    t1 = Task(func)
 +    t2 = Task() do
 +            sleep(timelimit)
 +            schedule(t1,​ "​execution of closure didn't finish within the $timelimit seconds time limit",​ error=true)
 +    end
 +    schedule(t1)
 +    schedule(t2)
 +    s = fetch(t1)
 +    return s
 +end
 +
 +julia> timeout(2, () -> sleep(5))
 +ERROR: "​execution of closure didn't finish within the 2 seconds time limit"
 +Stacktrace:
 + [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](https://​docs.julialang.org/​en/​v1/​stdlib/​Test/​index.html).
 +
 +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.
 +
 +```julianoeval
 +
 +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))"​ )
 + ...
 +end#if
 +```
 +
 +* 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
 +
 +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.
 +
 +```juliarepl
 +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
 +            end#for
 +            put!(c, "no more"​) ##​ finally caller will stop here and return
 +        end;#​function##​
 +
 +julia> cnsmchnl= Channel(producer);​ ##​ the producer function will feed, Main will consume
 +
 +julia> take!(cnsmchnl)
 +"​start"​
 +
 +julia> take!(cnsmchnl)
 +2
 +
 +julia> take!(cnsmchnl)
 +4
 +
 +julia> take!(cnsmchnl) ##​ closes, because producer() will reach end of function
 +"no more"
 +
 +julia> take!(cnsmchnl)
 +ERROR: InvalidStateException("​Channel is closed.",​ :closed)
 +Stacktrace:
 +
 +```
 +
 +* 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.
 +
 +
 +
 +# Backmatter
 +
 +## Useful Packages on Julia Repository
 +
 +## Notes
 +
 +* Discuss Interfaces for
 +
 +  - Iteration (start, next, done; and more)
 +  - Indexing (getindex, setindex, endof) and AbstractArray (advancing index, etc.)
 +
 +
 +## References
 +
 +* For more information on exceptions and their handling, refer to the [Julia Language Docs](https://​docs.julialang.org/​en/​stable/​manual/​control-flow.html#​Exception-Handling-1)
 +
 +
 +
 +
  
controlflow.txt · Last modified: 2018/12/28 11:18 (external edit)