Julia-函数

函数

在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)

尽管函数可以是复合表达式 (见 Compound Expressions),但在赋值形式下,函数体必须是一个一行的表达式。简短的函数定义在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函数参数遵循有时称为“pass-by-sharing”的约定,这意味着变量在被传递给函数时其值并不会被复制。函数参数本身充当新的变量绑定(指向变量值的新地址),它们所指向的值与所传递变量的值完全相同。调用者可以看到对函数内可变值(如数组)的修改。这与Scheme,大多数Lisps,Python,Ruby和Perl以及其他动态语言中的行为相同。
注:在julia中所有变量都是对象,即变量传参即是传递对指向对象的引用,在函数内部如果对不可变参数进行赋值运算,即是改变了参数的指向,不会影响实参值,如果对可变参数(如数组)进行修改,则不会改变指向,此时时参与形参指向同一对象(数组)

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的三角形的斜边长度时可以避免overflow:

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可以省略,因为它是最后一个表达式。

也可以使用::运算符在函数声明中指定返回类型。 这可以将返回值转换为指定的类型。

这个函数有三个可能的返回处,返回三个不同表达式的值,具体取决于x和y的值。 最后一行的return可以省略,因为它是最后一个表达式。

也可以使用::运算符在函数声明中指定返回类型。 这可以将返回值转换为指定的类型。

注:分号的作用:在repl模式(交互模式下)end后不另分号分提示定义函数的描述,加分则不显示,如果是运行文件不加分号不刷屏,加上分刷屏;附图如下:
这里写图片描述

操作符也是一类函数


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

julia> 1 + 2 + 3
6

julia> +(1,2,3)#+表示调用加法函数,+号代表这个函数引用;看下面f= +赋值操作
6

中缀形式和函数形式的使用完全等价。 事实上,前一种形式被内在地解释为函数调用。 这意味着你可以对操作符,例如 + and * 进行赋值和传递,就像对其它函数值一样。

julia> f = +;#将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

如果已经存在转换函数的命名函数作为第一个参数传递到映射,那么这是很好的。然而,通常不存在准备好使用的命名函数。在这些情况下,匿名函数构造允许轻松创建单用函数对象,而不需要名称:

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

一个接受多个参数的匿名函数可以使用语法(x,y,z)-> 2x+yz来编写。一个零参数匿名函数被写为()->3。没有参数的函数的概念似乎很奇怪,但对于延迟计算是有用的。在这个用法中,一个代码块被封装在一个零参数函数中,这个函数后来被调用为F。

元组


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

命名元组


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

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)=参数:

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

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

julia> range(minmax(10, 2))
8

注:插入赋值,将元组中的参数按照等号左侧的表达式运算完后的结果当做range函数最终的参数附图如下
这里写图片描述

可变参数函数


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

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

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

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))

此外,将Script映射到函数调用中的迭代对象不必是元组:

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])为给定的年份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

注意,此定义调用另一个日期函数方法,该方法接受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

可选参数实际上只是编写具有不同数量的参数的多个方法定义的方便语法(参见关于可选参数和关键字参数的说明)。可以通过调用方法函数对我们的Datefunction示例进行检查。

关键字参数


有些函数需要大量的参数,或者有大量的行为。记住如何调用这些函数是很困难的。关键字参数可以使这些复杂接口更易于使用和扩展,方法是允许通过名称而不是仅按位置标识参数。
例如,考虑绘制一行的函数图。此函数可能有许多选项,用于控制线条样式、宽度、颜色等。如果它接受关键字参数,那么一个可能的调用可能类似于plote(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

如果在方法定义中没有为关键字参数分配默认值,那么就需要它:如果调用者没有为它分配值,则将引发UndefKeywordError异常:

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

在f内,kwargs将是一个名为tuple的元组。命名元组(以及字典)可以在调用中使用分号作为关键字参数传递,例如f(x,z=1;kwargs.)。
还可以在分号之后传递key=>value表达式。例如,绘图(x,y;:Width=>2)等价于绘图(x,y,宽度=2)。这在运行时计算关键字名的情况下非常有用。
关键字参数的性质使得可以多次指定相同的参数。例如,在调用图(x,y;Options.,Width=2)中,Options结构也可能包含宽度的值。在这种情况下,最右边的发生优先;在本例中,宽度肯定有值2。但是,不允许多次显式地指定相同的关键字参数,例如图(x,y,宽度=2,宽度=3),并导致语法错误。

默认值的计算范围


当计算可选参数和关键字参数默认表达式时,只有以前的参数在作用域中。例如,鉴于这一定义:

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

a=b中的b指的是外部作用域中的a 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创建匿名函数,并将其作为映射的第一个参数传递。类似地,do a,b将创建一个双参数匿名函数,而普通do将声明以下是表单()->.的匿名函数。
如何初始化这些参数取决于“外部”函数;在这里,map将顺序地将x设置为A、B、C,对每个参数调用匿名函数,就像在语法映射(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/finallystructure将在ControlFlow中描述。)
使用DO块语法,它有助于检查文档或实现,以了解用户函数的参数是如何初始化的。

与任何其他内部函数一样,DO块可以从其封闭作用域“捕获”变量。例如,上面的open.do示例中的变量数据是从外部范围捕获的。捕获的变量可以像性能提示中讨论的那样创建性能挑战。

矢量函数的点句法


待续

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值