AnyMath 文档

功能

在Julia中,函数是将参数值元组映射到返回值的对象。 Julia函数不是纯粹的数学函数,因为它们可以改变并受到程序全局状态的影响。 在Julia中定义函数的基本语法是:

julia> function f(x, y)
           x + y
       end
f (generic function with 1 method)

此函数接受两个参数 xy 并返回计算的最后一个表达式的值,即 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

与变量一样,Unicode也可以用于函数名称:

julia> ∑(x, y) = x + y
∑ (generic function with 1 method)

julia> ∑(2, 3)
5

参数传递行为

Julia函数参数遵循有时称为"传递共享"的约定,这意味着值在传递给函数时不会被复制。 函数参数本身充当新变量_bindings_(可以引用值的新"名称"),很像 作业 argument_name=argument_value,以便它们引用的对象与传递的值相同。 对可变值的修改(例如 阵列s)在一个函数中生成的函数将对调用者可见。 (这与Scheme,大多数Lisps,Python,Ruby和Perl以及其他动态语言中的行为相同。)

例如,在函数

function f(x, y)
    x[1] = 42    # mutates x
    y = 7 + y    # new binding for y, no mutation
    return y
end

声明 x[1]=42 mutates_对象 x,因此此更改_will_在调用者为此参数传递的数组中可见。 另一方面,分配 y=7+y 更改_binding("名称") y 引用新值 7+y,而不是突变所指的_original_对象 y,因此_not_更改调用者传递的相应参数。 这可以看出,如果我们调用 f(x,y):

julia> a = [4, 5, 6]
3-element Vector{Int64}:
 4
 5
 6

julia> b = 3
3

julia> f(a, b) # returns 7 + b == 10
10

julia> a  # a[1] is changed to 42 by f
3-element Vector{Int64}:
 42
  5
  6

julia> b  # not changed
3

作为Julia中的一个常见约定(不是语法要求),这样的函数将 通常被命名 f!(x,y)而不是 f(x,y),作为调用站点的视觉提醒,至少有一个参数(通常是第一个)正在突变。

参数之间的共享内存当一个变异的参数与另一个参数共享内存时,一个变异函数的行为可能是意外的,这种情况被称为别名(例如当一个是另一个的视图时)。 除非函数docstring显式指示别名产生预期结果,否则调用方有责任确保此类输入上的正确行为。

参数类型声明

您可以通过追加声明函数参数的类型 ::类型名称 到参数名称,像往常一样 类型声明。 例如,以下函数计算https://en.wikipedia.org/wiki/Fibonacci_number[斐波那契数]递归:

fib(n::Integer) = n ≤ 2 ? one(n) : fib(n-1) + fib(n-2)

::整数 规范意味着它只有在 n摘要 整数 类型。

