Функции
В Julia, функция — это объект, который сопоставляет кортеж значений аргументов со значением возврата. Функции Julia не являются чистыми математическими функциями в том смысле, что функции могут изменять и быть затронутыми глобальным состоянием программы. Базовый синтаксис определения функций в Julia:
julia> function f(x,y)
x + y
end
f (generic function with 1 method)
Существует второй, более краткий синтаксис для определения функции в 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
Как и с переменными, для имён функций также можно использовать Unicode:
julia> ∑(x,y) = x + y ∑ (generic function with 1 method) julia> ∑(2, 3) 5
Поведение передачи аргументов
Аргументы функций Julia следуют соглашению, иногда называемому «передачей по ссылке», что означает, что значения не копируются при их передаче в функции. Аргументы функций сами выступают в качестве новых связываний переменных (новых местоположений, которые могут ссылаться на значения), но значения, на которые они ссылаются, идентичны передаваемым значениям. Изменения в изменяемых значениях (например, Arrays), выполненные внутри функции, будут видны вызывающей стороне. Это то же поведение, что и в Scheme, большинстве диалектов Lisp, Python, Ruby и Perl, а также в других динамических языках.
Ключевое слово return
Значение, возвращаемое функцией, является значением последнего вычисленного выражения, которое по умолчанию является последним выражением в теле определения функции. В примере функции f, из предыдущего раздела это значение выражения x + y. Как и в C и большинстве других императивных или функциональных языков, ключевое слово 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 большинство операторов — это просто функции с поддержкой специального синтаксиса. (Исключение составляют операторы со специальной семантикой вычисления, такие как && и ||. Эти операторы не могут быть функциями, поскольку краткое замыкание требует, чтобы их операнды не вычислялись до вычисления оператора.) Поэтому их также можно применять с использованием списков аргументов в скобках, как и любые другие функции:
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.
Кортежи
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[2:end] .= sin.(Y), то она переводится в broadcast! на view, например, broadcast!(sin, view(X, 2: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–2019 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.2.0/manual/functions/