Fonksiyonel programlama ve durumsal algoritmalar


12

Haskell ile fonksiyonel programlama öğreniyorum . Bu arada Automata teorisini inceliyorum ve ikisi birbirine çok iyi uyuyor gibi göründüğü için automata ile oynamak için küçük bir kütüphane yazıyorum.

Soruyu sormamı sağlayan sorun işte burada. Bir devletin ulaşılabilirliğini değerlendirmenin bir yolunu incelerken, basit bir özyinelemeli algoritmanın oldukça verimsiz olacağı fikrini aldım, çünkü bazı yollar bazı durumları paylaşabilir ve bunları birden fazla kez değerlendirebilirim.

Örneğin burada, değerlendirme reachability ait g dan bir , ben dışlamak zorundayız f aracılığıyla yolunu kontrol ederken hem d ve c :

bir otomatı temsil eden digraf

Benim fikrim, birçok yolda paralel çalışan ve hariç tutulan durumların paylaşılan bir kaydını güncelleyen bir algoritmanın harika olabileceği, ancak bu benim için çok fazla.

Bazı basit özyineleme durumlarında bir kişinin bir argüman olarak devleti geçebileceğini gördüm ve burada yapmam gereken şey bu, çünkü döngülerden kaçınmak için yaşadığım devletlerin listesini iletiyorum. Ama bu listeyi de geriye doğru iletmenin bir yolu var canReachmı , benim fonksiyonumun boole sonucuyla birlikte bir demet halinde geri dönmek gibi ? (bu biraz zorlanmış hissetmesine rağmen)

Örnek durumumun geçerliliğinin yanı sıra, bu tür sorunları çözmek için başka hangi teknikler mevcut? İle ne gibi çözümler olmak zorunda olduğunu bu ortak yeterince olmalı gibi hissediyorum fold*ya map.

Şimdiye kadar, learnyouahaskell.com okumak hiç bulamadım, ama henüz monads dokunmadım düşünün.

( ilgiliyse kodumu kod görünümünde yayınladım )


3
Birincisi, üzerinde çalışmaya çalıştığınız kodu görmek isterim. Bunun yokluğunda, en iyi tavsiyem Haskell'in tembelliklerinden çoğu zaman bir kereden fazla hesaplamak için sömürülebileceğidir. "Düğümün bağlanması" ve tembel değer özyinelemesine bakın, ancak probleminiz sonsuz değerlerden ve benzer şeylerden yararlanan daha gelişmiş tekniklerin aşırıya kaçması ve muhtemelen şu anda sizi karıştırması yeterince basittir.
Ptharien'in Alevi

1
@ Ptharien'sFlame gösterdiğiniz ilgi için teşekkür ederiz! İşte kod , tüm projeye bir bağlantı da var. Şimdiye kadar yaptığımla zaten kafam karıştı, bu yüzden evet, gelişmiş tekniklere bakmamak daha iyi :)
bigstones

1
Bir durum otomata hemen hemen fonksiyonel programlamanın antitezidir. İşlevsel programlama, dahili durum olmadan sorunları çözmekle ilgilidir, bir durum otomata ise tamamen kendi durumunu yönetmekle ilgilidir.
Philipp

@Philipp Kabul etmiyorum. Bir otomat veya durum makinesi bazen bir sorunu temsil etmenin en doğal ve doğru yoludur ve fonksiyonel otomatlar iyi çalışılmıştır.
Ptharien'in Alevi

5
@Philipp: İşlevsel programlama, durumu yasaklamak değil, durumu açık yapmakla ilgilidir. Aslında, kuyruk özyineleme, gotos ile dolu bu devlet makinelerini uygulamak için gerçekten harika bir araçtır.
hugomg

Yanıtlar:


16

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.


Onu daha az acı verici ve belki daha okunabilir hale getirmenin bir yolu olması gerektiğini biliyordum, çünkü örneğinizi anlamakta zorlanıyorum. Seninki gibi kodu anlamak için monadlara mı yoksa daha iyi uygulamalara mı gideyim?
bigstones

@bigstones: Sanırım kodlarım monads ile mücadele etmeden önce nasıl çalıştığını anlamaya çalışmalısınız - temelde yaptığımla aynı şeyi yapacaklar ama sizi şaşırtmak için ekstra soyutlama katmanlarıyla yapacaklar. Her neyse, işleri biraz daha açık hale getirmek için bazı ekstra açıklamalar ekledim
hugomg

1
"Fonksiyonel programlama durumdan kurtulmaz. Sadece açık yapar!": Bu gerçekten açıklayıcı!
Giorgio

"[Monads], devlet değişkenini dolaylı olarak geçirmenize izin verir ve arabirim, tek iş parçacıklı bir şekilde gerçekleşmesini garanti eder" <- Bu, monad'ların aydınlatıcı bir açıklamasıdır; Bu sorunun bağlamı dışında, 'durum değişkeni' yerine 'kapatma' değiştirebilirim
antropik android

2

İşte size basit bir cevap mapConcat.

 mapConcat :: (a -> [b]) -> [a] -> [b]
 -- mapConcat is in the std libs, mapConcat = concat . map
 type Path = []

 isReachable :: a -> Auto a -> a -> [Path a]
 isReachable to auto from | to == from = [[]]
 isReachable to auto from | otherwise = 
    map (from:) . mapConcat (isReachable to auto) $ neighbors auto from

Nerede neighborshemen bir duruma bağlı durumları döndürür. Bu bir dizi yol döndürür.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.