Haskell'deki travers işlevini birisi açıklayabilir mi?


99

traverseİşlevi elimden almaya çalışıyorum ve başarısız oluyorum Data.Traversable. Amacını göremiyorum. Zorunlu bir geçmişten geldiğim için, lütfen birisi bunu bana zorunlu bir döngü açısından açıklayabilir mi? Sözde kod çok takdir edilecektir. Teşekkürler.


1
Yineleyici Kalıbının Özü makalesi , adım adım geçiş kavramını oluşturduğu için yararlı olabilir. Yine de bazı gelişmiş kavramlar mevcut
Jackie

Yanıtlar:


121

traversefmapveri yapısını yeniden oluştururken efektleri çalıştırmanıza izin vermesi dışında aynıdır .

Data.TraversableDokümantasyondaki örneğe bir göz atın .

 data Tree a = Empty | Leaf a | Node (Tree a) a (Tree a)

FunctorÖrneği Treeolacaktır:

instance Functor Tree where
  fmap f Empty        = Empty
  fmap f (Leaf x)     = Leaf (f x)
  fmap f (Node l k r) = Node (fmap f l) (f k) (fmap f r)

fHer değere uygulayarak tüm ağacı yeniden inşa eder .

instance Traversable Tree where
    traverse f Empty        = pure Empty
    traverse f (Leaf x)     = Leaf <$> f x
    traverse f (Node l k r) = Node <$> traverse f l <*> f k <*> traverse f r

Oluşturucuların Traversableuygulama tarzında çağrılması dışında, örnek hemen hemen aynıdır. Bu, ağacı yeniden inşa ederken (yan) etkilere sahip olabileceğimiz anlamına gelir. Etkilerin önceki sonuçlara bağlı olmaması dışında, uygulama neredeyse monadlarla aynıdır. Bu örnekte bu, örneğin sol dalı yeniden oluşturma sonuçlarına bağlı olarak bir düğümün sağ dalından farklı bir şey yapamayacağınız anlamına gelir.

Tarihsel nedenlerle, Traversablesınıf da bir monadic sürümünü içeren traversedenilen mapM. Tüm niyetler ve amaçlar mapMiçin aynıdır traverse- ayrı bir yöntem olarak mevcuttur, çünkü Applicativeancak daha sonra bir üst sınıf haline gelmiştir Monad.

Bunu saf olmayan bir dilde uygularsanız, yan etkileri önlemenin bir yolu olmadığı fmapiçin aynı olacaktır traverse. Veri yapınızı yinelemeli olarak geçmek zorunda olduğunuz için bunu bir döngü olarak uygulayamazsınız. İşte bunu Javascript'te nasıl yapacağımla ilgili küçük bir örnek:

Node.prototype.traverse = function (f) {
  return new Node(this.l.traverse(f), f(this.k), this.r.traverse(f));
}

Bunu bu şekilde uygulamak, sizi dilin izin verdiği etkilere sınırlar. Belirsizlik (Uygulayıcı modellerin liste örneği) istiyorsanız ve dilinizde yerleşik değilse, şansınız kalmaz.


11
'Etki' terimi ne anlama geliyor?
missingfaktor

25
@missingfaktor: FunctorParametrik olmayan kısmın, a'nın yapısal bilgisidir . Durum değeri State, başarısızlık Maybeve Eitheriçindeki eleman sayısı []ve tabii ki keyfi dış yan etkiler IO. Bunu genel bir terim olarak umursamıyorum ( Monoid"boş" ve "ekleme" kullanan işlevler gibi, kavram, terimin ilk başta önerdiğinden daha geneldir) ama oldukça yaygın ve amaca yeterince iyi hizmet ediyor.
CA McCann

@CA McCann: Anladım. Cevabın için teşekkür ederim!
missingfaktor

1
"Bunu yapmamanız gerektiğinden oldukça eminim [...]." Kesinlikle hayır - etkilerini apönceki sonuçlara bağlı kılmak kadar kötü olur. Bu yorumu buna göre yeniden ifade ettim.
2016

