User Tools

Site Tools


funother

Differences

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

Link to this comparison view

funother [2019/01/01 17:34] (current)
Line 1: Line 1:
 +
 +~~CLOSETOC~~
 +
 +~~TOC 1-3 wide~~
 +
 +```juliarepl
 +julia> pkgchk.( [ "​julia"​ => v"​1.0.3"​ ] );
 +```
 +
 +
 +FIXME This chapter needs major reorganization
 +
 +
 +# Julia Functions
 +
 +## Prior Coverage
 +
 +* [[inquiring#​julia_s_function_names|Inquiring]] already described some function-related aspects of Julia. ​ For example,
 +
 +  1. Function naming conventions:​ `postfix!` alters the argument, `postfix.` applies a scalar function element-wise to an array, and `@fun` calls a macro at compile time.
 +
 +  2. To learn about existing functions:
 +
 +    * `methods(sqrt)` gives all versions of sqrt
 +    * `methodswith(Bool)` gives all functions operating on Booleans
 +    * `@which sqrt( 5 )` tells which function will be called for argument '​5'​
 +
 +* A function (like `sqrt()`) can have multiple declarations,​ depending on input type.  Each "​function"​ version is called a "​method." ​ For example, in the case of the function `Base.sqrt`,​ there are 10 methods.
 +
 +
 +
 +
 +## Function Types (and Function Names)
 +
 +Function names were already mentioned and used repeatedly (e.g., in [[inquiring#​julia_s_function_names]]). ​ This is a more elaborate explanation.
 +
 +## AT-PREFIX FUNCTIONS (@):
 +
 +These are function implemented as [macros](https://​docs.julialang.org/​en/​stable/​manual/​metaprogramming/#​man-macros-1). They are similar to the preprocessor macro functions that are in C (e.g. [#​define](https://​docs.julialang.org/​en/​stable/​manual/​metaprogramming/#​man-macros-1)). ​ Their definitions have access to types, but not to variables.
 +
 +```juliarepl
 +julia> using Printf
 +
 +julia> @printf "v = %0.2f" 0.2345
 +v = 0.23
 +
 +julia> @assert(false,"​do not know what's happening"​)
 +ERROR: AssertionError:​ do not know what's happening
 +Stacktrace:
 +
 +julia> @boundscheck @assert(1>​0,​ "code only works when 1 is positive"​)
 +
 +```
 +
 +* Many of the most useful `@` functions relate to such things as type-checking at compile-time,​ assertions, parallelization,​ and bounds-checking.
 +
 +* It is *occasionally* useful for ordinary end users to create macros. ​ Emphasis on *occasional*. ​ For the most part, they can be considered like LaTeX style files to authors&​mdash;​magic that someone else wrote, that works, and that can be but is better not tinkered with.  If you do not know what I am talking about, don't worry.
 +
 +
 +
 +
 +## DOT Functions and Operators
 +
 +Programmers need not write dot-equivalent functions. ​ Julia automatically understands that a scalar 'dot function'​ applied to an array should work on each element. ​ This works via the [[arraysmatrix#​broadcasting|Broadcasting]] feature.
 +
 +```juliarepl
 +julia> mysqrt(x) = sqrt(x)
 +mysqrt (generic function with 1 method)
 +
 +julia> mysqrt.( [1,2,3] )
 +3-element Array{Float64,​1}:​
 + 1.0
 + ​1.4142135623730951
 + ​1.7320508075688772
 +```
 +
 +
 +### DOT-PREFIX OPERATORS (.):
 +
 +When prefixed with a '​.',​ the operator works element-wise on iterables ([[arraysintro|tuples,​ arrays, iterators, etc]]). ​ (It does not work on [[structs|structs]].
 +
 +```juliarepl
 +julia> [1,2,1] .>= [3,​1,​0] ​              ## compare element-wise the contents of vectors
 +3-element BitArray{1}:​
 + false
 +  true
 +  true
 +```
 +
 +Not shown, `[1,2,1] >= [3,1,0]` gives an error, because '>​='​ is only defined for scalars and not for vectors.
 +
 +
 +FIXME Explain the `Ref()` to indicate scalar argument to vector function. ​ When is it necessary? ​ Why now and not before?
 +
 +
 +
 +### DOT-POSTFIX FUNCTIONS (.):
 +
 +Dots denote vector functions, just like dot operators, but the dot postfixes functions, rather than prefixes them.
 +
 +By default, any function defined on scalars works element-wise on the contents of arrays, too, given the dot-postfix. ​ That is, the programmer need only define the scalar function, and julia automatically recognizes the dot-postfix function for vectors. ​ See [Dot Syntax for Vectorizing Functions](https://​docs.julialang.org/​en/​latest/​manual/​functions.html#​man-vectorized-1).
 +
 +```text
 +julia> my_fun( x ) = sqrt( x )              ## define a new scalar function
 +my_fun (generic function with 1 method)
 +
 +julia> my_fun( [1, 2] )                     ## scalar function should not be used on vector
 +WARNING: sqrt(x::​AbstractArray{T}) where T <: Number is deprecated, use sqrt.(x) instead.
 +
 +julia> my_fun.( [1, 2] )                    ## with postfix dot, apply scalar op to each element of vector
 +2-element Array{Float64,​1}:​
 + 1.0
 + ​1.41421
 +```
 +
 +Eventually, the warnings will become errors. ​ Get used to using dot-postfix notation for invoking function on elements of arrays.
 +
 +There are rare reasons for a programmer to define a dot-postfix function herself, e.g., when the vector version of a function calls external vectorized GPU-code; or when functions have different meanings for scalars and arrays (e.g., multiplication).
 +
 +
 +
 +
 +
 +
 +## EXCLAMATION-POSTFIX FUNCTIONS (!):
 +
 +By convention, functions should be postfixed with '​!'​ when they plan to change their passed objects in-place. ​ Julia names these "​bang"​ functions. ​ That is, any exclamation-postfix named function should be expected to tinker with its operands:
 +
 +```juliarepl
 +julia> x= [1,2]; push!(x, 3)         ## expect x to be be modified
 +3-element Array{Int64,​1}:​
 + 1
 + 2
 + 3
 +
 +julia> x
 +3-element Array{Int64,​1}:​
 + 1
 + 2
 + 3
 +
 +julia> push!( [1,2], 3 )             ## allowed, but '​!'​ is useless, because [1,2] is ephemeral
 +3-element Array{Int64,​1}:​
 + 1
 + 2
 + 3
 +
 +```
 +
 +Sensibly, exclamation-postfix functions can operate on read-write arrays, but not on [[arraysintro#​tuples|read-only tuples]]:
 +
 +```juliarepl
 +julia> push!( (1,2) , 3 )
 +ERROR: MethodError:​ no method matching push!(::​Tuple{Int64,​Int64},​ ::Int64)
 +Closest candidates are:
 +  push!(::​Any,​ ::Any, !Matched::​Any) at abstractarray.jl:​2064
 +  push!(::​Any,​ ::Any, !Matched::​Any,​ !Matched::​Any...) at abstractarray.jl:​2065
 +  push!(!Matched::​Array{Any,​1},​ ::Any) at array.jl:​862
 +  ...
 +Stacktrace:
 +
 +
 +```
 +
 +
 +### WARNING: Be very careful writing exclamation functions
 +
 +```juliarepl
 +julia> gextern= [1,2]; ## let's see how our h! function will change this external value
 +
 +julia> h!(G)= (G=[3,​4]);​ ##​ wrong: G will be replaced by a new G.  the external G=gextern will never see the change
 +
 +julia> println( h!(gextern) ) ## As expected, h!() returns its new [3,4]
 +[3, 4]
 +
 +julia> println( gextern ) ## ... but h! created (and returned) a local G inside. it did not modify gextern'​s contents!
 +[1, 2]
 +
 +julia> h!(G)= (G.=[3,​4]);​ ##​ note the dot-prefix .= assignment. ​ this replaces G's contents, not G itself
 +
 +julia> h!(gextern);​ println( gextern ) ## presumably what you wanted
 +[3, 4]
 +
 +```
 +
 +
 +
 +
 +
 +# Best-Practice Speed Recommendations for Functions
 +
 +* Function calls are resolved at run time, not compile time, eliminating the need for function declarations (before function definitions). ​ Ergo, a function definition is the same as a function declaration.
 +
 +* Julia can be extremely fast when run-time takes much longer than compile-time. ​ But even when this is the case, Julia also has oodles of '​gotchas that can slow down execution. ​ In particular, when input or output types can vary, especially at run-time, then the julia compiler can end up having to create swaths of conditional functions, rather than just one function.
 +
 +
 +
 +Here are my recommendations *both* to avoid bugs *and* to speed up programs. ​ [Traceur](https://​github.com/​MikeInnes/​Traceur.jl) can analyze code for many gotchas.
 +
 +
 +- Do not use global variables. ​ (They cannot be typed in 0.6.2, even when defined in modules!) ​ Global constants are ok.
 +
 +- Specify the types of all arguments and the type of function return value.
 +
 +- Use specific primitive-type arguments and return values when generality is not important --- or at least restrict yourself to `Real`, `AbstractFloat`,​ or `Integer`. ​ Again, this is *not* only because it can help the compiler and improve speed and memory use, but because this habit will help you find bugs in your program more quickly. ​  Again and again, it is often advisable to define functions with typed arguments that suit their intended use in *your* programs:
 +
 +**Bad:**
 +
 +```juliarepl
 +julia> b= 1.0;  function g(a) a+b; end;#​function ##​ Bad
 +```
 +
 +**Better:**
 +
 +```juliarepl
 +julia> const b= 1.0;  function g(a::​Real)::​Real;​ a+b; end;#​function ##​ Better
 +```
 +
 +**Best:**
 +
 +```juliarepl
 +julia> const b= 1.0;  function g(a::​Float64)::​Float64;​ a+b; end;#​function ##​ Best
 +```
 +
 +(Typing the return value is useful because it helps *you* catch bugs *inside* your function. ​ The compiler already knowns that a+b will be `Float64`. ​ Actually, 0.6.2 has a [bug](https://​github.com/​JuliaLang/​julia/​issues/​15276) that can occasionally bite, making it even more advisable to type everything.)
 +
 +
 +- "​Program by contract":​ add lots of at the start and end of each function.
 +
 +```juliarepl
 +julia> function f( arg1::​AbstractFloat )
 +     @boundscheck @assert(isfinite(arg1),​ "first argument '​$arg1'​ should be finite."​ )
 + end;#​function##​
 +```
 +
 +- Come to think of it, add many more `@asserts` in the middle of your code, too.
 +
 +- Use `@inbounds` only after you have debugged your program and are sure that bounds checking is no longer needed.
 +
 +- The advice is bendable for really short and simple functions with no risk of misunderstanding: ​ `sqr(x)= x^2` is ok.  (Strong typing on such a simple function would waste the programmer'​s time.)
 +
 +
 +
 +PS: [Traceur is a code analyzer that should catch many speed problems](https://​github.com/​MikeInnes/​Traceur.jl). ​ Unfortunately,​ julia 1.0 (or julialint) do not offer many compiler warnings when meaningful default type variability arises, e.g., due to the use of untyped or undefined variables, global variables, untyped arguments, or untyped return values.
 +
 +
 +
 +
 +
 +
 +## Understanding Passing by Sharing
 +
 +Function arguments are just new variable bindings:
 +
 +```juliarepl
 +julia> f( x::Int )= ( x= 22)
 +f (generic function with 1 method)
 +
 +julia> x= 1
 +1
 +
 +julia> f(x)
 +22
 +
 +julia> x
 +1
 +```
 +
 +This is similar to other interpreted languages (Python, Ruby, Perl, etc.).
 +FIXME QUESTION Is this like the C++ '&'​ argument? ​ i.e., `f( int& x )`, or are there subtle distinctions?​
 +
 +
 +## Warning 1: Reassignment to the Binding
 +
 +```juliarepl
 +julia> f!( x::​Vector{Int} )::​Vector{Int}= ( x= [ -1,-2 ] )
 +f! (generic function with 1 method)
 +
 +julia> y= [ 1, 2 ]
 +2-element Array{Int64,​1}:​
 + 1
 + 2
 +
 +julia> f!( y ) ## change y?  assigned to x and returned
 +2-element Array{Int64,​1}:​
 +-1
 +-2
 +
 +julia> y == [ -1, -2 ] ## so did our f-bang function change y?
 +false
 +
 +julia> y ## no, because x in the f! was assigned to, so it lost its original bindings first
 +2-element Array{Int64,​1}:​
 + 1
 + 2
 +
 +
 +```
 +
 +FIXME IAW: Fix up the above, so that we abuse the switch to have useless assignments that then do *not* propagate as we thought they would; and use this as a substitute to copy and deepcopy.
 +
 +
 +**but** if the contents of x are changed, the binding of x itself does not change, and
 +
 +```juliarepl
 +julia> f!( x::​Vector{Int} )::​Vector{Int}= ( x[1]= -1; x[2]= -2; x )  ## or use the special dot-equal: x.= [ -1, -2 ]
 +f! (generic function with 1 method)
 +
 +julia> y= [ 1, 2 ]
 +2-element Array{Int64,​1}:​
 + 1
 + 2
 +
 +julia> f!( y ) ## change y
 +2-element Array{Int64,​1}:​
 +-1
 +-2
 +
 +julia> y == [ -1, -2 ] ## did our f-bang function change y?
 +true
 +
 +julia> y ## no, because x in the f! was assigned to, so it lost its change
 +2-element Array{Int64,​1}:​
 +-1
 +-2
 +
 +```
 +
 +
 +
 +## Warning 1: Assignment Side Effects
 +
 +A bang! function name should warn the user that the function does surreptitious things to its arguments, but this is just convention. ​ Even a normal function call can do this:
 +
 +```juliarepl
 +julia> ​ add1( x::​Vector{Int} )::​Vector{Int}= ( result= x .+ 1; (length(x) > 2) && ( x[div(length(x),​2)]= -999); ​ result );
 +
 +julia> y= [ 1, 2, 3 ]; z= add1( y ) ## seems to work
 +3-element Array{Int64,​1}:​
 + 2
 + 3
 + 4
 +
 +julia> y ## excuse me?!
 +3-element Array{Int64,​1}:​
 + -999
 +    2
 +    3
 +
 +```
 +
 +
 +
 +
 +## Function Calls with Tuples instead of (Naked) Caller Arguments
 +
 +Recall that a [[arraysintro#​tuples|Tuple]] is a list of values. ​ Tuples can be thought of as the arguments to the function but without the name of the function. ​ In fact, a function `f(a,b)` can accept the call `f(1,2)` or the call `tpl=(1,​2); ​ f(tpl)`.
 +
 +
 +Recall [[arraysintro#​passing_tuples_as_function_arguments|arraysintro]] for the explanation of tuples. ​  ​(Tuples are like lists of values.)
 +
 +```juliarepl
 +julia> function f1(x::Int, y::​AbstractFloat,​ z::Int8=3); print("​$x and $y and $z"); end;##​function##​
 +
 +julia> mytuple= ( 2, 3.4, Int8(5) )
 +(2, 3.4, 5)
 +
 +julia> typeof(mytuple)
 +Tuple{Int64,​Float64,​Int8}
 +
 +julia> f1( mytuple... ) ## note the trailing ...
 +2 and 3.4 and 5
 +```
 +
 +You can also write functions that treat the tuple as a tuple:
 +
 +```juliarepl
 +julia> function f2(x::​Tuple);​ print("​the input is $x"); end;##​function##​
 +
 +julia> mytuple= ( 2, 3.4, Int8(5) );   f2( mytuple )
 +the input is (2, 3.4, 5)
 +
 +```
 +
 +
 +* In `f(x; y=0,​kwargs...)`,​ kwargs passed in should be (key,value) tuples (or be capable of becoming such).
 +
 +See [[#​multiple_return_values_with_tuples|Tuples]] for returning multiple values with tuples.
 +
 +
 +
 +
 +
 +
 +
 +## Anonymous Functions
 +
 +* Anonymous functions are often passed to `map( anonfun, iterator )` and `filter( anonfun, iterator )`.
 +
 +* Anonymous functions cannot specify their return type in the definition. ​ They can however force an unambiguous type in the return, which will still induce the compiler to do the right thing. ​ In any case, arguably, strong typing can be overkill for short anonymous functions, where not only the compiler but also the author can be fairly certain of what is passed into the function and what comes out of it.
 +
 +
 +```juliarepl
 +julia> f= function(a, b; c=2); a+b+c ; end#​function ##​ f is variable that holds a function. ​ the function name is not f!!
 +#7 (generic function with 1 method)
 +
 +julia> f= function(a::​Int,​ b::Int=2; c::Int=3); a+b+c ; end#​function
 +#10 (generic function with 2 methods)
 +
 +julia> f= function(a::​Int,​ b::Int=2; c::Int=2); Int(a+b+c) ; end#​function ​  ## forces unique return type
 +#13 (generic function with 2 methods)
 +
 +julia> g= ( (a,​b)->​a^2+b^2 )
 +#16 (generic function with 1 method)
 +
 +julia> g= (a::​Int64,​b::​Int64;​ c::Int64=4) -> a^2+b^2+c^2 ##​ different way of writing function g
 +#18 (generic function with 1 method)
 +
 +```
 +
 +* Admittedly, in all these examples, the return type can be inferred as `Int` by the compiler. ​ From a compiler perspective,​ forcing type is more useful to force the return type when one of the aspects that go into the computation of the return value is an external global variable. ​ From a user perspective,​ forcing type protects against inadvertent type changes or worse bugs.
 +
 +* With ordinary functions that are named (see first subsection),​ Julia knows that `f` is a function `f()`. ​ With anonymous functions (in this subsection) assigned to the *variable* `f`, Julia must assume that `f` could be a function or could be something else.  This can reduce the compiler'​s ability to optimize.
 +
 +
 +
 +
 +## Varargs
 +
 +See [Varargs functions](https://​docs.julialang.org/​en/​stable/​manual/​functions/​) and [parametrically constrained varargs](https://​docs.julialang.org/​en/​stable/​manual/​methods/#​Methods-1).
 +
 +```juliarepl
 +julia> f(a,x...)= println("​a=$a | x=$x (type of vararg contents=$(typeof(x)))"​)
 +f (generic function with 1 method)
 +
 +julia> f(2,3,4)
 +a=2 | x=(3, 4) (type of vararg contents=Tuple{Int64,​Int64})
 +
 +```
 +
 +* The `x...` formal argument collects all caller arguments into a Tuple.
 +
 +* The `NTuple(N,​T)` feature makes it possible to require the passing of an Tuples with exactly N components of type T.  (Or even at least N units.)
 +
 +
 +
 +### Passing an Array or Tuple to a Varargs function
 +
 +```juliarepl
 +julia> f(x...) = sum( collect(x).^2 )          ## collect creates an array from the Tuple held in variable x
 +f (generic function with 1 method)
 +
 +julia> f(1,2,3)
 +14
 +
 +julia> f( [1,2,3]... ) ## here, the ... notation means disassemble the array into a tuple
 +14
 +```
 +
 +
 +### The Vararg Tuple Type
 +
 +The last parameter of a tuple type can be a `Vararg`, which denotes trailing elements.
 +
 +```juliarepl
 +julia> Mytupletype = Tuple{AbstractString,​Vararg{Int}} ##​ A string, followed by any number of Ints
 +Tuple{AbstractString,​Vararg{Int64,​N} where N}
 +
 +julia> isa( ("​1",​ 1,2,3,4,5) , Mytupletype ) ## Matches
 +true
 +
 +julia> isa( ("​1",​ 1,2,3,4,5 , "​B"​) , Mytupletype ) ## the last item is now a string, not an Int
 +false
 +```
 +
 +* See Section 15.5 of Julia'​s 0.6.2 manual.
 +
 +
 +
 +
 +## Multiple Return Values with Tuples
 +
 +* See also [[#​tuples_as_naked_caller_arguments|Tuples as (Naked) Caller Arguments]] for tuples as call inputs.
 +
 +```juliarepl
 +julia> f(x) = ( x, x^2, x^4, x^8 )             ## returns tuple
 +f (generic function with 1 method)
 +
 +julia> f(2)
 +(2, 4, 16, 256)
 +
 +julia> a,b,c,d= f(2); c
 +16
 +```
 +
 +### Tuples for Multiple and/or Mixed-Type Return Values
 +
 +```juliarepl
 +julia> function g(x::​Int)::​Tuple{Int64,​Int64};​ rv= (1,2); @info(typeof(rv));​ rv end#​function
 +g (generic function with 1 method)
 +
 +julia> g(2)
 +[ Info: Tuple{Int64,​Int64}
 +(1, 2)
 +
 +julia> function h(x::​Int)::​NTuple{2,​Int64};​ rv= (1,2); @info(typeof(rv));​ rv end#​function
 +h (generic function with 1 method)
 +
 +julia> h(2)
 +[ Info: Tuple{Int64,​Int64}
 +(1, 2)
 +```
 +
 +* WARNING Plain instead of curly parentheses as arguments to the `NTuple` make for baffling errors
 +
 +
 +### Ignoring Part of the Return (Tuple)
 +
 +```juliarepl
 +julia> using Random
 +
 +julia> Random.seed!(0);​
 +
 +julia> A= rand(3,3)
 +3×3 Array{Float64,​2}:​
 + ​0.823648 ​ 0.177329 ​ 0.0423017
 + ​0.910357 ​ 0.27888 ​  ​0.0682693
 + ​0.164566 ​ 0.203477 ​ 0.361828
 +
 +julia> using LinearAlgebra
 +
 +julia> (Q,_) = qr(A); ​             ## random orthogonalized matrix only wanted. qr returns two matrices, but _ ignores return
 +
 +julia> Q
 +3×3 LinearAlgebra.QRCompactWYQ{Float64,​Array{Float64,​2}}:​
 + ​-0.664962 ​  ​0.329743 ​  ​0.670146
 + ​-0.734965 ​ -0.129283 ​ -0.665667
 + ​-0.13286 ​  ​-0.935177 ​  ​0.328318
 +
 +```
 +
 +
 +
 +
 +## Function Documentation
 +
 +Strings preceding functions are considered documentation. ​ (Suggestion:​ Use [[strings#​triple-quoting_of_strings|triple double-quotes]].) ​ Because the format is markdown, it allows sectioning the documentation. ​ (Use double backquotes for LaTeX.)
 +
 +```julianoeval
 +"""​
 +    fdocumented(x[,​ y])
 +
 +The one-liner explanation. ​ Four spaces before the function name above.
 +
 +
 +# Arguments
 +
 +- `x::​Vector{Int}` : a vector of integers
 +- `y::​Vector{Int}` : a vector of more integers
 +
 +
 +# Details
 +
 +we do nothing, but we do it well.
 +
 +# Examples
 +
 +\```
 + ​julia>​ fdocumented( [1,2] )
 + [1,2]
 +\```
 +
 +"""​
 +fdocumented(x)=(x)
 +fdocumented(x,​y)=(x,​y)
 +
 +```
 +
 +and now the help browser is available:
 +
 +```text
 +
 +help?> fdocumented
 +search:
 +
 +  fdocumented(x[,​ y])
 +
 +  The one-liner explanation. Four spaces before the function name above.
 +
 +     ​Arguments
 +    ≡≡≡≡≡≡≡≡≡≡≡
 +
 +    •    x::​Vector{Int} : a vector of integers
 +
 +    •    y::​Vector{Int} : a vector of more integers
 +
 +
 +     ​Details
 +    ≡≡≡≡≡≡≡≡≡
 +
 +  we do nothing, but we do it well.
 +
 +     ​Examples
 +    ≡≡≡≡≡≡≡≡≡≡
 +
 +  julia> fdocumented( [1,2] )
 +   [1,2]
 +
 +
 +```
 +
 +### Checking Documentation
 +
 +```sh
 +# make -C doc doctests
 +```
 +
 +
 +
 +
 +
 +## Persistent State (Local Static Variables)
 +
 +### Let Clutch
 +
 +There is an ugly clutch facility (instead of the much nicer `static` C keyword), which works because functions declared inside a let block are global:
 +
 +```juliarepl
 +julia> let state= 0;
 +   ​global counter;
 +   ​function counter() state+=1; end#​function counter
 +   state
 +   ​end#​let##​
 +0
 +
 +julia> counter(); counter(); counter()
 +3
 +
 +```
 +
 +### Channels
 +
 +An often feasible and more elegant alternative to a state (ahem, let) variable is a [[controlflow#​channels|channel]]:​
 +
 +```juliarepl
 +julia> function counter(c::​Channel)::​Int;​
 +                ## don't worry---the following will not fill the channel, but return after each put!
 +                for state=1:​typemax(Int);​ put!(c, state); end#for
 + end;#​function##​
 +
 +julia> nn= Channel(counter);​
 +
 +julia> take!(nn); take!(nn); take!(nn)
 +3
 +
 +julia> close(nn)
 +```
 +
 +
 +
 +## Global Variables (and Changing Globals Inside Functions)
 +
 +Global variables cannot be typed. ​ Every program can see global variables, but they are by default read-only. ​ This can be changed with the `global` keyword, but should be strictly avoided.
 +
 +
 +```juliarepl
 +julia> gx= 1;  function f() global gx= 2; end;##​function
 +
 +julia> f(); gx
 +2
 +```
 +
 +* Nested functions have write access to local variables one scope up (unless the scope-up is global).
 +
 +
 +
 +## Introspection (Function Name, Content, etc.)
 +
 +FIXME mention @macro facility for knowing more about a function.
 +
 +
 +```juliarepl
 +julia> function a() end#​function
 +a (generic function with 1 method)
 +
 +julia> x= a
 +a (generic function with 1 method)
 +
 +julia> Symbol(x)
 +:a
 +```
 +
 +* Unfortunately,​ typing the name of the function does not print its source code, as it does in R.  In fact, the function source seems to be discarded (irretrievable in 0.6.2).
 +
 +
 +
 +## Chaining Function Calls (Syntactic Sugar)
 +
 +```juliarepl
 +julia> inv( sum( ([1:5;]).^2 ) )   ## watch the trailing semi-colon for array
 +0.01818181818181818
 +```
 +
 +Already mentioned in [[controlflow#​function_chaining|control flow]], Julia offers "​unix-like-piping"​ way to highlight sequencing for sequential (nested) function calls.
 +
 +```juliarepl
 +julia> [1:5;] |> x->x.^2 |> sum |> inv           ## x->x.^2 is an anonymous function
 +0.01818181818181818
 +```
 +
 +This becomes less convenient (requiring an anonymous function) when chained functions have more than one argument.
 +
 +```juliarepl
 +julia> gnep(needle,​heystack) = filter( x->​occursin(x,​ needle), heystack );
 +
 +julia> ss= [ "​ab1",​ "​ab2",​ "​cd1",​ "​ab3",​ "​ef5"​ ];
 +
 +julia> ( gnep("​AB",​ uppercase.(ss)) )  ==  ( uppercase.(ss) |>  x->gnep( "​AB",​ x) )
 +true
 +
 +```
 +
 +
 +## Wrapping a Function Call Around Another Function ("​Hooking Into a Function"​)
 +
 +```juliarepl
 +julia> function wrapfun( funin::​Function )::Function
 +     gallcount= 0; gfun= funin;
 +            function fundispatcher( x... );  gallcount+= 1; gfun( x... );  end#​function##​ add 1 and return the original function
 +            function fundispatcher()::​Int; ​ @info("​Number of Function Calls: $gallcount"​);​ gallcount; ​ end#​function##​ give info
 +     return fundispatcher ##​ return the wrapper function
 + end;#​function##​
 +
 +julia> mysqr= wrapfun( x->x^2 ); ## x^2 is an anonymous function that squares its contents
 +
 +julia> @assert( sum( map( x->​mysqr(x),​ 1:20 ) ) == 2870, "sum is wrong" ) ## run the function 20 times
 +
 +julia> mysqr( ) ## call the info function
 +[ Info: Number of Function Calls: 20
 +20
 +
 +```
 +
 +
 +
 +
 +## Argument Dependencies in Default Arguments
 +
 +Arguments are parsed left to right. ​ Later arguments can use values of earlier arguments.
 +
 +```juliarepl
 +julia> b= 9999;
 +
 +julia> lrfun( a, b=1, c=b )= println("​$a $b $c"​);​ ##​ ok
 +
 +julia> lrfun( 12 )
 +12 1 1
 +
 +julia> rlfun( a, c=b, b=1)= println("​$a $b $c" ); ## left-to-right means b is the global one before arg3
 +
 +julia> rlfun( 12 )
 +12 1 9999
 +```
 +
 +
 +
 +
 +## Passing Parameter State Arguments to Functions
 +
 +A common problem is that a function is optimized over x, but for different parameters xparm. ​ Alas, the generic optimization function wants a function only of x.  One good design pattern is to encapsulate the optiization caller:
 +
 +```julianoeval
 +julia> minimizeuserfun( fun::​Function,​ xparm )= minimize( x -> fun(x, parm) );
 +
 +julia> nsqr(x,a)= (x-a)^2 ;
 +
 +julia> minimizeuserfun( nsqr, 25 )
 +25
 +
 +```
 +
 +See also [[Global Variables / Performance / Daa Passing|https://​discourse.julialang.org/​t/​global-variables-performance-data-passing/​19069/​6]].
 +
 +
 +
 +## Writing Macros
 +
 +FIXME Show more macro examples
 +
 +Macros (like C Preprocessor `#define` functions) can be useful to do meta programming. ​ Avoid them unless you need meta programming---although the following examples just illustrate them.
 +
 +```juliarepl
 +julia> mutable struct Point; x::Float64; f::Float64; end#​struct;##​
 +
 +julia> module M
 + macro testme(newp)
 +     esc(:​($newp.f <= 0.0 ? string($newp,​ " below"​) : string($newp,​ " above"​)))
 + end#macro#
 + end#​module##​
 +Main.M
 +
 +julia> macroexpand(M,​ quote @testme p end) ## how to show what it expands into
 +quote
 +    #= none:1 =#
 +    if p.f <= 0.0
 +        string(p, " below"​)
 +    else
 +        string(p, " above"​)
 +    end
 +end
 +
 +julia> @M.testme(Point( 1.0, 2.0 ))
 +"​Point(1.0,​ 2.0) above"
 +
 +```
 +
 +### Macro Hygiene
 +
 +FIXME Explain Macro Hygiene with an example
 +
 +
 +
 +
 +
 +
 +# Backmatter
 +
 +## Useful Packages on Julia Repository
 +
 +## Notes
 +
 +* In Computer Science, sometimes the arguments (parameters) in the function definition are named "​formal parameters,"​ and the arguments (parameters) in the caller are named "​actual parameters." ​ I wish the jargon was clearer.
 +
 +* Unfortunately,​ there are no elegant C-type static variables inside functions.
 +
 +* Unfortunately,​ it is not possible to turn on a compiler warning whenever meaningful default type variability arises, e.g., due to global variables or untyped arguments. ​ Use [Traceur](https://​github.com/​MikeInnes/​Traceur.jl).
 +
 +* Julia does not forward-scan .jl source files to collect (forward references for) functions declared later, even in batch mode where a pre-scan would be a cheap fallback. ​ Thus, programmers typically define their least-important deepest utility functions first. ​ perl has a more programmer-friendly fallback here that scans forward, while admitting imperfection (it won't always work). ​ Forward-scanning would not be desirable forced behavior, but desirable with a pragma option.
 +
 +* There are all sorts of ambiguities that can arise with abstract containers. ​ Be specific (and/or read 15.9 of the Julia 0.6.2 manual).
 +
 +* Fancier dynamic meta-programming documentation creation is possible. ​ (Chapter 20, Julia 0.6.2)
 +
 +
 +
 +
  
funother.txt · Last modified: 2019/01/01 17:34 (external edit)