函数
函数是基本的代码块, 用于执行一个任务.
你可以通过函数来划分不同功能, 逻辑上每个函数执行的是指定的任务.
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)