参数类型声明*通常对性能没有影响*:无论声明了什么参数类型(如果有的话),Julia都会为调用者传递的实际参数类型编译函数的专用版本。 例如,调用 fib(1) 会触发编译专门版本的 n.纤维,纤维 专为 Int型 参数,然后在以下情况下重复使用 fib(7)fib(15) 被召唤。 (当参数类型声明可以触发其他编译器特化时,有一些罕见的例外情况;请参阅: 注意Julia何时避免专业。 在Julia中声明参数类型的最常见原因是:

**派遣:*如在 方法,对于不同的参数类型,您可以有不同版本的函数("方法"),在这种情况下,参数类型用于确定为哪个参数调用哪个实现。 例如,您可能实现完全不同的算法 fib(x::Number)=。.. 这对任何人都有效 电话号码 使用类型https://en.wikipedia.org/wiki/Fibonacci_number#Binet%27s_formula[Binet的公式]将其扩展为非整数值。 **正确性:*如果您的函数只为某些参数类型返回正确的结果,类型声明可能很有用。 例如,如果我们省略了参数类型并写 fib(n)=n≤2? 一(n):fib(n-1)+fib(n-2),则 fib(1.5) 会默默地给我们无厘头的答案 1.0. **清晰:*类型声明可以作为有关预期参数的文档形式。

但是,过度限制参数类型*是一个*常见的错误,这可能会不必要地限制函数的适用性,并防止在您没有预料到的情况下重新使用它。 例如, fib(n::整数) 上面的函数同样适用于 Int型 参数(机器整数)和 比金特 任意精度整数(参见 BigFloats和BigInts),这是特别有用的,因为斐波那契数呈指数级快速增长,并会迅速溢出任何固定精度类型,如 Int型 (见 溢出行为)。 如果我们将我们的函数声明为 fib(n::Int),但是,应用到 比金特 会无缘无故地被阻止的。 一般情况下,您应该使用最通用的抽象类型作为参数,如果有疑问,请省略参数类型*。 如果需要,您可以随时在以后添加参数类型规范,并且您不会通过省略它们来牺牲性能或功能。

回来吧 关键字

函数返回的值是计算的最后一个表达式的值,默认情况下,该表达式是函数定义正文中的最后一个表达式。 在示例函数中, f,从上一节这是表达式的值 x+y. 作为替代方案,与许多其他语言一样, 回来吧 关键字导致函数立即返回,提供一个返回其值的表达式:

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,使用 回来吧 是没有意义的,因为表达式 x+y 永远不会被评估,我们可以简单地做出 x*y 函数中的最后一个表达式,并省略 回来吧. 然而,与其他控制流结合使用, 回来吧 是真正有用的。 例如,这里有一个函数,它计算长边直角三角形的斜边长度 xy,避免溢出:

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 x
           end
           r = x/y
           return y*sqrt(1 + r*r)
       end
hypot (generic function with 1 method)

julia> hypot(3, 4)
5.0

此函数有三个可能的返回点,返回三个不同表达式的值,具体取决于 xy. 该 回来吧 最后一行可以省略,因为它是最后一个表达式。

返回类型

返回类型可以在函数声明中指定,使用 :: 接线员。 这将返回值转换为指定的类型。

julia> function g(x, y)::Int8
           return x * y
       end;

julia> typeof(g(1, 2))
Int8

此函数将始终返回 Int8 无论是哪种类型的 xy. 见 类型声明有关返回类型的更多信息。

返回类型声明在Julia中*很少使用*:一般来说,您应该编写"类型稳定"函数,其中Julia的编译器可以自动推断返回类型。 有关更多信息,请参阅 性能提示#man-performance-tips【性能提示】章节。

什么也不回来

对于不需要返回值的函数(仅用于某些副作用的函数),Julia约定是返回值 什么都没有:

function printx(x)
    println("x = $x")
    return nothing
end

这是一个意义上的_convention_ 什么都没有 不是Julia关键字,而只是类型的单例对象 什么都没有. 此外,你可能会注意到 打印x 上面的例子是做作的,因为 打印,打印 已经返回 什么都没有,使 回来吧 行冗余。

有两种可能的缩短形式 什么也不退货 表达。 一方面, 回来吧 关键字隐式返回 什么都没有,所以可以单独使用。 另一方面,由于函数隐式返回其最后计算的表达式, 什么都没有 当它是最后一个表达式时,可以单独使用。 表达式的首选项 什么也不退货 相对于 回来吧什么都没有 单独是编码风格的问题。

运算符是函数

在Julia中,大多数运算符只是支持特殊语法的函数。 (例外是具有特殊评估语义的运算符,如 &&||. 这些运算符不能是函数,因为 短路评估要求在评估运算符之前不评估它们的操作数。)因此,您也可以使用带括号的参数列表来应用它们,就像您对任何其他函数一样:

julia> 1 + 2 + 3
6

julia> +(1, 2, 3)
6

中缀形式完全等同于函数应用程序形式-实际上前者被解析以在内部产生函数调用。 这也意味着您可以分配和传递运算符,例如 +*就像你对其他函数值一样:

julia> f = +;

julia> f(1, 2, 3)
6

以名字命名 f 但是,该函数不支持中缀表示法。

具有特殊名称的运算符

一些特殊表达式对应于对具有非显而易见名称的函数的调用。 这些是:

表达方式 电话

[A B C...]

hcat,hcat

[A;B;C;。..]

vcat的

[A B;C D;。..]

[医hvcat]

[A;B;;C;D;;。..]

[医hvncat]

一个'

[医]

A[i]

getindex,getindex

A[i]=x

setindex!

A.n

获得财产

A.n=x

setproperty!

请注意,表达式类似于 [A;B;;C;D;;。..] 但连续两次以上 ; 也对应于 [医]hvncat 电话。

匿名函数

Julia中的函数是https://en.wikipedia.org/wiki/First-class_citizen[第一类对象]:它们可以被分配给变量,并使用标准函数调用语法从它们被分配到的变量中调用。 它们可以用作参数,也可以作为值返回。 它们也可以匿名创建,而不被命名,使用这些语法中的任何一个:

julia> x -> x^2 + 2x - 1
#2 (generic function with 1 method)

julia> function (x)
           x^2 + 2x - 1
       end
#5 (generic function with 1 method)

每个语句创建一个函数,接受一个参数 x 并返回多项式的值 x^2+2x-1 在那个值。 请注意,结果是一个泛型函数,但具有基于连续编号的编译器生成的名称。

匿名函数的主要用途是将它们传递给将其他函数作为参数的函数。 一个典型的例子是 地图,它将一个函数应用于数组的每个值,并返回一个包含结果值的新数组:

julia> map(round, [1.2, 3.5, 1.7])
3-element Vector{Float64}:
 1.0
 4.0
 2.0

如果一个影响转换的命名函数已经存在作为第一个参数传递给 地图. 但是,通常不存在即用型命名函数。 在这些情况下,匿名函数构造允许轻松创建一次性函数对象,而无需名称:

julia> map(x -> x^2 + 2x - 1, [1, 3, -1])
3-element Vector{Int64}:
  2
 14
 -2

接受多个参数的匿名函数可以使用以下语法编写 (x,y,z)->2x+y-z.

匿名函数的参数类型声明与命名函数一样工作,例如 x::整数->2x. 不能指定匿名函数的返回类型。

零参数匿名函数可以写成 ()->2+2. 没有参数的函数的想法可能看起来很奇怪,但在无法(或不应该)预计算结果的情况下很有用。 例如,Julia有一个零参数 时间返回当前时间(以秒为单位)的函数 秒=()->round(Int,time()) 是一个匿名函数,它返回这次四舍五入到分配给变量的最接近的整数 秒数. 每次这个匿名函数被调用为 秒() 将计算并返回当前时间。

元组

Julia有一个称为_tuple_的内置数据结构,它与函数参数和返回值密切相关。 元组是一个固定长度的容器,可以保存任何值,但不能被修改(它是_immutable_)。 元组是用逗号和圆括号构造的,可以通过索引访问:

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)元组。

