diff --git a/concepts.wip/functions/.meta/config.json b/concepts.wip/functions/.meta/config.json new file mode 100644 index 00000000..4c04f2d8 --- /dev/null +++ b/concepts.wip/functions/.meta/config.json @@ -0,0 +1,7 @@ +{ + "authors": [ + "colinleach" + ], + "contributors": [], + "blurb": "Functions are central to Julia, so will be the focus of several concepts. This one covers function arguments, splat and slurp." +} diff --git a/concepts.wip/functions/about.md b/concepts.wip/functions/about.md new file mode 100644 index 00000000..9ef54d85 --- /dev/null +++ b/concepts.wip/functions/about.md @@ -0,0 +1,208 @@ +# About + +The [`Basics`][basics] Concept introduced two ways to define a [function][functions]. + +Most generally, the multiline form: + +```julia +function muladd(x, y, z) + x * y + z +end +``` + +The "assignment", or "single-line" form for short definitions: +```julia +muladd(x, y, z) = x * y + z +``` + +In a third and even shorter form, a short, single-use function can be created without a name: + +```julia-repl +julia> map(x -> 2x, 1:3) +4-element Vector{Int64}: + 2 + 4 + 6 + +julia> map((x, y) -> x * y, 1:3, 4:6) +3-element Vector{Int64}: + 4 + 10 + 18 +``` + +In this case, `x -> 2x` is an ["anonymous function"][anonymous-function]. +This is equivalent to what some other languages call a "lambda function". + +Note that multiple arguments need parentheses, as in `(x, y) -> x * y`. + +Anonymous functions are common in Julia code, especially when combined with [higher-order functions][HOF] such as `map()` and `filter()`. + +## Function arguments + +So far in the syllabus, we have only looked at functions which have a precise number of arguments, and require function calls to supply all of them, in the correct order. +This would be limiting and inconvenient, so there are several other options. + +### Optional arguments + +Like many languages, Julia allows function definitions to supply default values for individual arguments. + +Function call can then either supply a value for that argument, or omit it and rely on the default. + +```julia-repl +julia> f(x, y=10) = x * y +f (generic function with 2 methods) + +julia> f(2, 3) +6 + +julia> f(2) +20 +``` + +All arguments _without_ defaults must come before any arguments _with_ defaults, meaning that `f(x=2, y)` would be invalid. + +### Keyword arguments + +All the examples so far use `positional arguments`, where values supplied in a function call must match the order of the corresponding arguments in the function definition. + +Like many languages, Julia also allows `keyword arguments`. +Function calls must specify the argument name, but multiple keyword arguments can then be specified in any order. + +A distinctive feature of Julia is that the keyword arguments (if any) in the function definition must be preceded by a semicolon `;` to separate them from any positional arguments. +A function call can use either `;` or `,` between the last positional argument and the first keyword argument. + +```julia-repl +julia> b(x; y) = x + y +b (generic function with 1 method) + +julia> b(2, y=3) +5 + +# keyword is required when calling +julia> b(2, 3) +ERROR: MethodError: no method matching b(::Int64, ::Int64) +The function `b` exists, but no method is defined for this combination of argument types. +``` + +Default values can optionally be specified, exactly as for positional arguments. + +It is common to end up with syntax like `myarg=myarg` within a function call, when a variable with the same name as the parameter was pre-calculated. +A shorthand syntax is allowed in this situation: + +```julia-repl +julia> width = 4.0 +4.0 + +julia> height = √ width +2.0 + +julia> area(; width, height) = width * height +area (generic function with 1 method) + +# repetition +julia> area(; width=width, height=height) +8.0 + +# shorthand form +julia> area(; width, height) +8.0 +``` + +### Splat and slurp + +These are the standard names for a useful aspect of Julia syntax, in case you wondered. +Both refer to the `...` operator. + +#### Splat + +Splatting is used in function _calls_, to expand collections into individual values required by the function. + +This may be easier to demonstrate than to explain: + +```julia-repl +julia> fxyz(x, y, z) = x * y * z +fxyz (generic function with 1 method) + +julia> xyz = [2, 3, 4] +3-element Vector{Int64}: + 2 + 3 + 4 + +# Using the vector directly in a function call is invalid +julia> fxyz(xyz) +ERROR: MethodError: no method matching fxyz(::Vector{Int64}) +The function `fxyz` exists, but no method is defined for this combination of argument types. + +# splatting converts the vector to 3 numbers, used as positional argumants +julia> fxyz(xyz...) +24 +``` + +Keyword arguments can also be supplied by splatting, using a [`named tuple`][named-tuple] or a [`Dict`][dict] (though not a [`struct`][struct], which is not iterable). + +```julia-repl +# function with 3 keyword arguments +julia> fabc(; a, b, c) = a + b + c +fabc (generic function with 1 method) + +# named tuple +julia> abc_nt = (a=2, b=3, c=4) +(a = 2, b = 3, c = 4) + +# there are no positional arguments, so need to use ; before kw argument +julia> fabc(;abc_nt...) +9 + +# Dict +julia> abc_dict = Dict(:a=>2, :b=>3, :c=>4) +Dict{Symbol, Int64} with 3 entries: + :a => 2 + :b => 3 + :c => 4 + +julia> fabc(;abc_dict...) +9 +``` + +#### Slurp + +Slurping is used in the function _definition_, to pack an arbitrary number of individual values into a collection. + +```julia-repl +julia> f_more(i, j, more...) = i + j + sum(more) +f_more (generic function with 1 method) + +julia> f_more(1, 3, 5, 7, 9, 11) +36 +``` + +The name of the slurped argument (in this case `more`) is not significant. +The type of this variable is chosen by the compiler, but for positional arguments is likely to be `tuple` or something similar. + +Keyword arguments can also be slurped, giving a `Dict` (or similar). + +```julia-repl +julia> f_kwslurp(x, y; switches...) = :mult in keys(switches) ? x * y : x + y +f_kwslurp (generic function with 1 method) + +julia> f_kwslurp(5, 6; mult=true) +30 + +julia> f_kwslurp(5, 6) +11 +``` + +Any keyword arguments can be used in the call. +It is for the function definition to decide which keywords to respond to and which to ignore. + + +[basics]: https://exercism.org/tracks/julia/concepts/basics +[anonymous-function]: https://docs.julialang.org/en/v1/manual/functions/#man-anonymous-functions +[HOF]: https://en.wikipedia.org/wiki/Higher-order_function +[named-tuple]: https://exercism.org/tracks/julia/concepts/sets +[dict]: https://exercism.org/tracks/julia/concepts/pairs-and-dicts +[splat]: https://docs.julialang.org/en/v1/manual/functions/#Varargs-Functions +[functions]: https://docs.julialang.org/en/v1/manual/functions/ +[struct]: https://docs.julialang.org/en/v1/base/base/#struct diff --git a/concepts.wip/functions/introduction.md b/concepts.wip/functions/introduction.md new file mode 100644 index 00000000..e10b99d0 --- /dev/null +++ b/concepts.wip/functions/introduction.md @@ -0,0 +1 @@ +# Introduction diff --git a/concepts.wip/functions/links.json b/concepts.wip/functions/links.json new file mode 100644 index 00000000..3a92190a --- /dev/null +++ b/concepts.wip/functions/links.json @@ -0,0 +1,6 @@ +[ + { + "url": "[functions]: https://docs.julialang.org/en/v1/manual/functions/", + "description": "Manual section on functions." + } +]