Spec-Zone.ru › Julia 1.3

Функции

В 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 следуют соглашению, иногда называемому «передачей по ссылке», что означает, что значения не копируются при передаче в функции. Сами аргументы функции действуют как новые привязки переменных (новые места, которые могут ссылаться на значения), но значения, на которые они ссылаются, идентичны переданным значениям. Изменения в изменяемых значениях (таких как Array), выполненные внутри функции, будут видны вызывающей стороне. Это то же поведение, что и в Scheme, большинстве Lisпов, 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.

Например, рассмотрите этот вызов 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[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–2020 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.3.1/manual/functions/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API