命名元组

元组的组件可以选择命名,在这种情况下构造_named tuple_:

julia> x = (a=2, b=1+2)
(a = 2, b = 3)

julia> x[1]
2

julia> x.a
2

命名元组的字段可以使用点语法按名称访问(x.a)除了常规索引语法(x[1]x[:a]).

解构赋值和多个返回值

逗号分隔的变量列表(可选地用括号包装)可以出现在赋值的左侧:右侧的值是_destructured_,方法是依次迭代并分配给每个变量:

julia> (a, b, c) = 1:3
1:3

julia> b
2

右边的值应该是一个迭代器(参见 迭代接口)至少只要左边的变量数量(迭代器的任何多余元素都被忽略)。

这可以用于通过返回元组或其他可迭代值从函数返回多个值。 例如,以下函数返回两个值:

julia> function foo(a, b)
           a+b, a*b
       end
foo (generic function with 1 method)

如果您在交互式会话中调用它而没有在任何地方分配返回值,则会看到返回的元组:

julia> foo(2, 3)
(5, 6)

解构赋值将每个值提取到一个变量中:

julia> x, y = foo(2, 3)
(5, 6)

julia> x
5

julia> y
6

另一个常见的用途是交换变量:

julia> y, x = x, y
(5, 6)

julia> x
6

