Fonksiyonel programlama durumdan kurtulamaz. Sadece açık yapar! Harita gibi işlevlerin genellikle "paylaşılan" bir veri yapısını "çözeceği" doğru olsa da, tek yapmanız gereken bir erişilebilirlik algoritması yazmaksa, o zaman daha önce ziyaret ettiğiniz düğümleri takip etmek meselesidir:
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]
Şimdi, tüm bu durumu el ile takip etmenin oldukça sinir bozucu ve hataya eğilimli olduğunu itiraf etmeliyim (s yerine 's' kullanımı kolaydır, aynı s 'yi birden fazla hesaba geçirmek kolaydır ...) . Monadlar burada devreye giriyor: daha önce yapamayacağınız hiçbir şey eklemiyorlar, ancak durum değişkenini dolaylı olarak geçirmenize izin veriyorlar ve arayüz, tek iş parçacıklı bir şekilde gerçekleşmesini garanti ediyor.
Düzenleme: Ben şimdi ne yaptığını daha fazla bir mantık vermeye çalışacağım: her şeyden önce, sadece ulaşılabilirlik için test yerine ben bir derinlik ilk arama kodladı. Uygulama hemen hemen aynı görünecek, ancak hata ayıklama biraz daha iyi görünüyor.
Durum bilgisi olan bir dilde, DFS şöyle görünecektir:
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)
Şimdi değişebilir durumdan kurtulmanın bir yolunu bulmalıyız. Her şeyden önce dfs geçersiz yerine döndürerek "ziyaret listesi" değişken kurtulmak:
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))
Ve şimdi zor kısmı geliyor: "ziyaret" değişkeninden kurtulmak. Temel hile, ihtiyacı olan işlevlere durumu ek bir parametre olarak geçirdiğimiz ve bu işlevlerin, değiştirmek istedikleri takdirde devletin yeni sürümünü ekstra bir dönüş değeri olarak döndürmesini sağlayan bir kural kullanmaktır.
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...
Bu kalıbı dfs'ye uygulamak için, "ziyaret edilen" seti fazladan bir parametre olarak almak ve "ziyaret edilen" in güncellenmiş sürümünü ekstra bir dönüş değeri olarak döndürmek için değiştirmemiz gerekir. Ayrıca, kodu her zaman "ziyaret edilen" dizinin "en son" sürümünü iletmemiz için yeniden yazmamız gerekir:
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 sürümü, burada yaptığımın hemen hemen hepsini yapıyor, ancak tüm yol boyunca gidiyor ve değişebilir "curr_visited" ve "childtrees" değişkenleri yerine iç özyinelemeli bir işlev kullanıyor.
Monad'lara gelince, temelde başardıkları şey, sizi el ile yapmaya zorlamak yerine dolaylı olarak "curr_visited" i geçmektedir. Bu sadece kod karmaşasını kaldırmakla kalmaz, aynı zamanda çatallama durumu (aynı "ziyaret edilen" setini durumu zincirlemek yerine sonraki iki çağrıya geçirme) gibi hatalar yapmanızı da önler.