函数规范
q不是纯粹的函数式语言,因为q函数可以访问全局变量。
函数定义
函数体用{}包围,[]用于输入参数列表,分号(;)用于分隔各行代码和参数列表,输入参数和返回值不指定类型。调用时参数列表也是用[]包围,分号分隔
q)f:{[a;b] a:a+b; a*b} q)f[3;4] 28
匿名函数
q){x:x+y; x*y}[3;4] 28
函数符号和术语
{[p1;...;[n]e1;...;en}
p1...pn是输入参数,e1...en是一系列表达式。最多只能有8个参数。可以用列表或者其他数据结构(例如字典)来封装多个参数。
建议:Q函数应该是紧凑和模块化的:每个函数应该执行定义良好的工作单元。如果超过20行,就需要看看能不能分解它。
函数内定义的变量是本地变量。
函数返回值
- 没有左边变量名的赋值语句,相当于Java的return,后面的语句都不会被执行,如
:1b
- 如果没有上述语句,则使用执行的最后一个表达式的值作为返回值,如:
q)f3:{[x] x*x} q)f3[3] 9
q一般不支持函数重载(即函数名相同,参数个数不同),但是有些操作符和内建函数支持重载,例如?操作符,需要根据它的操作数决定它的含义。
q)f3:{[x] x*x} q)f3[3] 9 q)f3:{[x;y] x*y} q)f3[3] {[x;y] x*y}[3] q)f3[3;4] 12
隐式参数
如果定义函数的时候不使用[]定义其参数,那么x、y、z自动代表第一、二、三个参数。例如以下两个函数式等价的
q)f:{[x] x*x} q)g:{x*x}
注意一旦使用了[],即使里面是空的,隐式参数也是不存在的
q)g:{[] x*x} q)g[3] {[] x*x} 'x
如果在函数里使用了隐式参数,那么尽量不要使用x、y、z作为其它变量,以免混淆。
匿名函数
匿名函数多用于只用一次的情况。
q)){x*x} [3] 9
Identity函数(::)
::函数返回其参数。一个应用场景是当需要在同一数据上应用多个操作时,其中一个操作是什么也不做
q)(::;avg)@\:1 2 3 1 2 3 2f
函数作为被操作的对象
q也有函数式编程的特性, 函数可以作为一个值赋给变量
q)f:{2*x} q)a:f q)a 3 6
注意赋值的时候函数已经被解析成它的内容了,后面更改这个函数的时候不影响被赋值的变量,除非使用别名(::)
q)f:{3*x} q)a {2*x} q)b::f q)f:{4*x} q)b {4*x}
本地变量和全局变量
本地变量
函数内定义的变量叫本地变量。本地变量的生命周期是从第一次赋值开始,到函数执行完毕。
全局变量
定义在函数外的变量或者在函数内使用::定义的变量是全局变量。似乎在函数内使用::表示全局变量,在函数外使用::表示别名
q)a:1 q)b::a / 别名,b随a变而变 q)b 1 q)a 1 q)a:2 q)b 2 q)a 2 q)f:{m:1;n::m;m:2} /全局变量,n不随m变而变 q)f[] 2 q)n 1
本地和全局的冲突
在函数内,本地变量会掩盖掉全局变量
q)a:42 q)f:{a:98; x+a} q)f[6] 104 q)a 42
即使用::重新定义变量(相当于::无效,等同于:)
q)a:42 q)f:{a:6;a::98;x*a} q)f[6] 588 q)a 42 q)f:{a:6;a:98;x*a} q)f[6] 588 q)a 42
在函数定义了全局变量后赋值都必须用::,否则抛出错误
q)f:{b::6} q)f:{b::6;b:98} 'b q)f:{b::6;b::98}
赋值(:)
类似于C、Java,q也支持如下的缩写
q)x:42 q)x+:2 / 相当于x:x+2 q)x 44
这种写法也可以应用于列表
q)L1:100 200 300 400 q)L1+:1 q)L1 101 201 301 401 q)L1[0 2]+:99 q)L1 200 201 400 401
投影(Projection)
投影这个翻译不知道准不准。
函数投影(Function Projection)
有些函数有超过2个的参数,我们经常会遇到这样的情况,其中一个参数是常量。每次都输入该常量繁琐而且有可能写错。在Java里我们可能会新加一个函数,参数个数少于原函数,在新函数里调用原函数,缺少的参数用常量传递给原函数。在q里我们可以这样做
q)diff:{x-y} q)diff_42:diff[42;] q)diff_42[6] 36 q)diff_42 {x-y}[42;]
常量位置并不限于第一个,也可以用diff[;6]形式指定第二个参数为6。
函数投影里后面的分号是可以省略的,例如diff[42;]可以写作diff[42],但是不建议这么做,因为会影响可阅读性,例如diff[42;][6]比diff[42][6]更清晰。
另外调用投影函数的时候,定义投影函数的[]是必须的,后面用于参数的[]则可以省略,如
diff[;42] 6
Verb Projection
二元操作符可以进行如下投影,但可以用于左边的参数,不能用于右边的参数,因为这些操作符放在值左边的话可能有别的含义,例如(-42)表示原子数-42
q)(42-)6 36 q)6(-42) ': Bad file descriptor q)(-42)6 'type
可以使用跟函数投影一样的方式来支持右边的参数
q)-[;42]98 56
多重投影
当参数超过2个,投影同样可以应用到多个参数上,如f[1;;3][5],等同于f[1;;][;3][5](注意3前面只有一个分号,因为f[1;;]只有2个参数了)或f[;;3][1;][5]。
注意投影函数同样支持别名
q)f:{x-y} q)g:f[42;] q)g {x-y}[42;] q)h::f[42;] q)h {x-y}[42;] q)f:{x+y} q)g {x-y}[42;] q)h {x+y}[42;]
列表和函数作为映射
本章阐述了列表和函数的关系
类似的写法
列表下标和函数的参数很类似
q)L:(0 1 4 9 16 25 36) q)f:{[x] x*x} q)L[2] 4 q)f[2] 4 q)L 5 25 q)f 5 25 q)L 3 6 9 36 q)f 3 6 9 36
列表是一个下标跟值的映射,而函数是一个由一系列表达式组成的算法定义的从输入到输出的映射。
智能列表处理
q)L[2 5] 4 25 q)f[2 5] 4 25
多维下标和不规则数组
嵌套的列表可以看做是列表的列表,同时也可以看做是输入-输出关系的多元映射。
创建不规则数组
q)0N 3#til 10 0 1 2 3 4 5 6 7 8 ,9
投影和下标省略
函数投影和多维列表下标省略具有相同的形式,如L[1;]和F[1;]。
下标越界
如果把列表看做是输入为整数的函数/映射,如果下标越界返回空就容易理解了。
数据转换成字符串
使用string函数可以将任意q实体转换成字符串(即字符列表)。
副词
从语法的角度来说,q包含名词(数据实体如原子类型、列表、字典、表,函数也是名词)、动词(基本符号操作符、中缀操作符)、副词(用于修饰动词或函数来衍生一个新的动词或函数)。
例如:c:a+b里a/b/c是名词,:/+是动词;而c:+[a;b]里a/b/c/+是名词,:是动词。
q里有如下副词
符号 | 名词 |
'(这里是单引号) | each both |
each | each monadic |
/: | each right |
\: | each left |
/ | over |
\ | scan |
':(这里是单引号) | each previous |
each-both(')
原文所述比较难理解,我个人理解是
- 对两边的操作数进行each操作,对列表而言就是取其中的每个元素
- both操作就是将两边操作数在第一步取出的元素一一对应起来,对列表而言就是按下标对应
- 将被修饰的动词(或函数)应用在第二步产生的两个操作数
原文中说被修饰的动词(或函数)与该副词之间不能有空白(空格等),但我试了是可以有的。
如果两个操作数都是列表,它们必须长度相等
q)L1:1 2 3 4 q)L2:5 6 q)L1,L2 1 2 3 4 5 6 q)L1,'L2 'length q)L1:1 2 3 4 q)L2:5 6 7 8 q)L1,L2 1 2 3 4 5 6 7 8 q)L1,'L2 1 5 2 6 3 7 4 8
如果一边是列表,一边是原子数,则将列表的每个元素跟原子数进行操作
q)L1,'1000 1 1000 2 1000 3 1000 4 1000
如果两边都是原子数,那么这个副词就没有意义了(跟不使用它一样)
q)1,2 1 2 q)1,'2 1 2
该副词也可以用于表的连接
q)t1:([] c1:1 2 3) q)t2:([] c2:`a`b`c) q)t1 c1 -- 1 2 3 q)t2 c2 -- a b c q)t1,'t2 c1 c2 ----- 1 a 2 b 3 c
同样的,行数必须相等。
Monadic each
该副词将一个非原子的函数应用到列表的每个元素上。有两种写法:
f each each[f]
后面的写法强调了each将函数转换为新的函数。
例如:
q)reverse (1 2;`a`b`c;"xyz") "xyz" `a`b`c 1 2 q)reverse each (1 2;`a`b`c;"xyz") 2 1 `c`b`a "zyx" q)each[reverse] (1 2;`a`b`c;"xyz") 2 1 `c`b`a "zyx"
注意不加each的时候操作是应用于整个列表,加each后是应用到列表的每个元素上。
each-left (\:)
该副词将右边的整个参数应用于左边参数的每个元素。
q)("Now";"is";"the";"time") , \: "," "Now," "is," "the," "time,"如果右边是原子数,那么它跟each-both的效果一样;否则不一样
q)("Now";"is";"the";"time") , \: "," "Now," "is," "the," "time," q)("Now";"is";"the";"time") ,' "," "Now," "is," "the," "time," q)("Now";"is";"the";"time") ,' " , " 'length q)("Now";"is";"the";"time") ,\: " , " "Now , " "is , " "the , " "time , " q)("Now";"is";"the";"time") ,' "~!@#" "Now~" "is!" "the@" "time#" q)("Now";"is";"the";"time") ,\: "~!@#" "Now~!@#" "is~!@#" "the~!@#" "time~!@#"
each-right (/:)
跟each-left只是方向相反。
笛卡尔积(,/:\:)
q)L1:1 2 q)L2:`a`b`c q)L1,/:\:L2 1 `a 1 `b 1 `c 2 `a 2 `b 2 `c q)L1,\:/:L2 1 `a 2 `a 1 `b 2 `b 1 `c 2 `c q)raze L1,/:\:L2 1 `a 1 `b 1 `c 2 `a 2 `b 2 `c q)raze L1,\:/:L2 1 `a 2 `a 1 `b 2 `b 1 `c 2 `c q)L1 cross L2 1 `a 1 `b 1 `c 2 `a 2 `b 2 `c
注意each left和each right位置不同结果也不一样
Over (/)
该副词将右边参数依次与左边参数进行操作,每次将结果当做下一个操作的左边参数。有点像java的StringBuffer的append方法
q)L:100 200 300 q)((L+1)+2)+3 106 206 306 q)L+/1 2 3 106 206 306 q)0+/10 20 30 60
打平一个列表
q)L1:(1; 2 3; (4 5; 6)) q)(),/L1 1 2 3 4 5 6 q)raze L1 1 2 3 4 5 6
函数
q)f:{2*x+y} q)100 f/ 1 2 3 822
字典
q)d:1 2 3!`a`b`c q)d _/1 3 2| b
Scan (\)
我的个人理解是它与over计算过程一致,不同的只是结果,over取的最后的计算结果作为返回值,而scan取每次计算的结果列表作为返回值
q)0+/1 2 3 6 q)0+\1 2 3 1 3 6
each-previous (':)
over和scan是每次把计算结果当做下次计算的左边参数,而each-previous是把右边列表中的上次参与计算的值当做下次计算的左边参数,例如scan是1+2,1+2+3,1+2+3+4,而each-previous则是1+2,2+3,3+4
q)1+\2 3 4 3 6 10 q)1+':2 3 4 3 5 7
它可以用于判断一个列表的变化趋势
q)0w>':8 9 7 8 6 7 010101b
下标和求值的动词形式
@
@左边是一个列表或者是一个一元或无参数函数,右边是下标列表或者参数列表(也可以是单个下标或参数),返回该列表对应下标列表的值或该函数对应该参数的返回值。如果是无参数函数,@右边可以是任意的标量。
q)L:0 1 2 q)L@0 0 q)L@0 2 0 2 q)f:{x*x} q)f@1 1 q)f@0 4 0 16 q)f:{6*7} q)f[] 42 q)f@ @[{6*7}] q)f@0 42 q)f@`a 42 q)f@"a" 42
@也可以用于字典、表、有关键字的表,对于字典和有关键字的表,@查找对应该关键字的记录,而非按下标查找,对于没关键字的表,才是用下标
q)d:`a`b`c!10 20 30 q)d@`b 20 q)t:([]c1:1 2 3; c2:`a`b`c) q)t@1 c1| 2 c2| `b q)kt:([k:`a`b`c]f:1.1 2.2 3.3) q)kt@`c f| 3.3 q)kt:([k:1 2 3]f:1.1 2.2 3.3) q)kt@0 f| q)kt@1 f| 1.1
Dot (.)
跟@的比较:
- @把左边的操作数当做一维列表、一元函数处理;而.把左边的操作数当做多维列表或者多元函数处理
- @把右边的操作数一一传递给左边操作数执行;而.把右边操作数当做整体一次传递给左边操作数执行
- @右边操作数可以是单个值也可以是一个列表;而.右边操作数必须是列表
q)L:(1 2;3 4 5;6) q)L@1 2 3 4 5 6 q)L . 1 2 5 q)f:{x*y} q)f . 2 3 6
如果操作数是名称或者常量,.需要在它们和.操作符之间加上空白(如空格)
q)L.1 2 'type q)L .1 2 'type q)L. 1 2 '. q)L . 1 2 5
右边参数必须是等大小同于维度或参数个数的列表
q)f:{x*x} q)f@2 4 q)f . 2 'type q)f . enlist 2 4
可以用空元素::来表示省略该处的下标(适用于列表而非函数)
q)m:(1 2 3;4 5 6) q)m . (::;1) 2 5 q)m[;1] 2 5
即便对于一元函数,只能是单元素的列表,不能像@一样用一个多元素的列表来调用函数多次
q)f . 2 4 'rank
对于无参数函数,同@类似,可以用任意值的单元素的列表
q)f:{6*7} q)f[] 42 q)f . enlist `a 42
.操作符可以用于复杂的组合实体包括列表、字典、表、有关键字的表。可以将这些组合实体看做映射来理解该操作,.操作符通过依次以右边操作数的每个元素当做关键字来查找映射并把结果当做下一步的左边操作数。
q)L:(1;2 3;`a`b!(4;5 6)) q)L . (2;`b;1) 6 q)L[2][`b][1] 6
Function Forms of Amend
@和.可以将任意函数应用到列表(包括表)的部分元素上,然后返回更后的列表。
@用于二元函数
@[L;I;f;y] L[I] f y f[L[I];y] (L@I) f y f[L@I;y]
上面这些写法都是等同的。下例中,下标为1和2的元素分别被加上了42和43
q)L:100 200 300 400 q)I:1 2 q)@[L;I;+;42 43] 100 242 343 400
:操作符可以用于替换其中的元素
q)@[L;I;:;42 43] 100 42 43 400
注意,原来的列表L并没有变更。如果想变更原来的列表,需要使用引用
q)L 100 200 300 400 q)@[`L;I;:;42] `L q)L 100 42 42 400
返回值是一个被变更的实体的名称的symbol形式,不是错误信息。
用于字典和表
q)d:`a`b`c!10 20 30 q)@[d;`a`c;+;9] a| 19 b| 20 c| 39 q)t:([] c1:`a`b`c; c2:10 20 30) q)@[t;0;:;(`aa;100)] c1 c2 ------ aa 100 b 20 c 30
@用于一元函数
@[L;I;f] f L[I] f[L[I]] f[L@I]
以上写法都是等同的。
q)L:101 102 103 q)I:0 2 q)@[L;I;neg] -101 102 -103 q)d:`a`b`c!10 20 30 q)@[d;`a`c;neg] a| -10 b| 20 c| -30
.用于二元函数
.跟@在这里的区别只在于它们是如何作用于原来的列表(例如@是按一维列表取值而.是按多维列表取值),其它部分基本一样,包括替换里面的值、用于字典/表、变更原来列表。
.[L;I;f;y] (L . I) f y f[L . I;y]
以上写法是等同的。
q)L:(100 200;300 400 500) q)I1:1 2 q)I2:(1;0 2) q).[L;I1;+;42] 100 200 300 400 542 q).[L;I2;+;42 43] 100 200 342 400 543
.用于一元函数
.[L;I;f] f[L . I]
以上写法是等同的。