julia> y
5

如果只需要迭代器的元素的子集,则常见的约定是将忽略的元素分配给仅由下划线组成的变量 _ (这是一个无效的变量名,请参阅 允许的变量名):

julia> _, _, _, d = 1:10
1:10

julia> d
4

其他有效的左侧表达式可以用作赋值列表的元素,它将调用 setindex!setproperty!,或者递归地解构迭代器的单个元素:

julia> X = zeros(3);

julia> X[1], (a, b) = (1, (2, 3))
(1, (2, 3))

julia> X
3-element Vector{Float64}:
 1.0
 0.0
 0.0

julia> a
2

朱莉娅>b
3
兼容性

朱莉娅1.6 ... 带赋值需要Julia1.6

如果赋值列表中的最后一个符号后缀为 ... (称为_slurping_),那么它将被分配右侧迭代器的剩余元素的集合或惰性迭代器:

julia> a, b... = "hello"
"hello"

julia> a
'h': ASCII/Unicode U+0068 (category Ll: Letter, lowercase)

julia> b
"ello"

julia> a, b... = Iterators.map(abs2, 1:4)
Base.Generator{UnitRange{Int64}, typeof(abs2)}(abs2, 1:4)

julia> a
1

julia> b
Base.Iterators.Rest{Base.Generator{UnitRange{Int64}, typeof(abs2)}, Int64}(Base.Generator{UnitRange{Int64}, typeof(abs2)}(abs2, 1:4), 1)

基地。休息有关特定迭代器的精确处理和定制的详细信息。

兼容性

朱莉娅1.9 ... 在作业的非最终位置需要Julia1.9

在分配中Slurping也可以发生在任何其他位置。 然而,与啜饮一个集合的结尾相反,这将永远是渴望的。

julia> a, b..., c = 1:5
1:5

julia> a
1

julia> b
3-element Vector{Int64}:
 2
 3
 4

julia> c
5

朱莉娅>前...,tail="嗨!"
"嗨!"

朱莉娅>前
"嗨"

朱莉娅>尾巴
'!':ASCII/Unicode U+0021(类别Po:标点符号,其他)

这是在功能方面实现的 基地。分裂,分裂.

请注意,对于变分函数定义,仍然只允许在最终位置进行slurping。 这不适用于 单参数解构虽然,因为这不影响方法调度:

julia> f(x..., y) = x
ERROR: syntax: invalid "..." on non-final argument
Stacktrace:
[...]

julia> f((x..., y)) = x
f (generic function with 1 method)

julia> f((1, 2, 3))
(1, 2)

财产破坏

也可以使用属性名称对赋值的右侧进行析构,而不是基于迭代进行析构。 这遵循NamedTuples的语法,并通过使用相同的名称为左侧的每个变量分配赋值右侧的属性来工作 获得财产:

julia> (; b, a) = (a=1, b=2, c=3)
(a = 1, b = 2, c = 3)

julia> a
1

julia> b
2

论证解构

解构功能也可以在函数参数中使用。 如果函数参数名称被写成元组(例如 (x,y))而不仅仅是一个符号,然后是一个赋值 (x,y)=参数 将为您插入:

julia> minmax(x, y) = (y < x) ? (y, x) : (x, y)

julia>gap((min,max))=max-min

朱莉娅>间隙(minmax(10,2))
8

注意在定义中额外的一组括号 差距. 没有那些, 差距 将是一个双参数函数,这个例子将不起作用。

同样,属性解构也可以用于函数参数:

julia> foo((; x, y)) = x + y
foo (generic function with 1 method)

julia> foo((x=1, y=2))
3

julia> struct A
           x
           y
       end

julia> foo(A(3, 4))
7

对于匿名函数,解构单个参数需要额外的逗号:

julia> map(((x, y),) -> x + y, [(1, 2), (3, 4)])
2-element Vector{Int64}:
 3
 7

Varargs函数

使用任意数量的参数编写函数通常很方便。 这种函数传统上被称为"varargs"函数,它是"可变参数数量"的缩写。 您可以通过在最后一个位置参数后面加上省略号来定义varargs函数:

julia> bar(a, b, x...) = (a, b, x)
bar (generic function with 1 method)

变量 ab 像往常一样绑定到前两个参数值,并且变量 x 绑定到传递到的零个或多个值的可迭代集合 酒吧 在它的前两个论点之后:

julia> bar(1, 2)
(1, 2, ())

julia> bar(1, 2, 3)
(1, 2, (3,))

朱莉娅>酒吧(1,2,3,4)
(1, 2, (3, 4))

朱莉娅>酒吧(1, 2, 3, 4, 5, 6)
(1, 2, (3, 4, 5, 6))

在所有这些情况下, x 绑定到传递到的尾随值的元组 酒吧.

可以约束作为变量参数传递的值的数量;这将在后面讨论 参数约束的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 Vector{Int64}:
 3
 4

julia> bar(1, 2, x...)
(1, 2, (3, 4))

julia> x = [1, 2, 3, 4]
4-element Vector{Int64}:
 1
 2
 3
 4

朱莉娅>酒吧(x。..)
(1, 2, (3, 4))

此外,参数被分割成的函数不必是varargs函数(尽管它通常是):

julia> baz(a, b) = a + b;

julia> args = [1, 2]
2-element Vector{Int64}:
 1
 2

julia> baz(args...)
3

julia> args = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> baz(args...)
ERROR: MethodError: no method matching baz(::Int64, ::Int64, ::Int64)
The function `baz` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  baz(::Any, ::Any)
   @ Main none:1

Stacktrace:
[...]

正如您所看到的,如果splatted容器中有错误数量的元素,那么函数调用将失败,就像显式给出了太多参数一样。

可选参数

通常可以为函数参数提供合理的默认值。 这可以使用户不必在每次调用时传递每个参数。 例如,函数 日期(y,[m,d])日期 模块构造一个 日期 指定年份的类型 y,月份 m 和日 d. 然而, md 参数是可选的,它们的默认值是 1. 这种行为可以简明扼要地表达为:

julia> using Dates

julia> function date(y::Int64, m::Int64=1, d::Int64=1)
           err = Dates.validargs(Date, y, m, d)
           err === nothing || throw(err)
           return Date(Dates.UTD(Dates.totaldays(y, m, d)))
       end
date (generic function with 3 methods)

观察,这个定义调用的另一个方法 日期 接受一个类型参数的函数 UTInstant{Day}.

有了这个定义,函数可以用一个,两个或三个参数来调用,并且 1 仅指定一个或两个参数时自动传递:

julia> date(2000, 12, 12)
2000-12-12

julia> date(2000, 12)
2000-12-01

julia> date(2000)
2000-01-01

可选参数实际上只是一种方便的语法,用于编写具有不同数量参数的多个方法定义(请参阅 关于可选参数和关键字参数的注意)。 这可以检查我们的 日期 函数示例通过调用 方法 功能:

julia> methods(date)
# 3 methods for generic function "date":
[1] date(y::Int64) in Main at REPL[1]:1
[2] date(y::Int64, m::Int64) in Main at REPL[1]:1
[3] date(y::Int64, m::Int64, d::Int64) in Main at REPL[1]:1

关键字参数

有些函数需要大量的参数,或者有大量的行为。 记住如何调用这些函数可能很困难。 通过允许参数按名称而不是仅按位置标识,关键字参数可以使这些复杂接口更易于使用和扩展。

例如,考虑一个函数 情节 这画了一条线。 此函数可能有许多选项,用于控制线条样式、宽度、颜色等。 如果它接受关键字参数,则可能的调用可能如下所示 图(x,y,宽度=2),我们选择只指定线宽。 请注意,这有两个目的。 这个调用更容易阅读,因为我们可以用它的含义来标记一个参数。 也可以以任何顺序传递大量参数的任何子集。

带有关键字参数的函数在签名中使用分号定义:

function plot(x, y; style="solid", width=1, color="black")
    ###
end

当函数被调用时,分号是可选的:可以调用 图(x,y,宽度=2)图(x,y;宽度=2),但前者的风格更常见。 仅在传递varargs或计算关键字时才需要显式分号,如下所述。

关键字参数默认值仅在必要时(未传递相应的关键字参数时)按从左到右的顺序计算。 因此,默认表达式可以引用先前的关键字参数。

关键字参数的类型可以明确如下:

function f(; x::Int=1)
    ###
end

关键字参数也可以在varargs函数中使用:

function plot(x...; style="solid")
    ###
end

可以使用以下方法收集额外的关键字参数 ...,如在varargs函数:

function f(x; y=0, kwargs...)
    ###
end

里面 f, 夸格斯 将是命名元组上的不可变键值迭代器。 命名元组(以及键为 符号,以及其他产生以符号作为第一个值的双值集合的迭代器)可以在调用中使用分号作为关键字参数传递,例如 f(x,z=1;kwargs。..).

如果在方法定义中没有为关键字参数分配默认值,则它是_required_:an [医无犯罪者]如果调用者没有为其赋值,则会抛出异常:

function f(x; y)
    ###
end
f(3, y=5) # ok, y is assigned
f(3)      # throws UndefKeywordError(:y)

一个也可以通过 键=>值 分号后的表达式。 例如, 绘图(x,y;:宽度=>2) 相当于 图(x,y,宽度=2). 这在运行时计算关键字名称的情况下很有用。

当裸标识符或点表达式出现在分号之后时,关键字参数名称由标识符或字段名称隐含。 例如 图(x,y;宽度) 相当于 绘图(x,y;宽度=宽度)plot(x,y;选项.宽度) 相当于 plot(x,y;width=options.宽度).

关键字参数的性质使得可以多次指定相同的参数。 例如,在调用 plot(x,y;选项...,宽度=2) 这是可能的 选项 结构还包含一个值 阔度. 在这种情况下,最右边的出现优先;在这个例子中, 阔度 一定有这个价值 2. 但是,多次显式指定相同的关键字参数,例如 绘图(x,y,宽度=2,宽度=3),是不允许的,并导致语法错误。

默认值的评估范围

当计算可选和关键字参数默认表达式时,只有_previous_参数在作用域中。 例如,给定这个定义:

function f(x, a=b, b=1)
    ###
end

ba=b 指一个 b 在外部范围中,而不是后面的参数 b.

函数参数的Do-Block语法

将函数作为参数传递给其他函数是一种强大的技术,但它的语法并不总是方便的。 当函数参数需要多行时,这样的调用尤其难以编写。 作为一个例子,考虑调用 地图关于有几种情况的函数:

map(x->begin
           if x < 0 && iseven(x)
               return 0
           elseif x == 0
               return 1
           else
               return x
           end
       end,
    [A, B, C])

Julia提供了一个保留字 为了更清楚地重写这段代码:

map([A, B, C]) do x
    if x < 0 && iseven(x)
        return 0
    elseif x == 0
        return 1
    else
        return x
    end
end

做x 语法创建一个带参数的匿名函数 x 并将匿名函数作为第一个参数传递给"外部"函数 - 地图在这个例子中。 同样地, 做a,b 将创建一个双参数匿名函数。 请注意 做(a,b) 将创建一个单参数匿名函数,其参数是要解构的元组。 一个平原 会声明下面是表单的匿名函数 () -> ....

如何初始化这些参数取决于"外部"函数;这里, 地图将顺序设置 xA, B, C,在每个调用匿名函数,就像语法中会发生的那样 地图(func,[A,B,C]).

