Поиск…


Синтаксис

  • 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 блок синтаксиса особенно полезно для ввода имени файла и вывода по причинам управления ресурсами.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow