As was pointed out by Steven G. Johnson, higher-order functions are great part of Julia language (topic for another big discusion) and often much more suitable to solve problems that metaprogramming tools, which are for many method of choice.
I want to learn more about them and how I should use them in Julia, but I new to this topic and I don’t know where to start.
As a side note, searching of both Documentation and YouTube about metaprogramming in Julia give you few answers immediately, when my searching about higher-order functions in documentation seems to found mostly pages with words like “higher dimensions” (I didn’t check all results). And one YT video from 2017. This is unfortunate, since as Tamas Papp points out, metaprogramming is often “first trick in the bag”, when should be the last, left for advanced users.
I don’t know of a document that lists all there is to know about the use of functions as values.
The main points I see are:
A function can be used like a regular value: assigned to a variable, passed as argument, etc.
Such function values can be created dynamically as anonymous functions, e.g. f = x->2x, or more verbose: f = function(x) 2x end. The function can refer to (capture) variables available in the scope where it’s defined.
Passing functions as values is most common when working with map, filter and similar functions, as in many languages.
Julia has some less common uses of functions as values including
Composition operations that return a new function:
julia> f = cos ∘ sin ∘ (x->2x)
#62 (generic function with 1 method)
julia> f(pi/2)
1.0
Negation operator returning a new function:
julia> notletter = !isletter
#64 (generic function with 1 method)
julia> notletter('2')
true
Partial applications returning new functions:
julia> under10 = <(10)
(::Base.Fix2{typeof(<),Int64}) (generic function with 1 method)
julia> under10(3)
true
julia> hasG = contains('G')
(::Base.Fix2{typeof(contains),Char}) (generic function with 1 method)
julia> hasG("hello")
false
A more practical example would be filter(contains('G'), myStringArray).
All functions have a type, which can be used for dispatch. As with any value, you can get the type with typeof:
julia> f = x->2x
#1 (generic function with 1 method)
julia> g(func::typeof(f)) = "That's f!"
g (generic function with 1 method)
julia> g(f)
"That's f!"
julia> g(sin)
ERROR: MethodError: no method matching g(::typeof(sin))
An interesting example of dispatch on functions is : which is actually a function of type Colon. You can use it like this:
julia> h(::Colon) = "called with :"
h (generic function with 1 method)
julia> h(:)
"called with :"
This is how syntax like a[:] works:
julia> a = [1,2,3];
julia> @less a[:]
shows that the relevant getindex method is defined as: