
用Python進(jìn)行基礎(chǔ)的函數(shù)式編程的教程
許多函數(shù)式文章講述的是組合,流水線和高階函數(shù)這樣的抽象函數(shù)式技術(shù)。本文不同,它展示了人們每天編寫(xiě)的命令式,非函數(shù)式代碼示例,以及將這些示例轉(zhuǎn)換為函數(shù)式風(fēng)格。
文章的第一部分將一些短小的數(shù)據(jù)轉(zhuǎn)換循環(huán)重寫(xiě)成函數(shù)式的maps和reduces。第二部分選取長(zhǎng)一點(diǎn)的循環(huán),把他們分解成單元,然后把每個(gè)單元改成函數(shù)式的。第三部分選取一個(gè)很長(zhǎng)的連續(xù)數(shù)據(jù)轉(zhuǎn)換循環(huán),然后把它分解成函數(shù)式流水線。
示例都是用Python寫(xiě)的,因?yàn)楹芏嗳擞X(jué)得Python易讀。為了證明函數(shù)式技術(shù)對(duì)許多語(yǔ)言來(lái)說(shuō)都相同,許多示例避免使用Python特有的語(yǔ)法:map,reduce,pipeline。
導(dǎo)引
當(dāng)人們談?wù)摵瘮?shù)式編程,他們會(huì)提到非常多的“函數(shù)式”特性。提到不可變數(shù)據(jù)1,第一類(lèi)對(duì)象2以及尾調(diào)用優(yōu)化3。這些是幫助函數(shù)式編程的語(yǔ)言特征。提到mapping(映射),reducing(歸納),piplining(管道),recursing(遞歸),currying4(科里化);以及高階函數(shù)的使用。這些是用來(lái)寫(xiě)函數(shù)式代碼的編程技術(shù)。提到并行5,惰性計(jì)算6以及確定性。這些是有利于函數(shù)式編程的屬性。
忽略全部這些??梢杂靡痪湓拋?lái)描述函數(shù)式代碼的特征:避免副作用。它不會(huì)依賴也不會(huì)改變當(dāng)前函數(shù)以外的數(shù)據(jù)。所有其他的“函數(shù)式”的東西都源于此。當(dāng)你學(xué)習(xí)時(shí)把它當(dāng)做指引。
這是一個(gè)非函數(shù)式方法:
a = 0
def increment1():
global a
a += 1
這是一個(gè)函數(shù)式的方法:
def increment2(a):
return a + 1
不要在lists上迭代。使用map和reduce。
Map(映射)
Map接受一個(gè)方法和一個(gè)集合作為參數(shù)。它創(chuàng)建一個(gè)新的空集合,以每一個(gè)集合中的元素作為參數(shù)調(diào)用這個(gè)傳入的方法,然后把返回值插入到新創(chuàng)建的集合中。最后返回那個(gè)新集合。
這是一個(gè)簡(jiǎn)單的map,接受一個(gè)存放名字的list,并且返回一個(gè)存放名字長(zhǎng)度的list:
name_lengths = map(len, ["Mary", "Isla", "Sam"])
print name_lengths
# => [4, 4, 3]
接下來(lái)這個(gè)map將傳入的collection中每個(gè)元素都做平方操作:
squares = map(lambda x: x * x, [0, 1, 2, 3, 4])
print squares
# => [0, 1, 4, 9, 16]
這個(gè)map并沒(méi)有使用一個(gè)命名的方法。它是使用了一個(gè)匿名并且內(nèi)聯(lián)的用lambda定義的方法。lambda的參數(shù)定義在冒號(hào)左邊。方法主體定義在冒號(hào)右邊。返回值是方法體運(yùn)行的結(jié)果。
下面的非函數(shù)式代碼接受一個(gè)真名列表,然后用隨機(jī)指定的代號(hào)來(lái)替換真名。
import random
names = ['Mary', 'Isla', 'Sam']
code_names = ['Mr. Pink', 'Mr. Orange', 'Mr. Blonde']
for i in range(len(names)):
names[i] = random.choice(code_names)
print names
# => ['Mr. Blonde', 'Mr. Blonde', 'Mr. Blonde']
(正如你所見(jiàn)的,這個(gè)算法可能會(huì)給多個(gè)密探同一個(gè)秘密代號(hào)。希望不會(huì)在任務(wù)中混淆。)
這個(gè)可以用map重寫(xiě):
import random
names = ['Mary', 'Isla', 'Sam']
secret_names = map(lambda x: random.choice(['Mr. Pink',
'Mr. Orange',
'Mr. Blonde']),
names)
練習(xí)1.嘗試用map重寫(xiě)下面的代碼。它接受由真名組成的list作為參數(shù),然后用一個(gè)更加穩(wěn)定的策略產(chǎn)生一個(gè)代號(hào)來(lái)替換這些名字。
names = ['Mary', 'Isla', 'Sam']
for i in range(len(names)):
names[i] = hash(names[i])
print names
# => [6306819796133686941, 8135353348168144921, -1228887169324443034]
(希望密探記憶力夠好,不要在執(zhí)行任務(wù)時(shí)把代號(hào)忘記了。)
我的解決方案:
names = ['Mary', 'Isla', 'Sam']
secret_names = map(hash, names)
Reduce(迭代)
Reduce 接受一個(gè)方法和一個(gè)集合做參數(shù)。返回通過(guò)這個(gè)方法迭代容器中所有元素產(chǎn)生的結(jié)果。
這是個(gè)簡(jiǎn)單的reduce。返回集合中所有元素的和。
sum = reduce(lambda a, x: a + x, [0, 1, 2, 3, 4])
print sum
# => 10
x是迭代的當(dāng)前元素。a是累加和也就是在之前的元素上執(zhí)行l(wèi)ambda返回的值。reduce()遍歷元素。每次迭代,在當(dāng)前的a和x上執(zhí)行l(wèi)ambda然后返回結(jié)果作為下一次迭代的a。
第一次迭代的a是什么?在這之前沒(méi)有迭代結(jié)果傳進(jìn)來(lái)。reduce() 使用集合中的第一個(gè)元素作為第一次迭代的a,然后從第二個(gè)元素開(kāi)始迭代。也就是說(shuō),第一個(gè)x是第二個(gè)元素。
這段代碼記'Sam'這個(gè)詞在字符串列表中出現(xiàn)的頻率:
sentences = ['Mary read a story to Sam and Isla.',
'Isla cuddled Sam.',
'Sam chortled.']
sam_count = 0
for sentence in sentences:
sam_count += sentence.count('Sam')
print sam_count
# => 3
下面這個(gè)是用reduce寫(xiě)的:
sentences = ['Mary read a story to Sam and Isla.',
'Isla cuddled Sam.',
'Sam chortled.']
sam_count = reduce(lambda a, x: a + x.count('Sam'),
sentences,
0)
這段代碼如何初始化a?出現(xiàn)‘Sam'的起始點(diǎn)不能是'Mary read a story to Sam and Isla.' 初始的累加和由第三個(gè)參數(shù)來(lái)指定。這樣就允許了集合中元素的類(lèi)型可以與累加器不同。
為什么map和reduce更好?
首先,它們大多是一行代碼。
二、迭代中最重要的部分:集合,操作和返回值,在所有的map和reduce中總是在相同的位置。
三、循環(huán)中的代碼可能會(huì)改變之前定義的變量或之后要用到的變量。照例,map和reduce是函數(shù)式的。
四、map和reduce是元素操作。每次有人讀到for循環(huán),他們都要逐行讀懂邏輯。幾乎沒(méi)有什么規(guī)律性的結(jié)構(gòu)可以幫助理解代碼。相反,map和reduce都是創(chuàng)建代碼塊來(lái)組織復(fù)雜的算法,并且讀者也能非常快的理解元素并在腦海中抽象出來(lái)?!班?,代碼在轉(zhuǎn)換集合中的每一個(gè)元素。然后結(jié)合處理的數(shù)據(jù)成一個(gè)輸出。”
五、map和reduce有許多提供便利的“好朋友”,它們是基本行為的修訂版。例如filter,all,any以及find。
練習(xí)2。嘗試用map,reduce和filter重寫(xiě)下面的代碼。Filter接受一個(gè)方法和一個(gè)集合。返回集合中使方法返回true的元素。
people = [{'name': 'Mary', 'height': 160},
{'name': 'Isla', 'height': 80},
{'name': 'Sam'}]
height_total = 0
height_count = 0
for person in people:
if 'height' in person:
height_total += person['height']
height_count += 1
if height_count > 0:
average_height = height_total / height_count
print average_height
# => 120
如果這個(gè)比較棘手,試著不要考慮數(shù)據(jù)上的操作。考慮下數(shù)據(jù)要經(jīng)過(guò)的狀態(tài),從people字典列表到平均高度。不要嘗試把多個(gè)轉(zhuǎn)換捆綁在一起。把每一個(gè)放在獨(dú)立的一行,并且把結(jié)果保存在命名良好的變量中。代碼可以運(yùn)行后,立刻凝練。
我的方案:
people = [{'name': 'Mary', 'height': 160},
{'name': 'Isla', 'height': 80},
{'name': 'Sam'}]
heights = map(lambda x: x['height'],
filter(lambda x: 'height' in x, people))
if len(heights) > 0:
from operator import add
average_height = reduce(add, heights) / len(heights)
寫(xiě)聲明式代碼,而不是命令式
下面的程序演示三輛車(chē)比賽。每次移動(dòng)時(shí)間,每輛車(chē)可能移動(dòng)或者不動(dòng)。每次移動(dòng)時(shí)間程序會(huì)打印到目前為止所有車(chē)的路徑。五次后,比賽結(jié)束。
下面是某一次的輸出:
-
--
--
--
--
---
---
--
---
----
---
----
----
----
-----
這是程序:
from random import random
time = 5
car_positions = [1, 1, 1]
while time:
# decrease time
time -= 1
print ''
for i in range(len(car_positions)):
# move car
if random() > 0.3:
car_positions[i] += 1
# draw car
print '-' * car_positions[i]
代碼是命令式的。一個(gè)函數(shù)式的版本應(yīng)該是聲明式的。應(yīng)該描述要做什么,而不是怎么做。
使用方法
通過(guò)綁定代碼片段到方法里,可以使程序更有聲明式的味道。
from random import random
def move_cars():
for i, _ in enumerate(car_positions):
if random() > 0.3:
car_positions[i] += 1
def draw_car(car_position):
print '-' * car_position
def run_step_of_race():
global time
time -= 1
move_cars()
def draw():
print ''
for car_position in car_positions:
draw_car(car_position)
time = 5
car_positions = [1, 1, 1]
while time:
run_step_of_race()
draw()
想要理解這段代碼,讀者只需要看主循環(huán)?!比绻鹴ime不為0,運(yùn)行下run_step_of_race和draw,在檢查下time?!叭绻x者想更多的理解這段代碼中的run_step_of_race或draw,可以讀方法里的代碼。
注釋沒(méi)有了。代碼是自描述的。
把代碼分解提煉進(jìn)方法里是非常好且十分簡(jiǎn)單的提高代碼可讀性的方法。
這個(gè)技術(shù)用到了方法,但是只是當(dāng)做常規(guī)的子方法使用,只是簡(jiǎn)單地將代碼打包。根據(jù)指導(dǎo),這些代碼不是函數(shù)式的。代碼中的方法使用了狀態(tài),而不是傳入?yún)?shù)。方法通過(guò)改變外部變量影響了附近的代碼,而不是通過(guò)返回值。為了搞清楚方法做了什么,讀者必須仔細(xì)閱讀每行。如果發(fā)現(xiàn)一個(gè)外部變量,必須找他它的出處,找到有哪些方法修改了它。
移除狀態(tài)
下面是函數(shù)式的版本:
from random import random
def move_cars(car_positions):
return map(lambda x: x + 1 if random() > 0.3 else x,
car_positions)
def output_car(car_position):
return '-' * car_position
def run_step_of_race(state):
return {'time': state['time'] - 1,
'car_positions': move_cars(state['car_positions'])}
def draw(state):
print ''
print 'n'.join(map(output_car, state['car_positions']))
def race(state):
draw(state)
if state['time']:
race(run_step_of_race(state))
race({'time': 5,
'car_positions': [1, 1, 1]})
代碼仍然是分割提煉進(jìn)方法中,但是這個(gè)方法是函數(shù)式的。函數(shù)式方法有三個(gè)標(biāo)志。首先,沒(méi)有共享變量。time和car_positions直接傳進(jìn)方法race中。第二,方法接受參數(shù)。第三,方法里沒(méi)有實(shí)例化變量。所有的數(shù)據(jù)變化都在返回值中完成。rece()
使用run_step_of_race() 的結(jié)果進(jìn)行遞歸。每次一個(gè)步驟會(huì)產(chǎn)生一個(gè)狀態(tài),這個(gè)狀態(tài)會(huì)直接傳進(jìn)下一步中。
現(xiàn)在,有兩個(gè)方法,zero() 和 one():
def zero(s):
if s[0] == "0":
return s[1:]
def one(s):
if s[0] == "1":
return s[1:]
zero() 接受一個(gè)字符串 s 作為參數(shù),如果第一個(gè)字符是'0′ ,方法返回字符串的其他部分。如果不是,返回None,Python的默認(rèn)返回值。one() 做的事情相同,除了第一個(gè)字符要求是'1′。
想象下一個(gè)叫做rule_sequence()的方法。接受一個(gè)string和一個(gè)用于存放zero()和one()模式的規(guī)則方法的list。在string上調(diào)用第一個(gè)規(guī)則。除非返回None,不然它會(huì)繼續(xù)接受返回值并且在string上調(diào)用第二個(gè)規(guī)則。除非返回None,不然它會(huì)接受返回值,并且調(diào)用第三個(gè)規(guī)則。等等。如果有哪一個(gè)規(guī)則返回None,rule_sequence()方法停止,并返回None。不然,返回最后一個(gè)規(guī)則方法的返回值。
下面是一個(gè)示例輸出:
print rule_sequence('0101', [zero, one, zero])
# => 1
print rule_sequence('0101', [zero, zero])
# => None
This is the imperative version of rule_sequence():
這是一個(gè)命令式的版本:
def rule_sequence(s, rules):
for rule in rules:
s = rule(s)
if s == None:
break
return s
練習(xí)3。上面的代碼用循環(huán)來(lái)完成功能。用遞歸重寫(xiě)使它更有聲明式的味道。
我的方案:
def rule_sequence(s, rules):
if s == None or not rules:
return s
else:
return rule_sequence(rules[0](s), rules[1:])
使用流水線
在之前的章節(jié),一些命令式的循環(huán)被重寫(xiě)成遞歸的形式,并被用以調(diào)用輔助方法。在本節(jié)中,會(huì)用pipline技術(shù)重寫(xiě)另一種類(lèi)型的命令式循環(huán)。
下面有個(gè)存放三個(gè)子典型數(shù)據(jù)的list,每個(gè)字典存放一個(gè)樂(lè)隊(duì)相關(guān)的三個(gè)鍵值對(duì):姓名,不準(zhǔn)確的國(guó)籍和激活狀態(tài)。format_bands方法循環(huán)處理這個(gè)list。
bands = [{'name': 'sunset rubdown', 'country': 'UK', 'active': False},
{'name': 'women', 'country': 'Germany', 'active': False},
{'name': 'a silver mt. zion', 'country': 'Spain', 'active': True}]
def format_bands(bands):
for band in bands:
band['country'] = 'Canada'
band['name'] = band['name'].replace('.', '')
band['name'] = band['name'].title()
format_bands(bands)
print bands
# => [{'name': 'Sunset Rubdown', 'active': False, 'country': 'Canada'},
# {'name': 'Women', 'active': False, 'country': 'Canada' },
# {'name': 'A Silver Mt Zion', 'active': True, 'country': 'Canada'}]
擔(dān)心源于方法的名稱(chēng)。”format”
是一個(gè)很模糊的詞。仔細(xì)查看代碼,這些擔(dān)心就變成抓狂了。循環(huán)中做三件事。鍵值為'country'的值被設(shè)置為'Canada'。名稱(chēng)中的標(biāo)點(diǎn)符號(hào)被移除了。名稱(chēng)首字母改成了大寫(xiě)。但是很難看出這段代碼的目的是什么,是否做了它看上去所做的。并且代碼難以重用,難以測(cè)試和并行。
和下面這段代碼比較一下:
print pipeline_each(bands, [set_canada_as_country,
strip_punctuation_from_name,
capitalize_names])
這段代碼很容易理解。它去除了副作用,輔助方法是函數(shù)式的,因?yàn)樗鼈兛瓷先ナ擎溤谝黄鸬?。上次的輸出?gòu)成下個(gè)方法的輸入。如果這些方法是函數(shù)式的,那么就很容易核實(shí)。它們很容易重用,測(cè)試并且也很容易并行。
pipeline_each()的工作是傳遞bands,一次傳一個(gè),傳到如set_cannada_as_country()這樣的轉(zhuǎn)換方法中。當(dāng)所有的bands都調(diào)用過(guò)這個(gè)方法之后,pipeline_each()將轉(zhuǎn)換后的bands收集起來(lái)。然后再依次傳入下一個(gè)方法中。
我們來(lái)看看轉(zhuǎn)換方法。
def assoc(_d, key, value):
from copy import deepcopy
d = deepcopy(_d)
d[key] = value
return d
def set_canada_as_country(band):
return assoc(band, 'country', "Canada")
def strip_punctuation_from_name(band):
return assoc(band, 'name', band['name'].replace('.', ''))
def capitalize_names(band):
return assoc(band, 'name', band['name'].title())
每一個(gè)都將band的一個(gè)key聯(lián)系到一個(gè)新的value上。在不改變?cè)档那闆r下是很難做到的。assoc()通過(guò)使用deepcopy()根據(jù)傳入的dictionary產(chǎn)生一個(gè)拷貝來(lái)解決這個(gè)問(wèn)題。每個(gè)轉(zhuǎn)換方法修改這個(gè)拷貝,然后將這個(gè)拷貝返回。
似乎這樣就很好了。原始Band字典不再擔(dān)心因?yàn)槟硞€(gè)鍵值需要關(guān)聯(lián)新的值而被改變。但是上面的代碼有兩個(gè)潛在的副作用。在方法strip_punctuation_from_name()中,未加標(biāo)點(diǎn)的名稱(chēng)是通過(guò)在原值上調(diào)用replace()方法產(chǎn)生的。在capitalize_names()方法中,將名稱(chēng)的首字母大寫(xiě)是通過(guò)在原值上調(diào)用title()產(chǎn)生的。如果replace()和title()不是函數(shù)式的,strip_punctuation_from_name()和capitalize_names()也就不是函數(shù)式的。
幸運(yùn)的是,replace() 和 title()并不改變它們所操作的string。因?yàn)镻ython中的strings是不可變的。例如,當(dāng)replace()操作band的名稱(chēng)字符串時(shí),是先拷貝原字符串,然后對(duì)拷貝的字符串做修改。嘖嘖。
Python中string和dictionaries的可變性比較闡述了類(lèi)似Clojure這類(lèi)語(yǔ)言的吸引力。程序員永遠(yuǎn)不用擔(dān)心數(shù)據(jù)是否可變。數(shù)據(jù)是不可變的。
練習(xí)4。試著重寫(xiě)pipeline_each方法??紤]操作的順序。每次從數(shù)組中拿出一個(gè)bands傳給第一個(gè)轉(zhuǎn)換方法。然后類(lèi)似的再傳給第二個(gè)方法。等等。
My solution:
我的方案:
def pipeline_each(data, fns):
return reduce(lambda a, x: map(x, a),
fns,
data)
所有的三個(gè)轉(zhuǎn)換方法歸結(jié)于對(duì)傳入的band的特定字段進(jìn)行更改。call()可以用來(lái)抽取這個(gè)功能。call接受一個(gè)方法做參數(shù)來(lái)調(diào)用,以及一個(gè)值的鍵用來(lái)當(dāng)這個(gè)方法的參數(shù)。
set_canada_as_country = call(lambda x: 'Canada', 'country')
strip_punctuation_from_name = call(lambda x: x.replace('.', ''), 'name')
capitalize_names = call(str.title, 'name')
print pipeline_each(bands, [set_canada_as_country,
strip_punctuation_from_name,
capitalize_names])
或者,如果我們希望能滿足簡(jiǎn)潔方面的可讀性,那么就:
print pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
call(lambda x: x.replace('.', ''), 'name'),
call(str.title, 'name')])
call()的代碼:
def assoc(_d, key, value):
from copy import deepcopy
d = deepcopy(_d)
d[key] = value
return d
def call(fn, key):
def apply_fn(record):
return assoc(record, key, fn(record.get(key)))
return apply_fn
There is a lot going on here. Let's take it piece by piece.
這段代碼做了很多事。讓我們一點(diǎn)一點(diǎn)的看。
一、call() 是一個(gè)高階函數(shù)。高階函數(shù)接受一個(gè)函數(shù)作為參數(shù),或者返回一個(gè)函數(shù)。或者像call(),兩者都有。
二、apply_fn() 看起來(lái)很像那三個(gè)轉(zhuǎn)換函數(shù)。它接受一個(gè)record(一個(gè)band),查找在record[key]位置的值,以這個(gè)值為參數(shù)調(diào)用fn,指定fn的結(jié)果返回到record的拷貝中,然后返回這個(gè)拷貝。
三、call()
沒(méi)有做任何實(shí)際的工作。當(dāng)call被調(diào)用時(shí),apply_fn()會(huì)做實(shí)際的工作。上面使用pipeline_each()的例子中,一個(gè)apply_fn()的實(shí)例會(huì)將傳入的band的country值改為”Canada“。另一個(gè)實(shí)例會(huì)將傳入的band的名稱(chēng)首字母大寫(xiě)。
四、當(dāng)一個(gè)apply_fn()
實(shí)例運(yùn)行時(shí),fn和key將不再作用域中。它們既不是apply_fn()的參數(shù),也不是其中的本地變量。但是它們?nèi)匀豢梢员辉L問(wèn)。當(dāng)一個(gè)方法被定義時(shí),方法會(huì)保存方法所包含的變量的引用:那些定義在方法的作用域外,卻在方法中使用的變量。當(dāng)方法運(yùn)行并且代碼引用一個(gè)變量時(shí),Python會(huì)查找本地和參數(shù)中的變量。如果沒(méi)找到,就會(huì)去找閉包內(nèi)保存的變量。那就是找到fn和key的地方。
五、在call()代碼中沒(méi)有提到bands。因?yàn)椴还苤黝}是什么,call()都可以為任何程序生成pipeline。函數(shù)式編程部分目的就是構(gòu)建一個(gè)通用,可重用,可組合的函數(shù)庫(kù)。
干的漂亮。閉包,高階函數(shù)和變量作用域都被包含在段落里。喝杯檸檬水。
還需要在band上做一點(diǎn)處理。就是移除band上除了name和country之外的東西。extract_name_and_country()能拉去這樣的信息。
def extract_name_and_country(band):
plucked_band = {}
plucked_band['name'] = band['name']
plucked_band['country'] = band['country']
return plucked_band
print pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
call(lambda x: x.replace('.', ''), 'name'),
call(str.title, 'name'),
extract_name_and_country])
# => [{'name': 'Sunset Rubdown', 'country': 'Canada'},
# {'name': 'Women', 'country': 'Canada'},
# {'name': 'A Silver Mt Zion', 'country': 'Canada'}]
extract_name_and_country() 可以寫(xiě)成叫做pluck()的通用函數(shù)。pluck()可以這樣使用:
print pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
call(lambda x: x.replace('.', ''), 'name'),
call(str.title, 'name'),
pluck(['name', 'country'])])
練習(xí)5。pluck()接受一系列的鍵值,根據(jù)這些鍵值去record中抽取數(shù)據(jù)。試著寫(xiě)寫(xiě)。需要用到高階函數(shù)。
我的方案:
def pluck(keys):
def pluck_fn(record):
return reduce(lambda a, x: assoc(a, x, record[x]),
keys,
{})
return pluck_fn
What now?
還有什么要做的嗎?
函數(shù)式代碼可以很好的和其他風(fēng)格的代碼配合使用。文章中的轉(zhuǎn)換器可以用任何語(yǔ)言實(shí)現(xiàn)。試試用你的代碼實(shí)現(xiàn)它。
想想Mary,Isla 和 Sam。將對(duì)list的迭代,轉(zhuǎn)成maps和reduces操作吧。
想想汽車(chē)競(jìng)賽。將代碼分解成方法。把那些方法改成函數(shù)式的。把循環(huán)處理轉(zhuǎn)成遞歸。
想想樂(lè)隊(duì)。將一系列的操作改寫(xiě)成pipeline。
標(biāo)注:
1、一塊不可變數(shù)據(jù)是指不能被改變的數(shù)據(jù)。一些語(yǔ)言像Clojure的語(yǔ)言,默認(rèn)所有的值都是不可變的。任何的可變操作都是拷貝值,并對(duì)拷貝的值做修改并返回。這樣就消除了程序中對(duì)未完成狀態(tài)訪問(wèn)所造成的bugs。
2、支持一等函數(shù)的語(yǔ)言允許像處理其他類(lèi)型的值那樣處理函數(shù)。意味著方法可以被創(chuàng)建,傳給其他方法,從方法中返回以及存儲(chǔ)在其他數(shù)據(jù)結(jié)構(gòu)里。
3、尾調(diào)用優(yōu)化是一個(gè)編程語(yǔ)言特性。每次方法遞歸,會(huì)創(chuàng)建一個(gè)棧。棧用來(lái)存儲(chǔ)當(dāng)前方法需要使用的參數(shù)和本地值。如果一個(gè)方法遞歸次數(shù)非常多,很可能會(huì)讓編譯器或解釋器消耗掉所有的內(nèi)存。有尾調(diào)用優(yōu)化的語(yǔ)言會(huì)通過(guò)重用同一個(gè)棧來(lái)支持整個(gè)遞歸調(diào)用的序列。像Python這樣的語(yǔ)言不支持尾調(diào)用優(yōu)化的通常都限制方法遞歸的數(shù)量在千次級(jí)別。在race()方法中,只有5次,所以很安全。
4、Currying意即分解一個(gè)接受多個(gè)參數(shù)的方法成一個(gè)只接受第一個(gè)參數(shù)并且返回一個(gè)接受下一個(gè)參數(shù)的方法的方法,直到接受完所有參數(shù)。
5、并行意即在不同步的情況下同時(shí)運(yùn)行同一段代碼。這些并發(fā)操作常常運(yùn)行在不同的處理器上。
6、惰性計(jì)算是編譯器的技術(shù),為了避免在需要結(jié)果之前就運(yùn)行代碼。
7、只有當(dāng)每次重復(fù)都能得出相同的結(jié)果,才能說(shuō)處理是確定性的
數(shù)據(jù)分析咨詢請(qǐng)掃描二維碼
若不方便掃碼,搜微信號(hào):CDAshujufenxi
SQL Server 中 CONVERT 函數(shù)的日期轉(zhuǎn)換:從基礎(chǔ)用法到實(shí)戰(zhàn)優(yōu)化 在 SQL Server 的數(shù)據(jù)處理中,日期格式轉(zhuǎn)換是高頻需求 —— 無(wú)論 ...
2025-09-18MySQL 大表拆分與關(guān)聯(lián)查詢效率:打破 “拆分必慢” 的認(rèn)知誤區(qū) 在 MySQL 數(shù)據(jù)庫(kù)管理中,“大表” 始終是性能優(yōu)化繞不開(kāi)的話題。 ...
2025-09-18CDA 數(shù)據(jù)分析師:表結(jié)構(gòu)數(shù)據(jù) “獲取 - 加工 - 使用” 全流程的賦能者 表結(jié)構(gòu)數(shù)據(jù)(如數(shù)據(jù)庫(kù)表、Excel 表、CSV 文件)是企業(yè)數(shù)字 ...
2025-09-18DSGE 模型中的 Et:理性預(yù)期算子的內(nèi)涵、作用與應(yīng)用解析 動(dòng)態(tài)隨機(jī)一般均衡(Dynamic Stochastic General Equilibrium, DSGE)模 ...
2025-09-17Python 提取 TIF 中地名的完整指南 一、先明確:TIF 中的地名有哪兩種存在形式? 在開(kāi)始提取前,需先判斷 TIF 文件的類(lèi)型 —— ...
2025-09-17CDA 數(shù)據(jù)分析師:解鎖表結(jié)構(gòu)數(shù)據(jù)特征價(jià)值的專(zhuān)業(yè)核心 表結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 規(guī)范存儲(chǔ)的結(jié)構(gòu)化數(shù)據(jù),如數(shù)據(jù)庫(kù)表、Excel 表、 ...
2025-09-17Excel 導(dǎo)入數(shù)據(jù)含缺失值?詳解 dropna 函數(shù)的功能與實(shí)戰(zhàn)應(yīng)用 在用 Python(如 pandas 庫(kù))處理 Excel 數(shù)據(jù)時(shí),“缺失值” 是高頻 ...
2025-09-16深入解析卡方檢驗(yàn)與 t 檢驗(yàn):差異、適用場(chǎng)景與實(shí)踐應(yīng)用 在數(shù)據(jù)分析與統(tǒng)計(jì)學(xué)領(lǐng)域,假設(shè)檢驗(yàn)是驗(yàn)證研究假設(shè)、判斷數(shù)據(jù)差異是否 “ ...
2025-09-16CDA 數(shù)據(jù)分析師:掌控表格結(jié)構(gòu)數(shù)據(jù)全功能周期的專(zhuān)業(yè)操盤(pán)手 表格結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 存儲(chǔ)的結(jié)構(gòu)化數(shù)據(jù),如 Excel 表、數(shù)據(jù) ...
2025-09-16MySQL 執(zhí)行計(jì)劃中 rows 數(shù)量的準(zhǔn)確性解析:原理、影響因素與優(yōu)化 在 MySQL SQL 調(diào)優(yōu)中,EXPLAIN執(zhí)行計(jì)劃是核心工具,而其中的row ...
2025-09-15解析 Python 中 Response 對(duì)象的 text 與 content:區(qū)別、場(chǎng)景與實(shí)踐指南 在 Python 進(jìn)行 HTTP 網(wǎng)絡(luò)請(qǐng)求開(kāi)發(fā)時(shí)(如使用requests ...
2025-09-15CDA 數(shù)據(jù)分析師:激活表格結(jié)構(gòu)數(shù)據(jù)價(jià)值的核心操盤(pán)手 表格結(jié)構(gòu)數(shù)據(jù)(如 Excel 表格、數(shù)據(jù)庫(kù)表)是企業(yè)最基礎(chǔ)、最核心的數(shù)據(jù)形態(tài) ...
2025-09-15Python HTTP 請(qǐng)求工具對(duì)比:urllib.request 與 requests 的核心差異與選擇指南 在 Python 處理 HTTP 請(qǐng)求(如接口調(diào)用、數(shù)據(jù)爬取 ...
2025-09-12解決 pd.read_csv 讀取長(zhǎng)浮點(diǎn)數(shù)據(jù)的科學(xué)計(jì)數(shù)法問(wèn)題 為幫助 Python 數(shù)據(jù)從業(yè)者解決pd.read_csv讀取長(zhǎng)浮點(diǎn)數(shù)據(jù)時(shí)的科學(xué)計(jì)數(shù)法問(wèn)題 ...
2025-09-12CDA 數(shù)據(jù)分析師:業(yè)務(wù)數(shù)據(jù)分析步驟的落地者與價(jià)值優(yōu)化者 業(yè)務(wù)數(shù)據(jù)分析是企業(yè)解決日常運(yùn)營(yíng)問(wèn)題、提升執(zhí)行效率的核心手段,其價(jià)值 ...
2025-09-12用 SQL 驗(yàn)證業(yè)務(wù)邏輯:從規(guī)則拆解到數(shù)據(jù)把關(guān)的實(shí)戰(zhàn)指南 在業(yè)務(wù)系統(tǒng)落地過(guò)程中,“業(yè)務(wù)邏輯” 是連接 “需求設(shè)計(jì)” 與 “用戶體驗(yàn) ...
2025-09-11塔吉特百貨孕婦營(yíng)銷(xiāo)案例:數(shù)據(jù)驅(qū)動(dòng)下的精準(zhǔn)零售革命與啟示 在零售行業(yè) “流量紅利見(jiàn)頂” 的當(dāng)下,精準(zhǔn)營(yíng)銷(xiāo)成為企業(yè)突圍的核心方 ...
2025-09-11CDA 數(shù)據(jù)分析師與戰(zhàn)略 / 業(yè)務(wù)數(shù)據(jù)分析:概念辨析與協(xié)同價(jià)值 在數(shù)據(jù)驅(qū)動(dòng)決策的體系中,“戰(zhàn)略數(shù)據(jù)分析”“業(yè)務(wù)數(shù)據(jù)分析” 是企業(yè) ...
2025-09-11Excel 數(shù)據(jù)聚類(lèi)分析:從操作實(shí)踐到業(yè)務(wù)價(jià)值挖掘 在數(shù)據(jù)分析場(chǎng)景中,聚類(lèi)分析作為 “無(wú)監(jiān)督分組” 的核心工具,能從雜亂數(shù)據(jù)中挖 ...
2025-09-10統(tǒng)計(jì)模型的核心目的:從數(shù)據(jù)解讀到?jīng)Q策支撐的價(jià)值導(dǎo)向 統(tǒng)計(jì)模型作為數(shù)據(jù)分析的核心工具,并非簡(jiǎn)單的 “公式堆砌”,而是圍繞特定 ...
2025-09-10