2
"Etkilerin önceki sonuçlara bağlı olmaması dışında, uygulama neredeyse monadlarla aynıdır." ... bu satırla benim için bir çok şey yerine oturdu, teşekkürler!
agam

58

traverseBir iç şeyler dönüyor Traversablebir içine Traversablebir "iç" şeylerin Applicative, verilen bir fonksiyonu yaptığı Applicativeşeylerin s out.

Let kullanması Maybeolarak Applicativeolarak ve listeden Traversable. İlk önce dönüştürme işlevine ihtiyacımız var:

half x = if even x then Just (x `div` 2) else Nothing

Yani bir sayı çift ise yarısını alırız (a'nın içinde Just), yoksa alırız Nothing. Her şey "iyi" giderse, şöyle görünür:

traverse half [2,4..10]
--Just [1,2,3,4,5]

Fakat...

traverse half [1..10]
-- Nothing

Bunun nedeni ise <*>fonksiyon sonucunu oluşturmak için kullanılır ve argümanlardan biri olduğunda Nothing, elde ederiz Nothinggeri.

Başka bir örnek:

rep x = replicate x x

Bu işlev x, içeriğe sahip bir uzunluk listesi oluşturur x, örneğin rep 3= [3,3,3]. Sonucu ne olur traverse rep [1..3]?

Biz kısmi sonuçlarını almak [1], [2,2]ve [3,3,3]kullanma rep. Şimdi listelerin semantiği olduğu gibi Applicatives"tüm kombinasyonları al", örneğin (+) <$> [10,20] <*> [3,4]olduğu gibi [13,14,23,24].

Öğesinin "tüm kombinasyonları" [1]ve [2,2]iki kere [1,2]. Tüm kombinasyonlar iki kez [1,2]ve [3,3,3]altı kez [1,2,3]. Böylece sahibiz:

traverse rep [1..3]
--[[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]

1
Sonucun bana bunu hatırlattı .
hugomg

3
@missingno: Evet, kaçırdılarfac n = length $ traverse rep [1..n]
Landei

1
Aslında, "List-encoding-programmer" altında (ancak liste anlamalarını kullanarak) orada. Bu web sitesi kapsamlı :)
hugomg

1
@missingno: Hm, tam olarak aynı değil ... ikisi de listedeki monad'ın Kartezyen ürün davranışına güveniyor, ancak site bir seferde yalnızca iki tane kullanıyor, bu yüzden liftA2 (,)daha genel bir form kullanmaktan çok yapmak gibi traverse.
CA McCann

42

Aşağıdaki gibi tanımlanabileceği gibi sequenceA, anlamının en kolay yol olduğunu düşünüyorum traverse.

traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f

sequenceA bir yapının elemanlarını soldan sağa sıralar, sonuçları içeren aynı şekle sahip bir yapı döndürür.

sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)
sequenceA = traverse id

sequenceAİki işlevin sırasını tersine çevirmeyi de düşünebilirsiniz , örneğin bir eylemler listesinden bir sonuç listesi döndüren bir eyleme geçme.

Dolayısıyla traverse, bir yapı alır ve yapıdaki fher öğeyi bir uygulamaya dönüştürmek için uygulanır , ardından bu uygulamaların etkilerini soldan sağa sıralar ve sonuçları içeren aynı şekle sahip bir yapı döndürür.

Ayrıca Foldable, ilgili işlevi tanımlayan ile de karşılaştırabilirsiniz traverse_.

traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f ()

Eğer arasındaki temel fark, görebilirsiniz Foldableve Traversableönceki başka bir değer içine sonucu katlayın gerektirir, oysa ikinci, sen yapının şekli korumak için izin vermesidir.


Kullanımının basit bir örneği, geçilebilir yapı ve IOuygulama olarak bir liste kullanmaktır:

λ> import Data.Traversable
λ> let qs = ["name", "quest", "favorite color"]
λ> traverse (\thing -> putStrLn ("What is your " ++ thing ++ "?") *> getLine) qs
What is your name?
Sir Lancelot
What is your quest?
to seek the holy grail
What is your favorite color?
blue
["Sir Lancelot","to seek the holy grail","blue"]

