函数

函数是基本的代码块, 用于执行一个任务.

你可以通过函数来划分不同功能, 逻辑上每个函数执行的是指定的任务.

Z1h 语言标准库提供了多种可动用的内置的函数. 例如, len() 函数可以接受不同类型参数并返回该类型的长度. 如果我们传入的是字符串则返回字符串的长度, 如果传入的是数组, 则返回数组中包含的元素个数.

函数定义

函数定义格式如下

函数名 = 参数 => {
    函数体
}

函数名 = (参数1, 参数2...) => {
    函数体
}

func 函数名(参数1, 参数2...) { // 这种情况只能声明局部的函数
    函数体
}

也可以将形参的内容解出, 如

// 将第一个参数的key1、key2值解出
函数名 = ({key1, key2}, 参数2) => {
    函数体
}

// 将第一个参数(数组)的key1、key2值解出
函数名 = ([item1, item2], 参数2) => {
    函数体
}

示例

add1 = num => {
    return num + 1
}

add = (num1, num2) => num1 + num2

func minus(num1, num2) {
    return num1 - num2
}

hello = {name, age} => {
    printf('Hello, name = %v, age = %v', name, age)
}

hi = ([p1, p2], a2) => {
    printf({p1, p2, a2})
}

定义解析

名称 描述
func 函数由 func 开始声明
函数名称 函数名就是这个函数的变量名
参数列表 参数就像一个占位符, 当函数被调用时, 你可以将值传递给参数, 这个值被称为实际参数. 参数列表指定的是参数类型、顺序、及参数个数. 参数是可选的, 也就是说函数也可以不包含参数.
函数体 函数定义的代码集合.

函数调用

当创建函数时, 你定义了函数需要做什么, 通过调用该函数来执行指定任务.

调用函数, 向函数传递参数, 并返回值, 例如:

func getDouble(num) {
    return num * 2;
}

getDouble(100);
// 输出200

函数返回多个值

Z1h 函数可以返回多个值, 调用得到的值是一个数组, 例如:

func nameAndSuffix(str) {
    var suffix = $file.ext(str, '');
    return (suffix ? str[:suffix.length+1]: str), suffix;
}

print(nameAndSuffix("hello.z1h"));
// 输出[ "hello", ".z1h" ]

[foo, bar] = nameAndSuffix('index.html');
print(`${foo} and ${bar}`)

示例

add = e => {
    e + 1
}
print(add(3))



plus = (num1, num2) => {
    num1 + num2
}
print(plus(3, 7))



func minus(n1, n2) {
    n1 - (n2 || 0)
}
minus(8, 7)

自动关闭的对象

如果一个对象包含Close函数(包括自定义结构体/map/原生对象), 可以使用该函数自动关闭

这个使用方式与Python很相似

// os.Create 返回的是 *os.File 类型, 包含Close函数
with assert(os.Create(`test.txt`)) as f {
    assert(f.Write("你好啊".bytes))
}

或者

with {
    tag: '测试',
    Close: e=>{print(`关闭: 标记=${this.tag}, 名称=${this.name}`)},
} as obj {
    obj.name = "haha"
    print('设置了name')
}

逃逸对象(闭包)

如果你的函数返回了函数(或函数的某种形式的引用), 包含了上层定义的变量, 此时就需要进行变量的逃逸 (相当于js里的闭包概念)

Z1h也是支持逃逸(闭包)的, 在产生函数的时候会保存当前的栈环境, 示例代码如下:

// 声明一个函数, 这个函数返回了三个函数
f = e => {
    var i = 1 // 三个函数共用的变量
    return [
        e => { i++ },
        e => { i += 2 },
        e => { i = nil; print('I=' + i) },
    ]
};
// 获取一套函数
[f1, f2, f3] = f()

// 以下函数逐行调用, 并观察结果
f1()
f2()
f3()
f2()
f1()
print(i) // 报错: 没有i这个变量

函数调用–函数名后置

foo = arg => print({arg})
bar = 123

(bar)foo
// 等同于
foo(bar)

// 都会输出 {"arg":123}

要求:

被调用的函数必须是 单个函数变量名, 不能是获取对象变量等其它方式(因为会产生歧义, 并且降低可读性), 例如下面这样是被禁止的

