Monadlar miras hiyerarşilerine uygulanabilir (belki tercih edilebilir) bir alternatif midir?


20

Bir dil-agnostik kullanmak için gidiyorum açıklamasına ilk Monoids anlatan böyle monad'ların:

Bir monoid parametre olarak bir tür almak ve aynı tür dönmek fonksiyonların (kabaca) kümesidir.

Bir tek hücreli bir alan işlevler (kabaca) bir dizi sarma bir parametre olarak türüne ve aynı ambalaj tipini verir.

Bunların tanım değil, açıklama olduğunu unutmayın. Bu açıklamaya saldırmaktan çekinmeyin!

Yani bir OO dilinde, bir monad aşağıdaki gibi operasyon kompozisyonlarına izin verir:

Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()

Monad'ın içerdiği sınıftan ziyade bu işlemlerin anlambilimini tanımladığını ve kontrol ettiğini unutmayın.

Geleneksel olarak, bir OO dilinde, bu semantiği sağlamak için bir sınıf hiyerarşisi ve miras kullanırdık. Yani bir olurdu Birdyöntemlerle sınıfı takeOff(), flyAround()ve land(), ve Ördek olanlar miras olacaktır.

Ama sonra uçamayan kuşlarla başımız belaya girer, çünkü penguin.takeOff()başarısız olur. İstisna atma ve işleme başvurmak zorundayız.

Ayrıca, Penguin'in bir olduğunu söyledikten sonra Bird, örneğin bir hiyerarşisine sahipsek, çoklu kalıtımla ilgili sorunlarla karşılaşırız Swimmer.

Esasen sınıfları kategorilere ayırmaya çalışıyoruz (Kategori Teorisi adamlarından özür dileriz) ve tek tek sınıflardan ziyade semantiği kategoriye göre tanımlamaya çalışıyoruz. Fakat monadlar bunu yapmak için hiyerarşilerden çok daha açık bir mekanizma gibi görünüyorlar.

Bu durumda, Flier<T>yukarıdaki örnek gibi bir monadımız olacaktı:

Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()

... ve asla a Flier<Penguin>. Bunun bir işaretleyici arayüzü ile olmasını önlemek için statik yazmayı bile kullanabiliriz. Veya kefalet için çalışma zamanı yetenek kontrolü. Ama gerçekten, bir programcı asla bir Pengueni Flier'e koymamalı, aynı anlamda asla sıfırla bölmemelidir.

Ayrıca, daha genel olarak uygulanabilir. Bir pilotun Kuş olması gerekmez. Örneğin Flier<Pterodactyl>, veya Flier<Squirrel>bu bireysel türlerin anlambilimlerini değiştirmeden.

Anlambilimi, bir kaptaki, tür hiyerarşileri yerine, birleştirilebilir işlevlere göre sınıflandırdığımızda, "tür bir tür, belirli tür bir hiyerarşiye uymayan" sınıflarla ilgili eski sorunları çözer. Aynı zamanda kolayca ve açıkça gibi bir sınıf için birden semantiğini verir Flier<Duck>hem de Swimmer<Duck>. Görünüşe göre davranışı sınıf hiyerarşileri ile sınıflandırarak bir empedans uyumsuzluğu ile mücadele ediyoruz. Monadlar zarif bir şekilde idare eder.

Öyleyse sorum şu: Aynı şekilde miras üzerine kompozisyonu tercih etmeye geldiğimiz gibi, miras üzerine monadları da tercih etmek mantıklı mı?