这种语法使得使用函数有效扩展语言变得更加容易,因为调用看起来像普通的代码块。 有许多可能的用途完全不同于 地图,如管理系统状态。 例如,有一个版本的 打开运行确保打开的文件最终关闭的代码:

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

这里, 打开首先打开文件进行写入,然后将生成的输出流传递给您在 做。.. 结束 块。 函数退出后, 打开将确保流正确关闭,无论您的函数是否正常退出或抛出异常。 (该 尝试/最后 构造将在 控制流程。)

块语法,它有助于检查文档或实现,以了解用户函数的参数是如何初始化的。

A block和任何其他内部函数一样,可以从它的封闭作用域中"捕获"变量。 例如,变量 数据资料 在上面的例子中 open...do 是从外部范围捕获的。 捕获的变量可能会产生性能挑战,如 性能提示

功能组成及配管

Julia中的函数可以通过组合或管道(链接)它们来组合。

函数组合是指将函数组合在一起并将生成的组合应用于参数。 您使用函数组合运算符()来组成函数,所以 (f≠g)(args...;千瓦。..) 是一样的 f(g(args...;千瓦。..)).

您可以在REPL和适当配置的编辑器中键入组合运算符,使用 \circ<tab>.

例如, sqrt,sqrt+ 函数可以这样组成:

julia> (sqrt ∘ +)(3, 6)
3.0

这首先添加数字,然后找到结果的平方根。

下一个示例组合了三个函数,并将结果映射到一个字符串数组上:

julia> map(first ∘ reverse ∘ uppercase, split("you can compose functions like this"))
6-element Vector{Char}:
 'U': ASCII/Unicode U+0055 (category Lu: Letter, uppercase)
 'N': ASCII/Unicode U+004E (category Lu: Letter, uppercase)
 'E': ASCII/Unicode U+0045 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)
 'E': ASCII/Unicode U+0045 (category Lu: Letter, uppercase)
 'S': ASCII/Unicode U+0053 (category Lu: Letter, uppercase)

函数链接(有时称为"管道"或"使用管道"将数据发送到后续函数)是将函数应用于前一个函数的输出:

julia> 1:10 |> sum |> sqrt
7.416198487095663

在这里,由 总和 被传递给 sqrt,sqrt 函数。 等效的组成将是:

julia> (sqrt ∘ sum)(1:10)
7.416198487095663

管道操作员也可以与广播一起使用,如 .|>,以提供链接/管道和点矢量化语法的有用组合(如下所述)。

julia> ["a", "list", "of", "strings"] .|> [uppercase, reverse, titlecase, length]
4-element Vector{Any}:
  "A"
  "tsil"
  "Of"
 7

当将管道与匿名函数组合时,如果后续管道不被解析为匿名函数主体的一部分,则必须使用括号。 比较一下:

julia> 1:3 .|> (x -> x^2) |> sum |> sqrt
3.7416573867739413

julia> 1:3 .|> x -> x^2 |> sum |> sqrt
3-element Vector{Float64}:
 1.0
 2.0
 3.0

矢量化函数的点语法

在技术计算语言中,通常会有函数的"矢量化"版本,这些版本只是应用给定的函数 f(x) 到数组的每个元素 A 产生一个新的数组 f(A). 这种语法便于数据处理,但在其他语言中也经常需要矢量化来提高性能:如果循环很慢,函数的"矢量化"版本可以调用用低级语言编写的快速库代码。 在Julia中,矢量化函数是_not_性能所需的,实际上编写自己的循环通常是有益的(参见 性能提示),但他们仍然可以方便。 因此,_any_Julia函数 f 可以使用以下语法将元素应用于任何数组(或其他集合) f.(A). 例如, 可应用于向量中的所有元素 A 像这样:

julia> A = [1.0, 2.0, 3.0]
3-element Vector{Float64}:
 1.0
 2.0
 3.0

julia> sin.(A)
3-element Vector{Float64}:
 0.8414709848078965
 0.9092974268256817
 0.1411200080598672

