repo: github.com/alphardex/p…
參考repo:github.com/hemanth/fun…
Arity - 函式引數個數
import inspect
add = lambda a, b: a + b
len(inspect.getfullargspec(add).args)
# 2
複製程式碼
Higher-Order Function - 高階函式
以函式為引數或返回值
is_type = lambda type_: lambda x: isinstance(x, type_)
li = [0, '1', 2, None]
[l for l in li if is_type(int)(l)]
# [0, 2]
複製程式碼
Closure - 閉包
閉包是一種在變數作用域之外訪問變數的方法。是一種將函式儲存在環境中的方法。
閉包是一個作用域,它捕獲函式的區域性變數以便訪問,即使在執行已經移出定義它的塊之後也是如此。
add_to = lambda x: lambda y: x + y
add_to_five = add_to(5)
add_to_five(3)
# 8
複製程式碼
函式addTo()返回一個函式(內部稱為add()),將它儲存在名為addToFive的變數中,它帶有引數為5的柯里化呼叫。
理想情況下,當函式addTo完成執行時,其作用域與區域性變數add,x,y就變得不可訪問。 但是,它在呼叫addToFive()時返回8。 這意味著即使在程式碼塊完成執行後也會儲存函式addTo的狀態,否則無法知道addTo被呼叫為addTo(5)並且x的值被設定為5。
詞法作用域範圍是它能夠找到x和add的值的原因 - 已經完成執行的父項的私有變數。該值稱為閉包。
堆疊以及函式的詞法範圍以對父項的引用形式儲存。這可以防止關閉和底層變數被垃圾收集(因為至少有一個對它的實時引用)。
閉包是一種通過引用其主體外部的欄位來包圍其周圍狀態的函式。封閉狀態保持在閉包的呼叫之間。
Partial Function - 偏函式
通過對原始函式預設引數來建立一個新的函式
from functools import partial
add3 = lambda a, b, c: a + b + c
five_plus = partial(add3, 2, 3)
five_plus(4)
# 9
複製程式碼
Currying - 柯里化
將一個多元函式轉變為一元函式的過程
add = lambda a, b: a + b
curried_add = lambda a: lambda b: a + b
curried_add(3)(4)
# 7
add2 = curried_add(2)
add2(10)
# 12
複製程式碼
Auto Currying - 自動柯里化
from toolz import curry
add = lambda a, b: a + b
curried_add = curry(add)
curried_add(1, 2)
# 3
curried_add(1)(2)
# 3
curried_add(1)
# <function <lambda> at 0x000002088BBD5E18>
複製程式碼
Function Composition - 函式組合
接收多個函式作為引數,從右到左,一個函式的輸入為另一個函式的輸出
import math
from functools import reduce
# 組合2個函式
compose = lambda f, g: lambda a: f(g(a))
# 組合多個函式
compose = lambda *funcs: reduce(lambda f, g: lambda *args: f(g(*args)), funcs)
floor_and_to_string = compose(str, math.floor)
floor_and_to_string(12.12)
# '12'
複製程式碼
Purity - 純函式
輸出僅由輸入決定,且不產生副作用
greet = lambda name: f'hello, {name}'
greet('world')
'hello, world'
複製程式碼
以下程式碼不是純函式
# 情況1:函式依賴全域性變數
NAME = 'alphardex'
greet = lambda: f'hi, {NAME}'
greet()
# 'hi, alphardex'
# 情況2:函式修改了全域性變數
greeting = None
def greet(name):
global greeting
greeting = f'hi, {name}'
greet('alphardex')
greeting
# 'hi, alphardex'
複製程式碼
Side effects - 副作用
如果函式與外部可變狀態進行互動,則它是有副作用的
最典型的例子是建立日期和IO
from datetime import datetime
different_every_time = datetime.now()
different_every_time
# datetime.datetime(2019, 4, 20, 17, 30, 24, 824876)
different_every_time = datetime.now()
different_every_time
# datetime.datetime(2019, 4, 20, 17, 31, 41, 204302)
複製程式碼
Idempotent - 冪等性
如果一個函式執行多次皆返回相同的結果,則它是冪等性的
abs(abs(abs(10)))
# 10
複製程式碼
Point-Free Style - Point-Free 風格
定義函式時,不顯式地指出函式所帶引數,這種風格通常需要柯里化或者高階函式
Point-Free風格的函式就像平常的賦值,不使用def或者lambda關鍵詞
map_ = lambda func: lambda li: [func(l) for l in li]
add = lambda a: lambda b: a + b
increment_all = map_(add(1))
numbers = [1, 2, 3]
increment_all(numbers)
# [2, 3, 4]
複製程式碼
Predicate - 謂詞
根據輸入返回 True 或 False。常用於filter函式中
filter函式亦可以用列表推導式的if判斷實現
above_two = lambda a: a > 2
li = [1, 2, 3, 4]
[l for l in li if above_two(l)]
# [3, 4]
複製程式碼
Contracts - 契約
契約保證了函式或者表示式在執行時的行為。當違反契約時,將丟擲一個錯誤。
def contract(input):
if isinstance(input, int):
return True
raise Exception('Contract Violated: expected int -> int')
add_one = lambda num: contract(num) and num + 1
add_one(2)
# 3
add_one('hello')
# Exception Traceback
複製程式碼
Functor - 函子
一個實現了map函式的物件,map會遍歷物件中的每個值並生成一個新的物件。
Python中最具代表性的函子就是list, 因為它遵守因子的兩個準則
在Python中可以用列表推導式來代表map操作
Preserves identity - 一致性
li = [1, 2, 3]
[l for l in li] == li
# True
複製程式碼
Composable - 組合性
li = [1, 2, 3]
compose = lambda f, g: lambda a: f(g(a))
[compose(str, lambda x: x+1)(l) for l in li]
# ['2', '3', '4']
[str(l+1) for l in li]
# ['2', '3', '4']
複製程式碼
Referential Transparency - 引用透明性
一個表示式能夠被它的值替代而不改變程式的行為成為引用透明
greet = lambda: 'hello, world.'
複製程式碼
Lazy evaluation - 惰性求值
按需求值機制,只有當需要計算所得值時才會計算
Python中可用生成器實現
import random
def rand():
while True:
yield random.random()
rand_iter = rand()
next(rand())
# 0.16066473752585098
複製程式碼
Monoid - 單位半群
一個物件擁有一個函式用來連線相同型別的物件
數值加法是一個簡單的Monoid
1 + 1
# 2
複製程式碼
以上例子中,數值是物件,而+是函式
以下能更清晰地說明它
from operator import add
type(1)
# <class 'int'>
add(1, 1)
# 2
複製程式碼
數值是int類的例項物件,add是實現了加法的函式
與另一個值結合而不會改變它的值必須存在,稱為identity
。
加法的identity值為 0:
1 + 0
# 1
複製程式碼
需要滿足結合律
1 + (2 + 3) == (1 + 2) + 3
# True
複製程式碼
list的結合也是Monoid
[1, 2].extend([3, 4])
複製程式碼
identity值為空陣列
[1, 2].extend([])
複製程式碼
identity與compose函式能夠組成monoid
identity = lambda a: a
compose = lambda f, g: lambda a: f(g(a))
foo = lambda bar: bar + 1
compose(foo, identity)(1) == compose(identity, foo)(1) == foo(1)
# True
複製程式碼
Monad - 單子
擁有of
和chain
函式的物件。chain
很像map
,除了用來鋪平巢狀資料。
flatten = lambda li: sum(li, [])
of = lambda *args: list(args)
chain = lambda func: lambda li: list(flatten([func(l) for l in li]))
[s.split(',') for s in of('cat,dog', 'fish,bird')]
# [['cat', 'dog'], ['fish', 'bird']]
chain(lambda s: s.split(','))(of('cat,dog', 'fish,bird'))
# ['cat', 'dog', 'fish', 'bird']
複製程式碼
Comonad - 餘單子
擁有extract
與extend
函式的物件。
class CoIdentity:
def __init__(self, v):
self.val = v
def extract(self):
return self.val
def extend(self, func):
return CoIdentity(func(self))
CoIdentity(1).extract()
1
from beeprint import pp
pp(CoIdentity(1).extend(lambda x: x.extract() + 1))
# instance(CoIdentity):
# val: 2
複製程式碼
Morphism - 態射
一個變形的函式
Endomorphism - 自同態
輸入輸出是相同型別的函式
uppercase = lambda string: string.upper()
uppercase('hello')
# 'HELLO'
decrement = lambda number: number - 1
decrement(2)
# 1
複製程式碼
Isomorphism - 同構
不同型別物件的變形,保持結構並且不丟失資料。
例如,一個二維座標既可以表示為列表[2, 3]
,也可以表示為字典{'x': 2, 'y': 3}
。
pair_to_coords = lambda pair: {'x': pair[0], 'y': pair[1]}
coords_to_pair = lambda coords: [coords['x'], coords['y']]
pair_to_coords(coords_to_pair({'x': 1, 'y': 2}))
#{'x': 1, 'y': 2}
複製程式碼
Setoid
擁有equals
函式的物件。equals
可以用來和其它物件比較。
Python裡的==
就是equals
函式
[1, 2] == [1, 2]
# True
[1, 2] == [3, 4]
# False
複製程式碼
Semigroup - 半群
擁有concat
函式的物件。concat
可以連線相同型別的兩個物件。
Python裡列表的extend
就是concat
函式
li = [1]
li.extend([2])
li
# [1, 2]
複製程式碼
Foldable
一個擁有reduce
函式的物件。reduce
可以把一種型別的物件轉化為另一種型別。
from functools import reduce
sum_ = lambda li: reduce(lambda acc, val: acc + val, li, 0)
sum_([1, 2, 3])
6
複製程式碼
Type Signatures - 型別簽名
通常可以在註釋中指出引數與返回值的型別
# add :: int -> int -> int
add = lambda x: lambda y: x + y
# increment :: int -> int
increment = lambda x: x + 1
複製程式碼
如果函式的引數也是函式,那麼這個函式需要用括號括起來
# call :: (a -> b) -> a -> b
call = lambda func: lambda x: func(x)
複製程式碼
字元a, b, c, d表明引數可以是任意型別。以下版本的map
的引數func,把一種型別a的陣列轉化為另一種型別b的陣列
# map :: (a -> b) -> [a] -> [b]
map_ = lambda func: lambda li: [func(l) for l in li]
複製程式碼