Функции
В Julia функция — это объект, который сопоставляет кортеж значений аргументов со значением возврата. Функции Julia не являются чистыми математическими функциями в том смысле, что функции могут изменять и зависеть от глобального состояния программы. Базовый синтаксис определения функций в Julia:
julia> function f(x,y)
x + y
end
f (generic function with 1 method)
Эта функция принимает два аргумента x и y и возвращает значение последнего вычисленного выражения, которое равно x + y.
Существует второй, более краткий синтаксис для определения функции в Julia. Традиционный синтаксис объявления функции, показанный выше, эквивалентен следующей компактной форме «присваивания»:
julia> f(x,y) = x + y f (generic function with 1 method)
В форме присваивания тело функции должно быть единственным выражением, хотя оно может быть составным выражением (см. Составные выражения). Короткие, простые определения функций часто используются в Julia. Короткий синтаксис функции поэтому довольно идиоматичен, значительно сокращая написание кода и визуальный шум.
Функция вызывается с использованием традиционного синтаксиса с круглыми скобками:
julia> f(2,3) 5
Без круглых скобок выражение f относится к объекту функции и может передаваться как любое значение:
julia> g = f; julia> g(2,3) 5
Как и для переменных, для имён функций также можно использовать Юникод:
julia> ∑(x,y) = x + y ∑ (generic function with 1 method) julia> ∑(2, 3) 5
Поведение передачи аргументов
Аргументы функций Julia следуют соглашению, иногда называемому «передачей по ссылке», что означает, что значения не копируются при передаче функций. Аргументы функций сами действуют как новые связи переменных (новые места, которые могут ссылаться на значения), но значения, на которые они ссылаются, идентичны переданным значениям. Изменения в изменяемых значениях (таких как Array ) внутри функции будут видны вызывающей стороне. Это то же поведение, что и в Scheme, большинстве диалектов Lisp, Python, Ruby и Perl, а также в других динамических языках.
Ключевое слово return
Значение, возвращаемое функцией, — это значение последнего вычисленного выражения, которое по умолчанию является последним выражением в теле определения функции. В примере функции f, из предыдущего раздела это значение выражения x + y . В качестве альтернативы, как и во многих других языках, ключевое слово return заставляет функцию немедленно вернуть значение, предоставляя выражение, значение которого возвращается:
function g(x,y)
return x * y
x + y
end
Поскольку определения функций можно вводить в интерактивные сессии, легко сравнить эти определения:
julia> f(x,y) = x + y
f (generic function with 1 method)
julia> function g(x,y)
return x * y
x + y
end
g (generic function with 1 method)
julia> f(2,3)
5
julia> g(2,3)
6
Конечно, в чисто линейном теле функции, например, g, использование return бесполезно, так как выражение x + y никогда не вычисляется, и мы могли бы просто сделать x * y последним выражением в функции и опустить ключевое слово return. Однако в сочетании с другими управляющими конструкциями ключевое слово return действительно полезно. Например, здесь приведена функция, вычисляющая длину гипотенузы прямоугольного треугольника с длинами катетов x и y, избегая переполнения:
julia> function hypot(x,y)
x = abs(x)
y = abs(y)
if x > y
r = y/x
return x*sqrt(1+r*r)
end
if y == 0
return zero(x)
end
r = x/y
return y*sqrt(1+r*r)
end
hypot (generic function with 1 method)
julia> hypot(3, 4)
5.0
Из этой функции возможны три точки возврата, возвращающие значения трёх разных выражений, в зависимости от значений x и y . Ключевое слово return в последней строке можно опустить, так как это последнее выражение.
Тип возвращаемого значения
Тип возвращаемого значения можно указать в объявлении функции с помощью оператора :: . Это преобразует значение возврата к указанному типу.
julia> function g(x, y)::Int8
return x * y
end;
julia> typeof(g(1, 2))
Int8
Эта функция всегда вернёт Int8 , независимо от типов x и y . Дополнительную информацию о типах возвращаемых значений см. в разделе Объявления типов.
Возвращение ничего
Для функций, которым не нужно возвращать значение (функций, используемых только для каких-то побочных эффектов), соглашением в Julia является возвращение значения nothing:
function printx(x)
println("x = $x")
return nothing
end
Это соглашение в том смысле, что nothing не является ключевым словом Julia, а всего лишь одиночный объект типа Nothing . Кроме того, вы можете заметить, что пример функции printx выше искусственный, потому что println уже возвращает nothing, поэтому строка return избыточна.
Существует две возможные сокращённые формы выражения return nothing . С одной стороны, ключевое слово return неявным образом возвращает nothing , поэтому его можно использовать самостоятельно. С другой стороны, поскольку функции неявно возвращают значение последнего вычисленного выражения, nothing можно использовать самостоятельно, если это последнее выражение. Предпочтение выражения return nothing по сравнению с return или nothing — вопрос стиля программирования.
Операторы — это функции
В Julia большинство операторов — это просто функции с поддержкой специального синтаксиса. (Исключение составляют операторы со специальной семантикой вычисления, такие как && и || . Эти операторы не могут быть функциями, так как короткое замыкание требует, чтобы их операнды не вычислялись до вычисления оператора.) Соответственно, вы также можете применять их, используя списки аргументов в скобках, как и любую другую функцию:
julia> 1 + 2 + 3 6 julia> +(1,2,3) 6
Инфиксная форма точно эквивалентна форме вызова функции — на самом деле первая форма разбирается для внутреннего вызова функции. Это также означает, что вы можете назначать и передавать операторы, такие как + и *, как и другие значения функций:
julia> f = +; julia> f(1,2,3) 6
Однако функция под именем f не поддерживает инфиксную запись.
Операторы со специальными именами
Некоторые специальные выражения соответствуют вызовам функций с неочевидными именами. Это:
| Выражение | Вызовы |
|---|---|
[A B C ...] |
hcat |
[A; B; C; ...] |
vcat |
[A B; C D; ...] |
hvcat |
A' |
adjoint |
A[i] |
getindex |
A[i] = x |
setindex! |
A.n |
getproperty |
A.n = x |
setproperty! |
Анонимные функции
Функции в Julia — объекты первого класса: они могут быть назначены переменным, вызываться с использованием стандартного синтаксиса вызова функций из переменной, которой они были назначены. Они могут использоваться в качестве аргументов и возвращаться в качестве значений. Их также можно создавать анонимно, без присвоения имени, используя любой из этих синтаксисов:
julia> x -> x^2 + 2x - 1
#1 (generic function with 1 method)
julia> function (x)
x^2 + 2x - 1
end
#3 (generic function with 1 method)
Это создаёт функцию, принимающую один аргумент x и возвращающую значение многочлена x^2 + 2x - 1 в этой точке. Обратите внимание, что результатом является общая функция, но с сгенерированным компилятором именем на основе последовательного нумерации.
Основное использование анонимных функций — передача их в функции, которые принимают другие функции в качестве аргументов. Классическим примером является map, которая применяет функцию к каждому значению массива и возвращает новый массив, содержащий полученные значения:
julia> map(round, [1.2,3.5,1.7])
3-element Array{Float64,1}:
1.0
4.0
2.0
Это хорошо, если уже существует именованная функция, которая выполняет преобразование, для передачи её в качестве первого аргумента к map. Однако часто готовой именованной функции нет. В этих ситуациях конструкция анонимной функции позволяет легко создать объект функции одноразового использования без необходимости в имени:
julia> map(x -> x^2 + 2x - 1, [1,3,-1])
3-element Array{Int64,1}:
2
14
-2
Анонимная функция, принимающая несколько аргументов, может быть записана с использованием синтаксиса (x,y,z)->2x+y-z. Анонимная функция без аргументов записывается как ()->3. Идея функции без аргументов может показаться странной, но она полезна для «отложенного» вычисления. В этом случае блок кода помещается в функцию без аргументов, которая позже вызывается, вызвав её как f.
В качестве примера рассмотрим этот вызов get:
get(dict, key) do
# default value calculated here
time()
end
Приведённый выше код эквивалентен вызову get с анонимной функцией, содержащей код, заключённый между do и end , как показано ниже:
get(()->time(), dict, key)
Вызов time откладывается за счёт помещения его в анонимную функцию без аргументов, которая вызывается только тогда, когда запрашиваемый ключ отсутствует в dict.
Кортежи
Julia имеет встроенную структуру данных, называемую кортежем, которая тесно связана с аргументами и возвращаемыми значениями функций. Кортеж — это контейнер фиксированной длины, который может содержать любые значения, но не может быть изменён (он неизменяемый). Кортежи строятся с помощью запятых и круглых скобок и могут быть обработаны с помощью индексирования:
julia> (1, 1+1) (1, 2) julia> (1,) (1,) julia> x = (0.0, "hello", 6*7) (0.0, "hello", 42) julia> x[2] "hello"
Обратите внимание, что кортеж длины 1 должен быть написан с запятой, (1,), так как (1) просто будет скобочным значением. () представляет собой пустой (длиной 0) кортеж.
Именованные кортежи
Компоненты кортежей могут быть необязательно именованными, в таком случае строится именованный кортеж:
julia> x = (a=1, b=1+1) (a = 1, b = 2) julia> x.a 1
Именованные кортежи очень похожи на кортежи, за исключением того, что к полям можно дополнительно обращаться по имени, используя синтаксис точки (x.a).
Возвращение нескольких значений
В Julia для имитации возвращения нескольких значений возвращается кортеж значений. Однако кортежи можно создавать и деструктурировать без необходимости использования скобок, тем самым создавая иллюзию возвращения нескольких значений вместо одного значения кортежа. Например, следующая функция возвращает пару значений:
julia> function foo(a,b)
a+b, a*b
end
foo (generic function with 1 method)
Если вы вызовете её в интерактивной сессии без присвоения возвращаемого значения, вы увидите возвращённый кортеж:
julia> foo(2,3) (5, 6)
Однако, типичное использование таких пар возвращаемых значений включает извлечение каждого значения в переменную. Julia поддерживает простую «деструктуризацию» кортежей, которая облегчает это:
julia> x, y = foo(2,3) (5, 6) julia> x 5 julia> y 6
Вы также можете вернуть несколько значений с явным использованием ключевого слова return:
function foo(a,b)
return a+b, a*b
end
Это имеет тот же эффект, что и предыдущее определение foo.
Деструктуризация аргументов
Функциональность деструктуризации также может быть использована в аргументе функции. Если имя аргумента функции записано как кортеж (например, (x, y)) вместо просто символа, то для вас будет вставлено присваивание (x, y) = argument:
julia> minmax(x, y) = (y < x) ? (y, x) : (x, y) julia> range((min, max)) = max - min julia> range(minmax(10, 2)) 8
Обратите внимание на дополнительные скобки в определении range. Без них range была бы функцией с двумя аргументами, и этот пример не сработал бы.
Функции с произвольным числом аргументов
Часто удобно иметь возможность создавать функции, принимающие произвольное количество аргументов. Такие функции традиционно известны как функции с произвольным числом аргументов (varargs), что сокращенно означает «переменное число аргументов». Вы можете определить функцию varargs, добавив эллипсис после последнего аргумента:
julia> bar(a,b,x...) = (a,b,x) bar (generic function with 1 method)
Переменные a и b связаны с первыми двумя значениями аргументов, как обычно, а переменная x связана с итерируемым набором нулевых или более значений, переданных в bar после её первых двух аргументов:
julia> bar(1,2) (1, 2, ()) julia> bar(1,2,3) (1, 2, (3,)) julia> bar(1, 2, 3, 4) (1, 2, (3, 4)) julia> bar(1,2,3,4,5,6) (1, 2, (3, 4, 5, 6))
Во всех этих случаях x связана с кортежем последующих значений, переданных в bar.
Можно ограничить количество значений, передаваемых как аргумент; это будет обсуждаться позже в параметрически ограниченных методах varargs.
С другой стороны, часто удобно «распаковывать» значения, содержащиеся в итерируемом наборе, в вызов функции в качестве отдельных аргументов. Для этого также используется ... но в вызове функции вместо этого:
julia> x = (3, 4) (3, 4) julia> bar(1,2,x...) (1, 2, (3, 4))
В этом случае кортеж значений вставляется в вызов varargs точно там, где находятся аргументы с переменным числом. Однако это необязательно:
julia> x = (2, 3, 4) (2, 3, 4) julia> bar(1,x...) (1, 2, (3, 4)) julia> x = (1, 2, 3, 4) (1, 2, 3, 4) julia> bar(x...) (1, 2, (3, 4))
Кроме того, итерируемый объект, распакованный в вызов функции, не обязательно должен быть кортежем:
julia> x = [3,4]
2-element Array{Int64,1}:
3
4
julia> bar(1,2,x...)
(1, 2, (3, 4))
julia> x = [1,2,3,4]
4-element Array{Int64,1}:
1
2
3
4
julia> bar(x...)
(1, 2, (3, 4))
Также функция, в которую распаковываются аргументы, не обязательно должна быть функцией varargs (хотя часто так и бывает):
julia> baz(a,b) = a + b;
julia> args = [1,2]
2-element Array{Int64,1}:
1
2
julia> baz(args...)
3
julia> args = [1,2,3]
3-element Array{Int64,1}:
1
2
3
julia> baz(args...)
ERROR: MethodError: no method matching baz(::Int64, ::Int64, ::Int64)
Closest candidates are:
baz(::Any, ::Any) at none:1
Как вы можете видеть, если в распакованном контейнере неверное количество элементов, то вызов функции завершится с ошибкой, так же как и если будут переданы лишние аргументы явно.
Необязательные аргументы
Во многих случаях аргументы функций имеют разумные значения по умолчанию, и поэтому их не нужно явно передавать в каждом вызове. Например, функция Date(y, [m, d]) из модуля Dates строит тип Date для заданного года y, месяца m и дня d. Однако аргументы m и d необязательны, и их значение по умолчанию равно 1.
function Date(y::Int64, m::Int64=1, d::Int64=1)
err = validargs(Date, y, m, d)
err === nothing || throw(err)
return Date(UTD(totaldays(y, m, d)))
end
Обратите внимание, что это определение вызывает другой метод функции Date, который принимает один аргумент типа UTInstant{Day}.
С этим определением функция может быть вызвана с одним, двумя или тремя аргументами, и 1 автоматически передаётся, когда какой-либо из аргументов не указан:
julia> using Dates julia> Date(2000, 12, 12) 2000-12-12 julia> Date(2000, 12) 2000-12-01 julia> Date(2000) 2000-01-01
Необязательные аргументы на самом деле являются просто удобным синтаксисом для написания нескольких определений методов с разным количеством аргументов (см. Примечание об необязательных и именованных аргументах). Это можно проверить для нашего примера функции Date вызвав функцию methods.
Именованные аргументы
Некоторые функции требуют большого количества аргументов или имеют большое количество вариантов. Запоминать, как вызывать такие функции, может быть сложно. Именованные аргументы могут облегчить использование и расширение этих сложных интерфейсов, позволяя идентифицировать аргументы по имени, а не только по позиции.
Например, рассмотрим функцию plot, которая строит линию. Эта функция может иметь много опций для управления стилем линии, шириной, цветом и т. д. Если она принимает именованные аргументы, возможный вызов может выглядеть так plot(x, y, width=2), где мы выбрали указать только ширину линии. Обратите внимание, что это выполняет две задачи. Вызов легче читать, так как мы можем пометить аргумент его значением. Также становится возможным передать любое подмножество большого числа аргументов в любом порядке.
Функции с именованными аргументами определяются с использованием точки с запятой в сигнатуре:
function plot(x, y; style="solid", width=1, color="black")
###
end
При вызове функции точка с запятой необязательна: можно вызвать plot(x, y, width=2) или plot(x, y; width=2), но первый стиль более распространён. Явная точка с запятой требуется только для передачи varargs или вычисленных именованных аргументов, как описано ниже.
Значения по умолчанию для именованных аргументов вычисляются только при необходимости (когда соответствующий именованный аргумент не передан), и в порядке следования слева направо. Поэтому выражения по умолчанию могут ссылаться на предыдущие именованные аргументы.
Типы именованных аргументов могут быть сделаны явными следующим образом:
function f(;x::Int=1)
###
end
Дополнительные именованные аргументы могут быть собраны с использованием ..., как и в функциях varargs:
function f(x; y=0, kwargs...)
###
end
Внутри f, kwargs будет итератором по ключу-значению именованного кортежа. Именованные кортежи (а также словари с ключами Symbol) могут быть переданы в качестве именованных аргументов с использованием точки с запятой в вызове, например f(x, z=1; kwargs...).
Если именованному аргументу не назначено значение по умолчанию в определении метода, то он является обязательным: исключение UndefKeywordError будет брошено, если вызывающий метод не назначит ему значение:
function f(x; y)
###
end
f(3, y=5) # ok, y is assigned
f(3) # throws UndefKeywordError(:y)
Также можно передать выражения key => value после точки с запятой. Например, plot(x, y; :width => 2) эквивалентно plot(x, y, width=2) . Это полезно в ситуациях, когда имя именованного аргумента вычисляется во время выполнения.
Характер именованных аргументов позволяет указывать один и тот же аргумент более одного раза. Например, в вызове plot(x, y; options..., width=2) возможно, что структура options также содержит значение для width. В таком случае, значение правого наиболее часто встречающегося случая имеет приоритет; в этом примере width гарантированно будет иметь значение 2 . Однако явное указание одного и того же именованного аргумента несколько раз, например plot(x, y, width=2, width=3), запрещено и приводит к синтаксической ошибке.
Область вычисления значений по умолчанию
При вычислении выражений по умолчанию для необязательных и именованных аргументов в области видимости находятся только предыдущие аргументы. Например, при данном определении:
function f(x, a=b, b=1)
###
end
переменная b в a=b относится к переменной b во внешней области видимости, а не к последующему аргументу b.
Синтаксис блока do для аргументов функции
Передача функций в качестве аргументов другим функциям является мощной техникой, но синтаксис для этого не всегда удобен. Такие вызовы особенно неудобны для написания, когда аргумент функции требует нескольких строк. Например, рассмотрим вызов map для функции с несколькими случаями:
map(x->begin
if x < 0 && iseven(x)
return 0
elseif x == 0
return 1
else
return x
end
end,
[A, B, C])
Julia предоставляет зарезервированное слово do для более чёткого переписывания этого кода:
map([A, B, C]) do x
if x < 0 && iseven(x)
return 0
elseif x == 0
return 1
else
return x
end
end
Синтаксис do x создаёт анонимную функцию с аргументом x и передаёт её в качестве первого аргумента функции map. Аналогично, do a,b создаст анонимную функцию с двумя аргументами, а просто do укажет, что то, что следует за ним, является анонимной функцией формы () -> ....
Способ инициализации этих аргументов зависит от «внешней» функции; здесь map последовательно установит x на A, B, C, вызвав анонимную функцию для каждого, как это происходит в синтаксисе map(func, [A, B, C]).
Этот синтаксис облегчает использование функций для эффективного расширения языка, так как вызовы выглядят как обычные блоки кода. Существует множество возможных применений, весьма отличных от map, таких как управление состоянием системы. Например, есть версия open, которая выполняет код, гарантирующий, что открытый файл будет в конечном итоге закрыт:
open("outfile", "w") do io
write(io, data)
end
Это достигается следующим определением:
function open(f::Function, args...)
io = open(args...)
try
f(io)
finally
close(io)
end
end
Здесь open сначала открывает файл для записи, а затем передает полученный поток вывода анонимной функции, которую вы определили в блоке do ... end. После выхода вашей функции open гарантирует, что поток будет должным образом закрыт, независимо от того, завершилась ли ваша функция нормально или произошла ошибка. (Конструкции try/finally будут описаны в Потоке управления.)
Синтаксис блоков do помогает проверить документацию или реализацию, чтобы понять, как инициализируются аргументы пользовательской функции.
Блок do, как и любая другая внутренняя функция, может «захватывать» переменные из своего окружающего пространства. Например, переменная data в приведенном выше примере open...do захвачена из внешней области видимости. Захваченные переменные могут создавать проблемы с производительностью, как описано в подсказках по производительности.
Композиция и цепочки функций
Функции в Julia можно комбинировать, выполняя композицию или цепочку (наследование).
Композиция функций — это когда вы объединяете функции и применяете полученную композицию к аргументам. Вы используете оператор композиции функций (∘) для композиции функций, поэтому (f ∘ g)(args...) эквивалентно f(g(args...)).
Вы можете ввести оператор композиции в REPL и в соответствующих редакторах, используя \circ<tab>.
Например, функции sqrt и + можно составить следующим образом:
julia> (sqrt ∘ +)(3, 6) 3.0
Это складывает числа сначала, а затем находит квадратный корень результата.
Следующий пример комбинирует три функции и применяет результат к массиву строк:
julia> map(first ∘ reverse ∘ uppercase, split("you can compose functions like this"))
6-element Array{Char,1}:
'U'
'N'
'E'
'S'
'E'
'S'
Цепочка функций (иногда называемая «трубой» или «использованием трубы» для передачи данных последующей функции) — это когда вы применяете функцию к результату предыдущей функции:
julia> 1:10 |> sum |> sqrt 7.416198487095663
Здесь, общее значение, произведённое sum, передаётся функции sqrt. Эквивалентная композиция будет:
julia> (sqrt ∘ sum)(1:10) 7.416198487095663
Оператор трубы также может использоваться с широковещательной рассылкой, как .|>, чтобы обеспечить полезное сочетание синтаксиса цепочки/трубы и синтаксиса векторизации точек (описанного ниже).
julia> ["a", "list", "of", "strings"] .|> [uppercase, reverse, titlecase, length]
4-element Array{Any,1}:
"A"
"tsil"
"Of"
7
Синтаксис точки для векторизации функций
В технических языках вычислений часто встречаются «векторизованные» версии функций, которые просто применяют заданную функцию f(x) к каждому элементу массива A для получения нового массива через f(A). Такой синтаксис удобен для обработки данных, но в других языках векторизация также часто необходима для производительности: если циклы медленные, «векторизованная» версия функции может вызывать быстрый код библиотеки, написанный на низкоуровневом языке. В Julia векторизованные функции не являются необходимыми для производительности, и на самом деле часто выгодно писать свои собственные циклы (см. Подсказки по производительности), но они по-прежнему могут быть удобными. Поэтому любую функцию Julia f можно применить поэлементно к любому массиву (или другой коллекции) с синтаксисом f.(A). Например, sin можно применить ко всем элементам вектора A следующим образом:
julia> A = [1.0, 2.0, 3.0]
3-element Array{Float64,1}:
1.0
2.0
3.0
julia> sin.(A)
3-element Array{Float64,1}:
0.8414709848078965
0.9092974268256817
0.1411200080598672
Конечно, вы можете опустить точку, если вы напишите специализированный «векторный» метод f, например, через f(A::AbstractArray) = map(f, A), и это так же эффективно, как f.(A). Но этот подход требует, чтобы вы заранее определили, какие функции вы хотите векторизировать.
В более общем случае, f.(args...) на самом деле эквивалентно broadcast(f, args...), что позволяет вам работать с несколькими массивами (даже разной формы) или смесью массивов и скаляров (см. Широковещательную рассылку). Например, если у вас f(x,y) = 3x + 4y, то f.(pi,A) вернёт новый массив, состоящий из f(pi,a) для каждого a в A, а f.(vector1,vector2) вернёт новый вектор, состоящий из f(vector1[i],vector2[i]) для каждого индекса i (вызывая исключение, если векторы имеют разную длину).
julia> f(x,y) = 3x + 4y;
julia> A = [1.0, 2.0, 3.0];
julia> B = [4.0, 5.0, 6.0];
julia> f.(pi, A)
3-element Array{Float64,1}:
13.42477796076938
17.42477796076938
21.42477796076938
julia> f.(A, B)
3-element Array{Float64,1}:
19.0
26.0
33.0
Более того, вложенные вызовы f.(args...) сливаются в один broadcast цикл. Например, sin.(cos.(X)) эквивалентно broadcast(x -> sin(cos(x)), X), аналогично [sin(cos(x)) for x in X]: существует только один цикл по X, и для результата выделяется один массив. [В отличие от sin(cos(X)) в типичном «векторизованном» языке, сначала будет выделен один временный массив для tmp=cos(X), а затем sin(tmp) будет вычисляться в отдельном цикле, выделяя второй массив.] Эта слияние циклов — не оптимизация компилятора, которая может или не может произойти, это синтаксическая гарантия всякий раз, когда встречаются вложенные вызовы f.(args...). Технически слияние останавливается, как только встречается вызов функции «без точки»; например, в sin.(sort(cos.(X))) циклы sin и cos не могут быть объединены из-за промежуточной функции sort.
Наконец, максимальная эффективность обычно достигается, когда выходной массив векторизованной операции предварительно выделен, чтобы повторные вызовы не выделяли новые массивы для результатов снова и снова (см. Предварительное выделение выходов). Удобный синтаксис для этого — X .= ..., который эквивалентен broadcast!(identity, X, ...) за исключением того, что, как и выше, цикл broadcast! сливается с любыми вложенными вызовами «точки». Например, X .= sin.(Y) эквивалентно broadcast!(sin, X, Y), перезаписывая X с sin.(Y) на месте. Если левая часть — выражение индексирования массива, например X[begin+1:end] .= sin.(Y), то оно переводится в broadcast! на view, например, broadcast!(sin, view(X, firstindex(X)+1:lastindex(X)), Y), так что левая часть обновляется на месте.
Поскольку добавление точек к множеству операций и вызовов функций в выражении может быть утомительным и приводить к трудночитаемому коду, макрос @. предоставляется для преобразования всех вызовов функций, операций и присваиваний в выражении в «точку» версию.
julia> Y = [1.0, 2.0, 3.0, 4.0];
julia> X = similar(Y); # pre-allocate output array
julia> @. X = sin(cos(Y)) # equivalent to X .= sin.(cos.(Y))
4-element Array{Float64,1}:
0.5143952585235492
-0.4042391538522658
-0.8360218615377305
-0.6080830096407656
Бинарные (или унарные) операторы, такие как .+, обрабатываются с помощью того же механизма: они эквивалентны вызовам broadcast и сливаются с другими вложенными вызовами «точки». X .+= Y и т. д. эквивалентно X .= X .+ Y и приводит к слиянию присваивания на месте; см. также операторы точек.
Вы также можете комбинировать операции точек с цепочкой функций, используя |>, как показано в этом примере:
julia> [1:5;] .|> [x->x^2, inv, x->2*x, -, isodd]
5-element Array{Real,1}:
1
0.5
6
-4
true
Дополнительные материалы для чтения
Следует упомянуть, что это далеко не полное описание определения функций. Julia имеет сложную систему типов и позволяет множественную диспатчирование по типам аргументов. Ни один из приведенных здесь примеров не содержит никаких аннотаций типов для своих аргументов, что означает, что они применимы ко всем типам аргументов. Система типов описана в Типах, а определение функции в терминах методов, выбранных множественным диспетчированием по типам аргументов во время выполнения, описано в Методах.
© 2009–2020 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.4.2/manual/functions/