隱式編程

隱式（tacit）編程，或稱為函數級編程，是一種編程範型，也叫做無點（point-free）樣式。其中函數定義不標示所要運算的被稱為“點”的參數，轉而函數定義只是其他函數的複合，比如那些操縱參數的組合子。隱式編程有著理論價值，因為嚴格的使用複合導致程序適配於等式推理。隱式編程是特定編程語言的天然樣式，這包括了APL的一些現代實現和方言，和串接式語言比如Forth。由於缺少參數命名，認為這種風格導致了不必要的晦澀難懂的人，給它起了個綽號叫做“無意義”（pointless）風格。

**APL語言家族**
在一些APL方言中，允許將函數組合入服從幾個規則的“列車”（train）；這允許建立複雜的派生函數，而不需要顯式指定任何參數；實現了列車的APL方言包括：J語言、Dyalog APL、dzaima/APL、ngn/apl和NARS2000。

**J語言**
在J語言中，下列在一個數值的列表（陣列）上計算平均值的函數採用了一種無參數樣式代碼：
avg=: +/ % #
+/通過將求和（+）插入（/）到一個陣列的所有元素之間來計算它們的合計值。#總計一個陣列的元素數目。%用+/這個陣列的結果值除以#這個陣列的結果值。
歐拉公式可隱式表達為：
cos =: 2 o. 
sin =: 1 o. 
Euler =: ^@j. = cos j. sin
這裡定義的函數Euler在任何輸入值上都恆等於1，即這個等式永遠為真。其中用到了一些原語（primitive）函數：=:表示全局定義；o.表示圓函數，由左側名詞參數選擇具體的函數；不變動的返回右側名詞參數；^的一元定義為指數函數；j.的一元定義為虛數單位0j1乘以右側參數y，而它的二元定義為x + 0j1*y，即組合左側參數x和右側參數y成為複數，而二者分別是其實部和虛部；@表示數學函數複合；=是等於布爾運算，兩側參數相等返回1而不等返回0。

**Dyalog APL**
上述相同的隱式計算用APL的現代版本Dyalog APL表達為：
avg ← +⌿ ÷ ≢
cos ← 2 ○ ⊢
sin ← 1 ○ ⊢
j   ← {⍺←0 ⋄ ⍺+0j1×⍵}  ⍝ j函數的定義不是隱式的
Euler ← *∘j = cos j sin
這裡採用Direct function定義了j函數，其中在{與}之間由⋄分隔的是守衛的表達式序列（這裡只有表達式），⍺指示左參數而⍵指示右參數，⍺←指示一元定義需要的缺省左參數。

**Unix管道**
在Unix shell腳本中，命令是從標準輸入接收數據併發送結果至標準輸出的過濾器。例如：
sort | uniq -c | sort -rn
可以視為一個隱式或無點複合，它返回它的每個參數的計數和這些參數，並按照這個計數的遞減次序來排序。命令sort和uniq相當於函數，而-c和-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:
countsstr(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(x1), reverse=True)
return sorted(sort_rk2, key=lambda x:x0, reverse=True)
a = 2, 4, 3, 1, 3, 12, 2
b = sort_rn(uniq_c(sort(a)))    
這裡的sort()由於uniq_c()所採用的實現方法不再依賴於它而沒有存在必要，只是為了與上節Unix示例保持一致。
基於標準庫的函數式編程工具functools中的Partial application函數partial()和歸約函數reduce()，這個例子可以寫為無點樣式的沒有參數的一序列函數的複合：
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陣列1,2是求值為陣列的一個jq過濾器。儘管類似於Unix流水線，jq流水線允許將到來數據，如同並行的發送到在|右手端的多於一個接收者。
上述Python章節中例子，在jq中等價的無點風格定義為：
def uniq_c:
map(tostring) 

reduce . as $i ({}; .$i += 1)

map(.value, .key);
def sort_nr:
sort | reverse | map(.0, (.1|tonumber));
2, 4, 3, 1, 3, 12, 2 
sort_nr
將這段代碼保存入pipeline.jq文件中，下面是其執行結果：
$ 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
Haskell擁有函數複合算子：
(.) :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)
它有如下性質：
f . g = f(g)
f . g = (.) f g
f . g = (f .) g 
f . g = (. g) f
這裡的(f .)和(. g)，分別稱為.的左“節”（section）和右“節”，是對這個中綴算子的左側和右側Partial application。(f .) = ((.) f)，本文采用後者形式。正如前面所說，在“無點”中的點（point）指稱參數，而非不使用點號（dot），這是常見誤解。
下面的例子展示其用法，給出一個函數p的定義：
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
下面是一個複雜一些的例子，這裡的mf是一個先做映射（map）再加過濾器（filter）的函數，它接受一個列表list，向它應用一個函數operator，接著基於一個準則criteria來過濾元素：
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可以表達為無點樣式：
mf = (. map) . (.) . filter

**串接式編程語言**
在串接式編程語言和麵向堆棧編程語言中，無點方法也很常用。例如，計算斐波那契數的過程，在Unix的dc命令中可實現為：
d1
下面是其用例：
$ echo 7 | dc -e '? d1
在Factor中可與其等價的實現為：
 fib ( n -- Fn )
dup 1 > 
1 - fib   2 - fib  bi +
when ;
在這兩個例子中，遞歸調用函數的參數在堆棧中而沒有被顯式的命名。

**參見**
 組合子邏輯- 串接式編程語言- 函數級編程

**註釋和引用**

**外部鏈接**
  From Function-Level Programming to Pointfree Style-  Pure Functions in APL and J How to use tacit programming in any APL-like language-  Closed applicative languages 1971 - 1976 ff date=20200207055523 , in John W. Backus (Publications)