隐式编程隐式(tacit)编程[1],或称为函数级编程,是一种编程范型,也叫做无点(point-free)样式。其中函数定义不标示所要运算的被称为“点”的参数,转而函数定义只是其他函数的复合,比如那些操纵参数的组合子。隐式编程有着理论价值,因为严格的使用复合导致程序适配于等式推理[2]。隐式编程是特定编程语言的天然样式,这包括了APL的一些现代实现和方言[3],和串接式语言比如Forth。由于缺少参数命名,认为这种风格导致了不必要的晦涩难懂的人,给它起了个绰号叫做“无意义”(pointless)风格[2]。 APL语言家族在一些APL方言中,允许将函数组合入服从几个规则的“列车”(train);这允许建立复杂的派生函数,而不需要显式指定任何参数;实现了列车的APL方言包括:J语言、Dyalog APL、dzaima/APL、ngn/apl和NARS2000[1]。 J语言在J语言中,下列在一个数值的列表(阵列)上计算平均值的函数采用了一种无参数样式代码: avg=: +/ % #
欧拉公式可隐式表达为: cos =: 2 o. ]
sin =: 1 o. ]
Euler =: ^@j. = cos j. sin
这里定义的函数 Dyalog APL上述相同的隐式计算用APL的现代版本Dyalog APL[4]表达为: avg ← +⌿ ÷ ≢
cos ← 2 ○ ⊢
sin ← 1 ○ ⊢
j ← {⍺←0 ⋄ ⍺+0j1×⍵} ⍝ j函数的定义不是隐式的
Euler ← *∘j = cos j sin
这里采用直接函数定义了 Unix管道在Unix shell脚本中,命令是从标准输入接收数据并发送结果至标准输出的过滤器。例如: sort | uniq -c | sort -rn
可以视为一个隐式或无点复合,它返回它的每个参数的计数和这些参数,并按照这个计数的递减次序来排序。命令 $ echo '2 4 3 1 3 12 2' | xargs -n1 | sort | uniq -c | sort -rn
2 3
2 2
1 4
1 12
1 1
Python如下Python代码是对应上节示例中Unix管道中命令的函数定义和一序列的运算: def sort(argv):
return sorted(argv, key=str)
def uniq_c(argv):
counts = {}
for i in argv:
counts[str(i)] = counts.get(str(i), 0) + 1
return [(v, int(k)) for k , v in counts.items()]
def sort_rn(argv):
sort_rk2 = sorted(argv, key=lambda x:str(x[1]), reverse=True)
return sorted(sort_rk2, key=lambda x:x[0], reverse=True)
a = [2, 4, 3, 1, 3, 12, 2]
b = sort_rn(uniq_c(sort(a)))
这里的 基于标准库的函数式编程工具 from functools import partial, reduce
def compose(*func_list):
return partial(reduce, lambda argv,func:func(argv), func_list)
pipeline = compose(sort, uniq_c, sort_rn)
c = pipeline(a)
assert b == c
jq语言jq是面向JSON的纯函数式编程语言,它使用 $ echo '[1,2]' | jq 'add/length'
1.5
$ jq -n '[1,2] | add/length'
1.5
这里jq内的JSON阵列 上述Python章节中例子,在jq中等价的无点风格定义为: def uniq_c:
map(tostring)
| reduce .[] as $i ({}; .[$i] += 1)
| to_entries | map([.value, .key]);
def sort_nr:
sort | reverse | map([.[0], (.[1]|tonumber)]);
[2, 4, 3, 1, 3, 12, 2]
| sort | uniq_c | sort_nr
将这段代码保存入 $ jq -nc -f ./pipeline.jq
[[2,3],[2,2],[1,4],[1,12],[1,1]]
函数式编程语言下面是采用纯函数式编程语言Haskell的一个简单例子,它在一个列表上作合计的函数。编程者可以使用“有点”(pointed)也称为值级编程的方法,而递归的定义这个合计为: sum (x:xs) = x + sum xs
sum [] = 0
这是一种折叠(fold)运算,编程者可以将它改写为: sum xs = foldr (+) 0 xs
这里的参数是不必须的,进而将它改写成如下“无点”也称为函数级编程的样式: sum = foldr (+) 0
(.) :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)
它有如下性质[6]: f . g = f(g)
f . g = (.) f g
f . g = (f .) g
f . g = (. g) f
这里的 下面的例子展示其用法,给出一个函数 p x y z = f (g x y) z
经过如下推导: p = \x -> \y -> \z -> f (g x y) z
= \x -> \y -> \z -> (f ((g x) y)) z
= \x -> \y -> f ((g x) y)
= \x -> \y -> (f . (g x)) y
= \x -> f . (g x)
= \x -> ((.) f) (g x)
= \x -> (((.) f) . g) x
= ((.) f) . g
它可以归约成无点的等价定义: p = ((.) f) . g
下面是一个复杂一些的例子[8],这里的 mf criteria operator list = filter criteria (map operator list)
经过如下推导: mf = \x -> \y -> \z -> filter x (map y z)
= \x -> \y -> \z -> (filter x) ((map y) z)
= \x -> \y -> \z -> (filter x) . (map y) z
= \x -> \y -> (filter x) . (map y)
= \x -> \y -> ((.) (filter x)) (map y)
= \x -> \y -> ((.) (filter x)) . map y
= \x -> ((.) (filter x)) . map
= \x -> (. map) ((.) (filter x))
= \x -> (. map) ((.) . filter x)
= \x -> (. map) . ((.) . filter) x
= (. map) . ((.) . filter)
= (. map) . (.) . filter
mf = (. map) . (.) . filter
串接式编程语言在串接式编程语言和面向堆栈编程语言中,无点方法也很常用。例如,计算斐波那契数的过程,在Unix的dc命令中可实现为: [d1<G]sF [d 1- lFx r 2- lFx +]sG lFx 下面是其用例: $ echo 7 | dc -e '? [d1<G]sF [d 1- lFx r 2- lFx +]sG lFx p'
13
: fib ( n -- Fn )
dup 1 > [
[ 1 - fib ] [ 2 - fib ] bi +
] when ;
在这两个例子中,递归调用函数的参数在堆栈中而没有被显式的命名。 参见注释和引用
外部链接
|
Portal di Ensiklopedia Dunia