การเขียนโปรแกรมฟังก์ชั่นไม่ได้กำจัดรัฐ มันทำให้ชัดเจนเท่านั้น! ในขณะที่มันเป็นความจริงที่ฟังก์ชั่นเช่นแผนที่มักจะ "คลี่คลาย" โครงสร้างข้อมูล "ที่ใช้ร่วมกัน" หากคุณต้องการทำคือการเขียนอัลกอริธึมการเข้าถึงได้มันเป็นเพียงเรื่องของการติดตามว่าโหนดใดที่คุณเยี่ยมชมแล้ว
import qualified Data.Set as S
data Node = Node Int [Node] deriving (Show)
-- Receives a root node, returns a list of the node keyss visited in a depth-first search
dfs :: Node -> [Int]
dfs x = fst (dfs' (x, S.empty))
-- This worker function keeps track of a set of already-visited nodes to ignore.
dfs' :: (Node, S.Set Int) -> ([Int], S.Set Int)
dfs' (node@(Node k ns), s )
| k `S.member` s = ([], s)
| otherwise =
let (childtrees, s') = loopChildren ns (S.insert k s) in
(k:(concat childtrees), s')
--This function could probably be implemented as just a fold but Im lazy today...
loopChildren :: [Node] -> S.Set Int -> ([[Int]], S.Set Int)
loopChildren [] s = ([], s)
loopChildren (n:ns) s =
let (xs, s') = dfs' (n, s) in
let (xss, s'') = loopChildren ns s' in
(xs:xss, s'')
na = Node 1 [nb, nc, nd]
nb = Node 2 [ne]
nc = Node 3 [ne, nf]
nd = Node 4 [nf]
ne = Node 5 [ng]
nf = Node 6 []
ng = Node 7 []
main = print $ dfs na -- [1,2,5,7,3,6,4]
ตอนนี้ฉันต้องสารภาพว่าการติดตามสถานะทั้งหมดนี้ด้วยมือค่อนข้างน่ารำคาญและเกิดข้อผิดพลาดได้ง่าย (มันใช้งานง่าย 'แทน s' 'มันง่ายที่จะส่งการคำนวณเดียวกันมากกว่าหนึ่งการคำนวณ ... ) . นี่คือที่มาของพระ: พวกเขาไม่ได้เพิ่มอะไรที่คุณไม่เคยทำมาก่อน แต่พวกเขาให้คุณผ่านตัวแปรสถานะรอบ ๆ โดยปริยายและอินเทอร์เฟซรับประกันว่ามันจะเกิดขึ้นในแบบเธรดเดียว
แก้ไข: ฉันจะพยายามให้เหตุผลเพิ่มเติมเกี่ยวกับสิ่งที่ฉันทำตอนนี้: ก่อนอื่นแทนที่จะแค่ทดสอบความสามารถในการเข้าถึงที่ฉันเขียนโค้ดการค้นหาเชิงลึกครั้งแรก การนำไปใช้จะดูค่อนข้างเหมือนเดิม แต่การดีบั๊กดูดีขึ้นเล็กน้อย
ในภาษา stateful, DFS จะมีลักษณะเช่นนี้:
visited = set() #mutable state
visitlist = [] #mutable state
def dfs(node):
if isMember(node, visited):
//do nothing
else:
visited[node.key] = true
visitlist.append(node.key)
for child in node.children:
dfs(child)
ตอนนี้เราต้องหาวิธีกำจัดสถานะที่ไม่แน่นอน ก่อนอื่นเรากำจัดตัวแปร "visitlist" ด้วยการทำให้ dfs คืนค่านั้นแทน void:
visited = set() #mutable state
def dfs(node):
if isMember(node, visited):
return []
else:
visited[node.key] = true
return [node.key] + concat(map(dfs, node.children))
และตอนนี้มาถึงส่วนที่ยุ่งยาก: กำจัดตัวแปร "เยี่ยมชม" เคล็ดลับพื้นฐานคือการใช้การประชุมที่เราผ่านรัฐเป็นพารามิเตอร์เพิ่มเติมไปยังฟังก์ชั่นที่ต้องการและมีฟังก์ชั่นเหล่านั้นกลับรุ่นรัฐใหม่เป็นค่าตอบแทนพิเศษหากพวกเขาต้องการแก้ไข
let increment_state s = s+1 in
let extract_state s = (s, 0) in
let s0 = 0 in
let s1 = increment_state s0 in
let s2 = increment_state s1 in
let (x, s3) = extract_state s2 in
-- and so on...
หากต้องการใช้รูปแบบนี้กับ dfs เราจำเป็นต้องเปลี่ยนรูปแบบเพื่อรับชุด "เข้าชม" เป็นพารามิเตอร์พิเศษและเพื่อส่งคืนเวอร์ชัน "อัปเดต" ที่เข้าชม "เป็นค่าส่งคืนพิเศษ นอกจากนี้เราต้องเขียนโค้ดใหม่เพื่อให้เราส่งต่ออาร์เรย์ "เยี่ยมชม" รุ่นล่าสุดเสมอ:
def dfs(node, visited1):
if isMember(node, visited1):
return ([], visited1) #return the old state because we dont want to change it
else:
curr_visited = insert(node.key, visited1) #immutable update, with a new variable for the new value
childtrees = []
for child in node.children:
(ct, curr_visited) = dfs(child, curr_visited)
child_trees.append(ct)
return ([node.key] + concat(childTrees), curr_visited)
รุ่น Haskell ทำสิ่งที่ฉันทำที่นี่ได้สวยมากยกเว้นว่ามันไปตลอดทางและใช้ฟังก์ชั่นเรียกซ้ำภายในแทนตัวแปร "curr_visited" และ "childtrees" ที่ไม่แน่นอน
สำหรับพระสงฆ์สิ่งที่พวกเขาประสบความสำเร็จโดยทั่วไปคือการส่ง "curr_visited" ไปโดยปริยายแทนที่จะบังคับให้คุณทำมันด้วยมือ ไม่เพียง แต่จะลบความยุ่งเหยิงออกจากรหัสเท่านั้น แต่ยังป้องกันไม่ให้คุณทำผิดพลาดเช่นการฟอร์กสถานะ