erg/doc/zh_CN/syntax/04_function.md

7.4 KiB
Raw Blame History

函数

函数是一个块,它接受参数并对其进行处理,然后将其作为返回值返回。定义如下。

add x, y = x + y
# or
add(x, y) = x + y

在定义函数时指定的参数通常称为伪参数parameter。相反函数调用过程中传递的参数称为实际参数argument。是接受<gtr=“31”/>和<gtr=“32”/>作为假参数,然后返回<gtr=“33”/>的函数。你可以按如下方式调用(应用)定义的函数。

add 1, 2
# or
add(1, 2)

冒号样式

函数的调用方式如下:,但如果实际参数太多,一行太长,则可以使用<gtr=“35”/>(冒号)来应用。

f some_long_name_variable_1 + some_long_name_variable_2, some_long_name_variable_3 * some_long_name_variable_4
f some_long_name_variable_1 + some_long_name_variable_2:
    some_long_name_variable_3 * some_long_name_variable_4
f:
    some_long_name_variable_1 + some_long_name_variable_2
    some_long_name_variable_3 * some_long_name_variable_4

上面三个代码都是同一个意思。此样式在使用函数时也很有用。

result = if Bool.sample!():
    do:
        log "True was chosen"
        1
    do:
        log "False was chosen"
        0

在之后,不能写注释以外的代码,必须换行。

关键字参数Keyword Arguments

如果定义了具有大量参数的函数,则可能会导致传递参数的顺序错误。在这种情况下,使用关键字参数进行调用是安全的。

f x, y, z, w, v, u: Int = ...

上面定义的函数有很多参数,并且排列很难懂。我们不应该做这样的函数,但在使用别人写的代码时可能会碰到这样的代码。因此,我们使用关键字参数。关键字参数的名称优先于顺序,因此即使顺序不正确,也会将值从名称传递到正确的参数。

f u: 6, v: 5, w: 4, x: 1, y: 2, z: 3

请注意,如果在关键字参数和之后立即换行,将被视为冒号应用样式。

# means `f(x: y)`
f x: y

# means `f(x, y)`
f x:
    y

默认参数Default parameters

如果一个参数在大多数情况下是固定的,并且你想要省略它,则可以使用默认参数。

缺省参数由or-assign operator指定。如果未指定<gtr=“40”/>,则将<gtr=“41”/>赋给<gtr=“42”/>。

math_log x: Ratio, base := math.E = ...

assert math_log(100, 10) == 2
assert math_log(100) == math_log(100, math.E)

请注意,不指定参数和赋值是有区别的。

p! x := 0 = print! x
p!(2) # 2
p!() # 0
p!(None) # None

也可以与类型和模式一起使用。

math_log x, base: Ratio := math.E = ...
f [x, y] := [1, 2] = ...

但是,在缺省参数中,不能调用以下过程或赋值可变对象。

f x := p! 1 = ... # NG

此外,不能将刚定义的参数用作传递给缺省参数的值。

f x := 1, y := x = ... # NG

可变长度参数

函数将参数作为日志输出,可以接收任意数量的参数。

log "Hello", "World", "!" # Hello World !

如果要定义这样的函数,请将作为参数。这样,参数就可以作为可变长度数组接收。

f x: ...Int =
    for x, i ->
        log i

# x == [1, 2, 3, 4, 5]
f 1, 2, 3, 4, 5

多模式函数定义

fib n: Nat =
    match n:
        0 -> 0
        1 -> 1
        n -> fib(n - 1) + fib(n - 2)

如果函数的定义正下方出现,如上面所示,则可以重写如下所示。

fib 0 = 0
fib 1 = 1
fib(n: Nat): Nat = fib(n - 1) + fib(n - 2)

请注意,多模式函数定义不是所谓的过载(多重定义)。一个函数始终只有一个类型。在上面的示例中,必须与<gtr=“48”/>和<gtr=“49”/>具有相同的类型。此外,与<gtr=“50”/>相同,模式匹配从上到下依次进行。

如果存在不同类的混合实例,则必须在最后一个定义中指明函数参数类型为 Or。

f "aa" = ...
f 1 = ...
# `f x = ...` is invalid
f x: Int or Str = ...

它还必须具有包容性,如。

fib 0 = 0
fib 1 = 1
# PatternError: pattern of fib's parameter is not exhaustive

但是,即使在上述情况下,也可以使用下面的显式指定类型来获得全面性。

fib: 0..1 -> 0..1
fib 0 = 0
fib 1 = 1
# OK

递归函数

递归函数是定义中包含自身的函数。

作为一个简单的例子我们尝试定义函数来计算阶乘。阶乘是“乘以所有小于或等于的正数”的计算。5 的阶乘为。

factorial 0 = 1
factorial 1 = 1
factorial(n: Nat): Nat = n * factorial(n - 1)

首先从阶乘定义开始0 和 1 的阶乘都是 1. 按顺序计算2 的阶乘为3 的阶乘为4 的阶乘为。如果你仔细观察这里,你会发现一个数字 n 的阶乘是它前面的数字 n-1 的阶乘乘以 n。如果你将其放入代码中则会得到。<gtr=“60”/>是递归函数,因为<gtr=“59”/>的定义包含它自己。

注意,如果未指定类型,则会这样推断。

factorial: |T <: Sub(Int, T) and Mul(Int, Int) and Eq(Int)| T -> Int
factorial 0 = 1
factorial 1 = 1
factorial n = n * factorial(n - 1)

但是,即使可以推理,也应该明确指定递归函数的类型。在上面的示例中,像这样的代码是有效的,

factorial(-1) == -1 * factorial(-2) == -1 * -2 * factorial(-3) == ...

,此计算不会停止。如果不仔细定义值的范围,递归函数可能会陷入无限循环。类型还有助于防止接受不想要的值。

编译时函数

如果函数名以大写字母开头,则该函数为编译时函数。所有用户定义的编译时函数的参数都必须是常量,并且必须显式。编译函数能做的事情是有限的。在编译时函数中只能使用常量表达式,即某些运算符(四则运算,比较运算,类型构建运算等)和编译时函数。赋值的参数也必须是常量表达式。相反,计算可以在编译时进行。

Add(X, Y: Nat): Nat = X + Y
assert Add(1, 2) == 3

Factorial 0 = 1
Factorial(X: Nat): Nat = X * Factorial(X - 1)
assert Factorial(10) == 3628800

math = import "math"
Sin X = math.sin X # ConstantError: this function is not computable at compile time

编译时函数通常用于多相类型定义等。

Option T: Type = T or NoneType
Option: Type -> Type

Appendix比较函数

Erg 没有为函数定义。那是因为函数的结构等价性判定算法一般不存在。

f = x: Int -> (x + 1)**2
g = x: Int -> x**2 + 2x + 1

assert f == g # TypeError: cannot compare functions

和<gtr=“64”/>总是返回相同的结果但这是非常困难的。我们得把代数学灌输给编译器。因此Erg 放弃了整个函数比较,<gtr=“65”/>也会导致编译错误。这是与 Python 不同的规格,需要注意。

# Python, weird example
f = lambda x: x
assert f == f
assert (lambda x: x) != (lambda x: x)

Appendix2完成

f x: Object = ...
# will be completed to
f(x: Object) = ...

f a
# will be completed to
f(a)

f a, b # TypeError: f() takes 1 positional argument but 2 were given
f(a, b) # TypeError: f() takes 1 positional argument but 2 were given
f((a, b)) # OK

函数类型实际上是<gtr=“67”/>的糖衣语法。

Previous | Next