当然,如果你写一个专门的"向量"方法,你可以省略点。 f,例如通过 F(A::AbstractArray)=map(f,A),这是一样有效 f.(A). 的优势 f.(A) 语法是哪些函数是可向量化的,不需要由库编写者预先决定。

更一般地说, f.(args...) 实际上相当于 广播(f,args...),它允许您操作多个数组(即使是不同形状的),或者数组和标量的混合(参见 广播)。 例如,如果你有 f(x,y)=3x+4y,则 f.(pi,A) 将返回一个由 f(pi,a) 对于每个 aA,而 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 Vector{Float64}:
 13.42477796076938
 17.42477796076938
 21.42477796076938

julia> f.(A, B)
3-element Vector{Float64}:
 19.0
 26.0
 33.0

关键字参数不会广播,而是简单地传递给函数的每个调用。 例如, 圆。(x,数字=3) 相当于 广播(x->圆(x,数字=3),x).

此外,nested f.(args...) 调用_fused_为单个 广播 循环。 例如, 罪。(cos.(X)) 相当于 广播(x->sin(cos(x)),X),类似于 [x中x的sin(cos(x))]:只有一个循环过来 X,并为结果分配单个数组。 [相比之下, 罪(cos(X)) 在典型的"矢量化"语言中,首先会为 tmp=cos(X),然后计算 罪(tmp) 在单独的循环中,分配第二个数组。]这个循环融合不是可能发生也可能不会发生的编译器优化,它是一个_syntactic保证 f.(args...) 遇到呼叫。 从技术上讲,一旦遇到"非点"函数调用,融合就会停止;例如,在 罪。(排序(cos。(X)))[医] 循环不能合并,因为介入 排序 函数。

最后,当向量化操作的输出数组为_pre-allocated_时,通常可以实现最大效率,以便重复调用不会为结果一遍又一遍地分配新数组(请参阅 预分配产出)。 一个方便的语法是 X.= ...,相当于 广播!(身份,X,。..) 但如上所述, 广播! 循环与任何嵌套的"点"调用融合在一起。 例如, X.=罪。(Y) 相当于 广播!(sin,X,Y),复盖 X罪。(Y) 到位。 如果左侧是数组索引表达式,例如 X[开始+1:结束]。=罪。(Y),然后它转化为 广播! 在一个 查看,例如 广播!(sin,view(X,firstindex(X)+1:lastindex(X)),Y),使左侧就地更新。

由于在表达式中的许多操作和函数调用中添加点可能很乏味,并导致代码难以阅读,因此宏 @.提供用于将表达式中的_every_函数调用,操作和赋值转换为"虚线"版本。

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 Vector{Float64}:
  0.5143952585235492
 -0.4042391538522658
 -0.8360218615377305
 -0.6080830096407656

二元(或一元)运算符,如 .+ 用相同的机制处理:它们等价于 广播 调用,并与其他嵌套的"点"调用融合在一起。 X.+=Y etcetera相当于 X.=X.+Y 并导致融合的就地分配;参见 点运算符

您还可以使用[将点操作与函数链接结合起来`|>`](/base/base#Base.:

>),如本例所示:

julia> 1:5 .|> [x->x^2, inv, x->2&ast;x, -, isodd]
5-element Vector{Real}:
    1
    0.5
    6
   -4
 true

融合广播中的所有函数总是为结果的每个元素调用。 因此 X.+ σ .*兰德。() 将为数组的每个元素添加独立且相同采样的随机值的掩码 X,但是 X.+ σ .*兰德() 将_same_随机样本添加到每个元素。 在融合计算沿着广播迭代的一个或多个轴是恒定的情况下,可能利用空间-时间权衡并分配中间值以减少计算次数。 查看更多 性能提示

进一步阅读

我们应该在这里提到,这远不是定义函数的完整图景。 Julia有一个复杂的类型系统,并允许对参数类型进行多次调度。 这里给出的示例都没有对其参数提供任何类型注释,这意味着它们适用于所有类型的参数。 类型系统描述于 Types并根据运行时参数类型上的多个调度选择的方法定义函数,描述于 方法