(BTW Bunun burada mı yoksa Comp Sci'de mi olduğundan emin değildim, ancak bu daha pratik bir modelleme problemi gibi görünüyor. Ama belki orada daha iyi.)


1
Nasıl çalıştığını anladığımdan emin değilim: bir sincap ve bir ördek aynı şekilde uçmuyor - bu yüzden "sinek eyleminin" bu sınıflarda uygulanması gerekiyor ... Ve pilotun sincap ve ördeği yapmak için bir yönteme ihtiyacı var fly ... Belki ortak bir Flier arayüzünde ... Hata! Bir dakika ... Bir şey mi kaçırdım?
assylias

Arabirimler sınıf mirasından farklıdır, çünkü işlevsel kalıtım gerçek davranışı tanımlarken arabirimler yetenekleri tanımlar. "Kalıtım üzerine kompozisyon" da bile, arayüzlerin tanımlanması hala önemli bir mekanizmadır (örneğin polimorfizm). Arabirimler aynı çoklu kalıtım sorunlarıyla karşılaşmaz. Ayrıca, her uçucu, kabın kullanması için "getFlightSpeed ​​()" veya "getManuverability ()" gibi (bir arayüz ve polimorfizm yoluyla) yetenek özellikleri sağlayabilir.
Rob

3
Parametrik polimorfizm kullanımının alt tip polimorfizme her zaman uygun bir alternatif olup olmadığını sormaya mı çalışıyorsunuz?
ChaosPandion

Anlamsallığı koruyan birleştirilebilir işlevler eklemenin kırışıklığı ile. Parametrelendirilmiş konteyner türleri uzun zamandır var, ancak kendi başlarına bana tam bir cevap olarak vurmuyorlar. Bu yüzden monad paterninin daha temel bir rolü olup olmadığını merak ediyorum.
Rob

6
Monoid ve monad tanımını anlamıyorum. Monoidlerin temel özelliği, ilişkilendirilebilir bir ikili işlem içermesidir (kayan nokta toplama, tamsayı çarpma veya dize birleştirme). Bir monad, çeşitli (muhtemelen bağımlı) hesaplamaları bir sırada sıralamayı destekleyen bir soyutlamadır .
Rufflewind

Yanıtlar:


15

Kısa cevap hayır , monadlar miras hiyerarşilerine (alt tip polimorfizm olarak da bilinir) bir alternatif değildir. Monadların yararlandığı ancak bunu yapacak tek şey olmayan parametrik polimorfizmi tarif ediyor gibi görünüyorsunuz .

Onları anladığım kadarıyla, monadların aslında kalıtımla hiçbir ilgisi yok. İki şeyin az çok dik olduğunu söyleyebilirim: farklı problemleri ele almaları amaçlanıyor ve bu yüzden:

  1. En az iki anlamda sinerjik olarak kullanılabilirler:
    • Haskell'in tip sınıflarının çoğunu kapsayan Typeclassopedia'ya göz atın . Aralarında kalıtım benzeri ilişkiler olduğunu fark edeceksiniz. Örneğin, Monad, kendisi Functor'dan gelen Uygulayıcı'dan alınmıştır.
    • Monad örnekleri olan veri türleri sınıf hiyerarşilerine katılabilir. Unutmayın, Monad daha çok bir arayüz gibidir - belirli bir tür için uygulamak size veri türü hakkında bazı şeyler söyler, ancak her şeyi değil.
  2. Birini diğerini yapmak için kullanmaya çalışmak zor ve çirkin olacaktır.

Son olarak, bu sorunuza teğet olmakla birlikte, monad'ların bestelemek için inanılmaz güçlü bazı yolları olduğunu öğrenmek isteyebilirsiniz; daha fazla bilgi edinmek için monad transformatörlerini okuyun. Bununla birlikte, bu hala aktif bir araştırma alanıdır, çünkü biz (ve biz, benden 100000x daha akıllı insanlar) monadlar oluşturmak için harika yollar bulmadık ve bazı monadlar keyfi olarak bestelenmiyor gibi görünüyor.


Şimdi sorunuzu nit-pick (üzgünüm, bunun yardımcı olması ve sizi kötü hissetmemesi için niyet ediyorum): Biraz ışık tutmaya çalışacağım birçok tartışmalı bina olduğunu hissediyorum.

  1. Monad, kapsayıcı türünü parametre olarak alan ve aynı kapsayıcı türünü döndüren bir işlevler kümesidir.

    Hayır, bu bir MonadHaskell: Bir parametreli tip m abir uygulama ile return :: a -> m ave (>>=) :: m a -> (a -> m b) -> m başağıdaki yasaları tatmin:

    return a >>= k  ==  k a
    m >>= return  ==  m
    m >>= (\x -> k x >>= h)  ==  (m >>= k) >>= h
    

    Kaplar ( (->) b) olmayan bazı Monad örnekleri vardır ve Monad örnekleri olmayan (ve yapılamayan) bazı kaplar vardır ( Settip sınıfı kısıtlaması nedeniyle). Yani "konteyner" sezgisi kötüdür. Bkz bu fazla örnek için.

  2. Yani bir OO dilinde, bir monad aşağıdaki gibi operasyon kompozisyonlarına izin verir:

      Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
    

    Hayır, hiç de değil. Bu örnek bir Monad gerektirmez. İhtiyaç duyduğu tek şey, eşleşen giriş ve çıkış türlerine sahip işlevlerdir. İşte sadece fonksiyon uygulaması olduğunu vurgulayan başka bir yazma yolu:

    Flier<Duck> m = land(flyAround(takeOff(new Flier<Duck>(duck))));
    

    Bu bir "akıcı arayüz" veya "yöntem zincirleme" olarak bilinen bir desen olduğuna inanıyorum (ama emin değilim).

  3. Monad'ın içerdiği sınıftan ziyade bu işlemlerin anlambilimini tanımladığını ve kontrol ettiğini unutmayın.

    Aynı zamanda monad olan veri türlerinin, monadlarla ilgisi olmayan işlemleri olabilir (ve neredeyse her zaman yapar!). İşte monadlarla []ilgisi olmayan üç fonksiyondan oluşan bir Haskell örneği : []"operasyonun semantiğini tanımlar ve kontrol eder" ve "içerilen sınıf" yoktur, ancak bu bir monad yapmak için yeterli değildir:

    \predicate -> length . filter predicate . reverse
    
  4. Bir şeyleri modellemek için sınıf hiyerarşilerini kullanmada sorunlar olduğunu doğru bir şekilde not ettiniz. Bununla birlikte, örnekleriniz monadların yapabileceğine dair herhangi bir kanıt sunmuyor:

    • Mirasın iyi olduğu şeylerde iyi bir iş çıkar
    • Mirasın kötü olduğu şeylerde iyi bir iş çıkar

3
Teşekkür ederim! İşleyecek çok şey var. Kendimi kötü hissetmiyorum - içgörüyü çok takdir ediyorum. Kötü fikirleri taşırken daha kötü hissederdim. :) (Stackexchange'in tüm noktasına gider!)
Rob

1
@RobY Bir şey değil! Bu arada, daha önce duymadıysanız , LYAH'ı monadları (ve Haskell!) Öğrenmek için harika bir kaynak olduğu için tavsiye ederim , çünkü tonlarca örneği var (ve tonlarca örnek yapmanın en iyi yol olduğunu hissediyorum. Monadlarla mücadele).

Burada bir sürü var; Ben yorumları bataklık istemiyorum, ama birkaç yorum: # 2 land(flyAround(takeOff(new Flier<Duck>(duck))))çalışmıyor (en azından OO) çünkü bu inşaat Flier ayrıntılarını almak için kapsülleme kırılması gerektirir. Sınıftaki işlemleri zincirleyerek Flier'in detayları gizli kalır ve anlambilimini koruyabilir. Yani Haskell bir monad bağlandığı alanı olması nedeniyle benzer (a, M b)değil (M a, M b)bu yüzden monad "eylem" işlevine durumunu ortaya çıkarmak için yok.
Rob

# 1, ne yazık ki , Haskell'deki Monad'ın katı tanımını bulanıklaştırmaya çalışıyorum , çünkü Haskell'e bir şey eşlemenin büyük bir sorunu var: Java gibi bir yaya dilinde kolayca yapamayacağınız yapıcılar üzerinde kompozisyon da dahil olmak üzere fonksiyon kompozisyonu . Böylece unit(çoğunlukla) içerilen tip üzerinde yapıcı bindolur ve (çoğunlukla) "eylem" işlevlerini sınıfa bağlayan zımni bir derleme zamanı işlemi (yani erken bağlama) haline gelir . Birinci sınıf işlevleriniz veya bir İşlev <A, Monad <B>> sınıfınız varsa, bir bindyöntem geç bağlama yapabilir, ancak bu kötüye kullanımı bir sonraki adımda alacağım. ;)
Rob

# 3 katılıyorum ve bu onun güzelliği. Flier<Thing>Uçuş semantiğini kontrol ederse , o zaman uçuş semantiğini koruyan çok sayıda veri ve işlem ortaya koyabilirken, "monad"-spesifik semantikler gerçekten sadece zincirlenebilir ve kapsüllenmiş hale getirmekle ilgilidir. Bu endişeler, monad içindeki sınıfın endişelerini (ve kullandıklarım ile değil) ilgilendirmeyebilir: örneğin Resource<String>bir httpStatus özelliğine sahiptir, ancak String yoktur.
Rob

1

Öyleyse sorum şu: Aynı şekilde miras üzerine kompozisyonu tercih etmeye geldiğimiz gibi, miras üzerine monadları da tercih etmek mantıklı mı?

OO olmayan dillerde, evet. Daha geleneksel OO dillerinde hayır derim.

Sorun büyük diller yapamazsınız anlamına tip ihtisas sahibi kalmamasıdır Flier<Squirrel>ve Flier<Bird>farklı uygulamaları var. Böyle bir şey static Flier Flier::Create(Squirrel)(ve sonra her tür için aşırı yükleme) yapmanız gerekir . Bu da, yeni bir hayvan eklediğinizde bu türü değiştirmeniz ve muhtemelen çalışması için biraz kod çoğaltmanız gerektiği anlamına gelir.

Oh, ve Birkaç dilde (örneğin C #) public class Flier<T> : T {}yasadışı. İnşa etmeyecek bile. Çoğu, eğer tüm OO programcıları Flier<Bird>hala bir olmasını beklerdi Bird.


yorum için teşekkürler. Ben biraz daha düşünceler var, ama sadece önemsiz, Flier<Bird>parametreli bir kapsayıcı olsa da, hiç kimse bir Bird(!?) List<String>Bir dize değil bir liste olarak kabul ediyorum .
Rob

@RobY - sadece bir konteyner Flierdeğil . Bunu sadece bir konteyner olarak görüyorsanız, neden kalıtım kullanımının yerini alabileceğini hiç düşünesiniz ki?
Telastyn

Seni orada kaybettim ... Demek istediğim, monad gelişmiş bir konteyner. Animal / Bird / Penguingenellikle kötü bir örnektir, çünkü her türlü anlambilimi getirir. Pratik bir örnek, kullandığımız bir REST-ish monadıdır: (veya başka bir türün) Resource<String>.from(uri).get() Resourceüzerine anlambilim ekler String, bu yüzden a değil String.
Rob

@RobY - ama o zaman hiçbir şekilde miras ile ilgili değil.
Telastyn

Dışında farklı bir muhafaza. String'i Resource içine koyabilirim veya bir ResourceString sınıfı soyutlayabilir ve miras kullanabilirsiniz. Benim düşüncem şudur ki, bir sınıfı zincirleme konteynere koymak, soyut davranış için onu kalıtımla bir sınıf hiyerarşisine koymaktan daha iyi bir yoldur. Yani "hiçbir şekilde ilişkili" anlamında "değiştirilmesi / yok edilmesi" - evet.
Rob
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.