(bar)people.foo // 不允许这样写, 会报错: 语法错误
(bar)people[0] // 不允许这样写, 会报错: 语法错误

结合 #符号 可以做到这种用法

x = "aaa"
b = @ { print(a + "b") }
c = @ { print(a + "c") }
d = @ { print(a + "d") }
e = @ { print(a + "e") }
f = @ { print(a + "f") }
res = x# b# c# d# e# f // aaabcdef
// 因为上面的变成了 f(e(d(c(b(x)))))

函数调用–管道操作符(Pipeline Operator)

建议与下方"绑定函数"结合使用

funcs = {
    foo1: arg => arg + 1,
    foo2: arg => arg * 100,
}
foo3 = arg => "Result: " + arg
bar = 123

bar |> funcs.foo1 |> funcs.foo2 |> foo3 // Result: 12400

// 近似等于

foo3(funcs.foo2(funcs.foo1(bar))) // Result: 12400

区别

之所以说"近似等于", 是因为两种调用函数的方式略有区别:

通过管道操作符的方式, 是先计算左边传入的参数, 再计算右边调用的函数, 最后调用 传统的函数调用时先计算函数, 再计算参数, 再调用 通过以下代码能更为直观的感受到区别

foo = @{panic("foo")} // 快速声明函数的语法糖, @{TODO} 等同于 (a, a1, a2)=>{TODO}
bar = @{panic("bar")}

// 这种情况bar函数还没有被调用就崩了
foo()(bar()) // Panic at call func: foo ...


// 这种情况foo函数还没有被调用就崩了
bar() |> foo() // Panic at call func: bar ...

绑定函数

先看以下柯里化的示例

foo = (a, b) => (a + 1) * (b + 100)
foo1 = b => foo(1, b)
foo1(3) // 206

可以用语法糖进行简化

foo = (a, b) => (a + 1) * (b + 100)
foo(1, ~0)(3) // 206

上文示例中, foo(1, ~0) 本质上等于 arg => foo(1, arg)

~0 意思是绑定了未来传入的第0个参数, 可以以此类推 ~1~2、…、~99999都是可以的 并且~0等绑定占位符可以进行一些简单的操作符运算, 或者放在map/array里面作为元素

示例:

x = (a, b) => print({ a, b })
x(~1 / 3, ~0 / 2 + ~1 / 2 + "Heihei")(222, 333)
// {"a":111,"b":"277Heihei"}

x([333, 222, {
    [~1 / 3]: "xx" + ~0,
}], ~0 / 2 + ~1 / 2 + "Heihei")(222, 333)

当然了, ~0等绑定占位符如果放在嵌套函数里, 意思就变了

bar = foo(~1 + 2, ~0)
// 等价于
bar = (arg0, arg1) => {
    return foo(arg1 + 2, arg0)
}

// 但是
bar = foo(~1 + 2, foo1(~0))
// 这个foo1(~0)就会马上被执行, 返回了一个被绑定了的函数, 这个~0不是foo的, 而是foo1的了. 相当于
bar = (_, arg1) => {
    return foo(arg1 + 2, arg0 => {
        return foo1(arg0)
    })
}

绑定函数与管道操作符结合

其实绑定函数主要就是为了解决管道操作符(见上方)的易用性问题

"aaa,bbb" |> strings.Split(~0, ",")
// 相当于
foo = arg => strings.Split(arg, ",")
foo("aaa,bbb")

与Go原生交互

如果一个函数需要转成Go的原生函数, 只需要使用以下方式即可创建:

f = (e=>`传入参数的平方为${e*e}`).native_int_return_string
// 后面的部分也可以简写成 .native_i_return_s
print('Go类型:', type(f))
// func(int) string
print('返回结果:', f(20))
// "传入参数的平方为400"

如果需要创建有复杂类型的函数, 可以通过native函数:

f = new('func',
    ['', z1h.ElemType(os.FileInfo.Ptr), z1h.ElemType(errors.Interface)],
    z1h.ElemType(errors.Interface),
    (name, info, err)=>{
        print(`Name: ${name}, Size: ${info.Size()?? 'No info'}`)
        return z1h.NilValue(errors.Interface)
    }
)
print(type(f))
// func(string, os.FileInfo, error) error
path_filepath.Walk(".", f)