Bu örnek oldukça heyecan verici olmasa da, traversebaşka tür kaplarda kullanıldığında veya başka uygulamalar kullanıldığında işler daha ilginç hale geliyor .


Yani çapraz geçiş, mapM'nin daha genel bir biçimidir? Aslında sequenceA . fmaplisteler için eşit sequence . mapdeğil mi?
Raskell

Yan etkileri sıralamakla ne demek istiyorsun? Cevabınızdaki 'yan etki' nedir - yan etkilerin sadece monadlarda mümkün olduğunu düşündüm. Saygılarımızla.
Marek

1
@Marek "Yan etkilerin yalnızca monadlarda mümkün olduğunu düşündüm" - Bağlantı bundan çok daha gevşek: (1) IO Tür , yan etkileri ifade etmek için kullanılabilir; (2) IOçok uygun olduğu ortaya çıkan bir monad olur. Monadlar esasen yan etkilerle bağlantılı değildir. Ayrıca, "etki" nin olağan anlamında "yan etki" den daha geniş bir anlamı olduğu da unutulmamalıdır - saf hesaplamaları içeren bir anlam. Bu son noktada, ayrıca bkz . "Etkili" tam olarak ne anlama geliyor ?
18'i

(Bu arada, @hammar, ben nedeniyle yukarıdaki açıklamada belirtildiği nedenlerle bu yanıtında "etkisi" için "yan etkisi" değişim özgürlük aldı.)
duplode

17

Bu tür benzeri var fmapayrıca sonuç tipini değiştiren mapper fonksiyonunun içinden etkilerini çalıştırabilirsiniz dışında.

Bir veritabanında kullanıcı kimliklerini temsil eden tamsayılar listesini düşünün: [1, 2, 3]. Eğer isterseniz fmapadları bu kullanıcı kimlikleri, geleneksel bir kullanamazsınız fmap(- kullanarak, bu durumda etkisi gerektirir işlevi içinde kullanıcı adlarını okumak için veritabanına erişim gerektiğinden, IOmonad).

İmzası traverse:

traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)

İle traverse, efektler yapabilirsiniz, bu nedenle, kullanıcı kimliklerini kullanıcı adlarıyla eşleme kodunuz şöyle görünür:

mapUserIDsToUsernames :: (Num -> IO String) -> [Num] -> IO [String]
mapUserIDsToUsernames fn ids = traverse fn ids

Ayrıca mapM:

mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)

Herhangi bir kullanımı mapMile değiştirilebilir traverse, ancak tersi olamaz. mapMyalnızca monadlar için çalışır, oysa traversedaha geneldir.

Sadece bir etkisi elde etmek ve herhangi bir yararlı değer döndürmez istiyorsanız vardır traverse_ve mapM_fonksiyonun geri dönüş değerini göz ardı edip biraz daha hızlı her ikisi de bu fonksiyonların, sürümleri.



7

traverse bir döngü. Uygulanması, geçilecek veri yapısına bağlıdır. Bu bir liste, ağaç, olabilir Maybe, Seqbir for döngüsü veya özyinelemeli fonksiyonu gibi bir şey aracılığıyla geçer, bir jenerik bir yol vardır (bolluk,), ya da bir şey. Bir dizi, bir for-döngüsüne, bir while-döngüsüne, bir ağaca ya özyinelemeli bir şeye ya da bir while döngüsüyle bir yığının kombinasyonuna sahip olacaktır; ancak işlevsel dillerde bu hantal döngü komutlarına ihtiyacınız yoktur: Döngünün iç kısmını (bir işlev şeklinde) veri yapısı ile daha doğrudan ve daha az ayrıntılı bir şekilde birleştirirsiniz.

TraversableTypeclass ile muhtemelen algoritmalarınızı daha bağımsız ve çok yönlü yazabilirsiniz. Ancak deneyimlerime göre, bu Traversablegenellikle yalnızca algoritmaları mevcut veri yapılarına yapıştırmak için kullanılır. Nitelikli farklı veri türleri için de benzer işlevler yazmaya gerek kalmaması oldukça güzel.

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.