Julia Language
функции
Поиск…
Синтаксис
- f (n) = ...
- функция f (n) ... end
- н :: Тип
- x -> ...
- f (n) do ... end
замечания
Помимо общих функций (которые являются наиболее распространенными), есть также встроенные функции. Такие функции включают в себя is
, isa
, typeof
, throw
, и аналогичные функции. Встроенные функции обычно реализуются в C вместо Julia, поэтому они не могут быть специализированы по типам аргументов для отправки.
Квадратное число
Это самый простой синтаксис для определения функции:
square(n) = n * n
Чтобы вызвать функцию, используйте круглые скобки (без пробелов между ними):
julia> square(10)
100
Функции - это объекты в Julia, и мы можем показать их в REPL как с любыми другими объектами:
julia> square
square (generic function with 1 method)
По умолчанию все функции Julia являются родовыми (иначе известными как полиморфные ). Наша square
функция работает так же хорошо, как и значения с плавающей запятой:
julia> square(2.5)
6.25
... или даже матрицы :
julia> square([2 4
2 1])
2×2 Array{Int64,2}:
12 12
6 9
Рекурсивные функции
Простая рекурсия
Используя рекурсию и тернарный условный оператор , мы можем создать альтернативную реализацию встроенной factorial
функции:
myfactorial(n) = n == 0 ? 1 : n * myfactorial(n - 1)
Использование:
julia> myfactorial(10)
3628800
Работа с деревьями
Рекурсивные функции часто наиболее полезны для структур данных, особенно для древовидных структур данных. Поскольку выражения в Julia являются древовидными структурами, рекурсия может быть весьма полезна для метапрограммирования . Например, нижняя функция собирает набор всех головок, используемых в выражении.
heads(ex::Expr) = reduce(∪, Set((ex.head,)), (heads(a) for a in ex.args))
heads(::Any) = Set{Symbol}()
Мы можем проверить, что наша функция работает по назначению:
julia> heads(:(7 + 4x > 1 > A[0]))
Set(Symbol[:comparison,:ref,:call])
Эта функция является компактной и использует множество более совершенных методов, таких как reduce
функции более высокого порядка , тип Set
данных и выражения генератора.
Введение в диспетчеризацию
Мы можем использовать синтаксис ::
для отправки по типу аргумента.
describe(n::Integer) = "integer $n"
describe(n::AbstractFloat) = "floating point $n"
Использование:
julia> describe(10)
"integer 10"
julia> describe(1.0)
"floating point 1.0"
В отличие от многих языков, которые обычно предоставляют либо статичную множественную отправку, либо динамическую разовую отправку, Julia имеет полную динамическую множественную отправку. То есть функции могут быть специализированы для более чем одного аргумента. Это полезно при определении специализированных методов для операций над определенными типами и методов резервного копирования для других типов.
describe(n::Integer, m::Integer) = "integers n=$n and m=$m"
describe(n, m::Integer) = "only m=$m is an integer"
describe(n::Integer, m) = "only n=$n is an integer"
Использование:
julia> describe(10, 'x')
"only n=10 is an integer"
julia> describe('x', 10)
"only m=10 is an integer"
julia> describe(10, 10)
"integers n=10 and m=10"
Необязательные аргументы
Julia позволяет выполнять необязательные аргументы. За кулисами это реализуется как еще один частный случай множественной отправки. Например, давайте решим популярную проблему Fizz Buzz . По умолчанию мы будем делать это для чисел в диапазоне 1:10
, но при необходимости мы допустим другое значение. Мы также разрешим использовать разные фразы для Fizz
или Buzz
.
function fizzbuzz(xs=1:10, fizz="Fizz", buzz="Buzz")
for i in xs
if i % 15 == 0
println(fizz, buzz)
elseif i % 3 == 0
println(fizz)
elseif i % 5 == 0
println(buzz)
else
println(i)
end
end
end
Если мы fizzbuzz
в REPL, в нем сказано, что существует четыре метода. Для каждой комбинации аргументов был создан один метод.
julia> fizzbuzz
fizzbuzz (generic function with 4 methods)
julia> methods(fizzbuzz)
# 4 methods for generic function "fizzbuzz":
fizzbuzz() at REPL[96]:2
fizzbuzz(xs) at REPL[96]:2
fizzbuzz(xs, fizz) at REPL[96]:2
fizzbuzz(xs, fizz, buzz) at REPL[96]:2
Мы можем проверить, что наши значения по умолчанию используются, когда параметры не предоставляются:
julia> fizzbuzz()
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
но что дополнительные параметры принимаются и соблюдаются, если мы их предоставим:
julia> fizzbuzz(5:8, "fuzz", "bizz")
bizz
fuzz
7
8
Параметрическая отправка
Часто бывает, что функция должна отправлять параметрические типы, такие как Vector{T}
или Dict{K,V}
, но параметры типа не фиксированы. Этот случай может быть рассмотрен с помощью параметрической отправки:
julia> foo{T<:Number}(xs::Vector{T}) = @show xs .+ 1
foo (generic function with 1 method)
julia> foo(xs::Vector) = @show xs
foo (generic function with 2 methods)
julia> foo([1, 2, 3])
xs .+ 1 = [2,3,4]
3-element Array{Int64,1}:
2
3
4
julia> foo([1.0, 2.0, 3.0])
xs .+ 1 = [2.0,3.0,4.0]
3-element Array{Float64,1}:
2.0
3.0
4.0
julia> foo(["x", "y", "z"])
xs = String["x","y","z"]
3-element Array{String,1}:
"x"
"y"
"z"
Может возникнуть соблазн просто написать xs::Vector{Number}
. Но это работает только для объектов, тип которых явно Vector{Number}
:
julia> isa(Number[1, 2], Vector{Number})
true
julia> isa(Int[1, 2], Vector{Number})
false
Это связано с параметрической инвариантностью : объект Int[1, 2]
не является Vector{Number}
, поскольку он может содержать только Int
s, тогда как ожидается, что Vector{Number}
сможет содержать любые виды чисел.
Создание общего кода
Диспетчер - невероятно мощная функция, но часто лучше писать общий код, который работает для всех типов, вместо специализированного кода для каждого типа. Написание общего кода позволяет избежать дублирования кода.
Например, вот код для вычисления суммы квадратов вектора целых чисел:
function sumsq(v::Vector{Int})
s = 0
for x in v
s += x ^ 2
end
s
end
Но этот код работает только для вектора Int
s. Он не будет работать на UnitRange
:
julia> sumsq(1:10)
ERROR: MethodError: no method matching sumsq(::UnitRange{Int64})
Closest candidates are:
sumsq(::Array{Int64,1}) at REPL[8]:2
Он не будет работать с Vector{Float64}
:
julia> sumsq([1.0, 2.0])
ERROR: MethodError: no method matching sumsq(::Array{Float64,1})
Closest candidates are:
sumsq(::Array{Int64,1}) at REPL[8]:2
Лучшим способом написать эту функцию sumsq
должно быть
function sumsq(v::AbstractVector)
s = zero(eltype(v))
for x in v
s += x ^ 2
end
s
end
Это будет работать в двух случаях, перечисленных выше. Но есть некоторые коллекции, которые мы могли бы захотеть суммировать квадраты, которые вообще не являются векторами, в любом смысле. Например,
julia> sumsq(take(countfrom(1), 100))
ERROR: MethodError: no method matching sumsq(::Base.Take{Base.Count{Int64}})
Closest candidates are:
sumsq(::Array{Int64,1}) at REPL[8]:2
sumsq(::AbstractArray{T,1}) at REPL[11]:2
показывает, что мы не можем суммировать квадраты ленивого итерации .
Еще более общая реализация - это просто
function sumsq(v)
s = zero(eltype(v))
for x in v
s += x ^ 2
end
s
end
Что работает во всех случаях:
julia> sumsq(take(countfrom(1), 100))
338350
Это самый идиоматический код Юлии и может обрабатывать всевозможные ситуации. В некоторых других языках удаление аннотаций типа может повлиять на производительность, но это не относится к Julia; для производительности важна стабильность только типа .
Императивный факторный
Синтаксис длинной формы доступен для определения многострочных функций. Это может быть полезно, когда мы используем императивные структуры, такие как циклы. Возвращается выражение в хвостовом положении. Например, функция ниже использует цикл for
для вычисления факториала некоторого целого n
:
function myfactorial(n)
fact = one(n)
for m in 1:n
fact *= m
end
fact
end
Использование:
julia> myfactorial(10)
3628800
В более длинных функциях обычно используется оператор return
. Оператор return
не нужен в положении хвоста, но он по-прежнему используется для ясности. Например, другим способом написания вышеуказанной функции было бы
function myfactorial(n)
fact = one(n)
for m in 1:n
fact *= m
end
return fact
end
который идентичен поведению функции выше.
Анонимные функции
Синтаксис стрелок
Анонимные функции могут быть созданы с помощью синтаксиса ->
. Это полезно для передачи функций более высоким функциям , таким как функция map
. Функция ниже вычисляет квадрат каждого числа в массиве A
squareall(A) = map(x -> x ^ 2, A)
Пример использования этой функции:
julia> squareall(1:10)
10-element Array{Int64,1}:
1
4
9
16
25
36
49
64
81
100
Многострочный синтаксис
Многолинейные анонимные функции могут быть созданы с использованием синтаксиса function
. Например, следующий пример вычисляет факториалы первых n
чисел, но использует анонимную функцию вместо встроенного factorial
.
julia> map(function (n)
product = one(n)
for i in 1:n
product *= i
end
product
end, 1:10)
10-element Array{Int64,1}:
1
2
6
24
120
720
5040
40320
362880
3628800
Блочный синтаксис
Поскольку так часто передается анонимная функция в качестве первого аргумента функции, существует синтаксис do
block. Синтаксис
map(A) do x
x ^ 2
end
эквивалентно
map(x -> x ^ 2, A)
но первое может быть более понятным во многих ситуациях, особенно если в анонимной функции выполняется много вычислений. do
блок синтаксиса особенно полезно для ввода имени файла и вывода по причинам управления ресурсами.