Maalesef matematiğimi gerçekten bilmiyorum, bu yüzden İşlevleri Uygulamalı tip sınıfında nasıl telaffuz edeceğimi merak ediyorum
Matematiğini bilmek ya da bilmemek burada büyük ölçüde alakasız, sanırım. Muhtemelen farkında olduğunuz gibi, Haskell soyut matematiğin çeşitli alanlarından, özellikle Kategori Teorisinden , functor ve monadlardan aldığımız birkaç bitlik terminolojiyi ödünç alır . Bu terimlerin Haskell'de kullanımı, biçimsel matematiksel tanımlardan biraz farklıdır, ancak genellikle yine de iyi tanımlayıcı terimler olacak kadar yakındırlar.
Applicative
Tip sınıfı arasındaki bir yere oturur Functor
ve Monad
bir benzer bir matematik temeli olması beklenebilir yüzden. Control.Applicative
Modülün dokümantasyonu şu şekilde başlar:
Bu modül, bir functor ve bir monad arasındaki bir yapıyı açıklar: saf ifadeler ve sıralama sağlar, ancak bağlanma yoktur. (Teknik olarak, güçlü bir gevşek monoidal funktor.)
Hmm.
class (Functor f) => StrongLaxMonoidalFunctor f where
. . .
Monad
Sanırım o kadar akılda kalıcı değil .
Tüm bunların temelde özetlediği şey, matematiksel olarak Applicative
özellikle ilginç olan herhangi bir konsepte karşılık gelmemesi , dolayısıyla Haskell'de kullanılma şeklini yakalayan hazır terimlerin ortalıkta yok olmasıdır. Öyleyse, matematiği şimdilik bir kenara koyun.
Ne diyeceğimizi (<*>)
bilmek istiyorsak, temelde ne anlama geldiğini bilmek yardımcı olabilir.
Öyleyse neyin var Applicative
, neyse ve neden buna böyle diyoruz?
Applicative
Pratikte ne anlama geliyor, keyfi işlevleri a Functor
. Maybe
(Muhtemelen en basit olan, önemsiz olmayan Functor
) ve Bool
(aynı şekilde, önemsiz olmayan en basit veri türü) kombinasyonunu düşünün .
maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not
İşlev , üzerinde çalışmaktan çalışmaya fmap
geçmemizi sağlar . Ama ya kaldırmak istersek ?not
Bool
Maybe Bool
(&&)
maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd' = fmap (&&)
Eh, bu bizim istediğimiz değil hiç ! Aslında, oldukça faydasız. Zeki olmayı deneyebilir ve Bool
arkadan bir başkasını gizlice sokabiliriz Maybe
...
maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)
... ama bu hiç iyi değil. Bir kere yanlış. Başka bir şey için, bu çirkin . Denemeye devam edebilirdik, ancak birden fazla argümandan oluşan bir işlevi keyfi bir üzerinde çalışmak için kaldırmanın bir yoluFunctor
olmadığı ortaya çıktı . Can sıkıcı!
Kullandığımız Öte yandan, kolayca yapabileceğini Maybe
'ın Monad
örneği:
maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
y' <- y
return (x' && y')
Şimdi, bu sadece basit bir işlevi çevirmek için çok zahmetli - bu yüzden Control.Monad
otomatik olarak yapmak için bir işlev sağlar liftM2
. Adındaki 2, tam olarak iki bağımsız değişkenin işlevleri üzerinde çalıştığı gerçeğini ifade eder; benzer işlevler 3, 4 ve 5 bağımsız değişken işlevleri için mevcuttur. Bu işlevler daha iyidir , ancak mükemmel değildir ve argüman sayısını belirtmek çirkin ve beceriksizdir.
Bu da bizi Applicative type sınıfını tanıtan makaleye getiriyor . Yazarlar esasen iki gözlem yapıyorlar:
- Çoklu argüman fonksiyonlarını a'ya kaldırmak
Functor
çok doğal bir şeydir
- Bunu yapmak, bir
Monad
Normal işlev uygulaması, terimlerin basit yan yana getirilmesiyle yazılmıştır, bu nedenle "kaldırılmış uygulamayı" olabildiğince basit ve doğal hale getirmek için, bu makale , uygulama için hazır bulundurulacak, içine kaldırılacak infix operatörleriniFunctor
ve bunun için gerekli olanı sağlamak için bir tür sınıfını tanıtmaktadır . .
Tüm bunlar bizi şu noktaya getiriyor: (<*>)
basitçe işlev uygulamasını temsil ediyor - öyleyse neden onu boşluk "yan yana operatörü" olarak yaptığınızdan farklı bir şekilde telaffuz ediyorsunuz?
Ancak bu çok tatmin edici değilse, Control.Monad
modülün aynı şeyi monadlar için de yapan bir işlev sağladığını gözlemleyebiliriz :
ap :: (Monad m) => m (a -> b) -> m a -> m b
ap
Elbette "başvur" un kısaltması nerede . Herhangi yana Monad
olabilir Applicative
ve ap
özellikler ikincisi de mevcut yalnızca alt kümesini ihtiyacı belki söyleyebiliriz eğer (<*>)
bir operatör değildi, bu çağrılmalıdır ap
.
Olaylara diğer yönden de yaklaşabiliriz. Functor
Kaldırma işlemi denir fmap
bunun bir genelleme olduğu için map
listelerinde operasyonu. Listelerde ne tür bir işlev nasıl çalışır (<*>)
? ap
Elbette listelerde ne işe yarıyor, ama bu kendi başına pek yararlı değil.
Aslında, listeler için belki daha doğal bir yorum var. Aşağıdaki tip imzasına baktığınızda aklınıza ne geliyor?
listApply :: [a -> b] -> [a] -> [b]
Listeleri paralel olarak sıraya dizme fikrinde çok çekici bir şey var, birinci işlevin her bir işlevi ikincinin karşılık gelen öğesine uygulanması. Maalesef eski dostumuz için Monad
, bu basit işlem , listeler farklı uzunluklarda ise monad yasalarını ihlal ediyor . Ama bir para cezası verir Applicative
, bu durumda genelleştirilmiş bir versiyonunu bir araya(<*>)
getirmenin bir yolu haline gelir , bu yüzden belki onu adlandırmayı hayal edebiliriz ?zipWith
fzipWith
Bu sıkıştırma fikri aslında bize tam bir çember getiriyor. Tekoidal işlevler hakkında daha önceki matematik konularını hatırlıyor musunuz? Adından da anlaşılacağı gibi, bunlar, her ikisi de tanıdık Haskell tipi sınıflar olan monoidlerin ve functorların yapısını birleştirmenin bir yoludur:
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Monoid a where
mempty :: a
mappend :: a -> a -> a
Bunları bir kutuya koyup biraz sallasanız nasıl görünürdü? Dan Functor
biz fikrini tutacağız onun tipi parametresinin yapısı bağımsız ve gelen Monoid
biz fonksiyonlarının genel formunu devam edeceğiz:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ?
mfAppend :: f ? -> f ? -> f ?
Gerçekten "boş" yaratmanın bir yolu olduğunu varsaymak istemiyoruz Functor
ve keyfi bir türden bir değer oluşturamayız, bu yüzden mfEmpty
as tipini düzelteceğiz f ()
.
Ayrıca mfAppend
tutarlı bir tür parametresine ihtiyaç duymaya zorlamak istemiyoruz , bu yüzden şimdi elimizde:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ()
mfAppend :: f a -> f b -> f ?
Sonuç türü ne için mfAppend
? Hakkında hiçbir şey bilmediğimiz iki rastgele tipimiz var, bu yüzden fazla seçeneğimiz yok. En mantıklı şey, ikisini birden saklamaktır:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ()
mfAppend :: f a -> f b -> f (a, b)
Bu noktada mfAppend
, artık açıkça zip
listelerin genelleştirilmiş bir versiyonu ve Applicative
kolayca yeniden yapılandırabiliriz :
mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)
Bu aynı zamanda bize bunun pure
a'nın özdeşlik unsuruyla ilgili olduğunu gösterir Monoid
, bu nedenle diğer iyi isimler bir birim değeri, boş bir işlem veya benzeri herhangi bir şey olabilir.
Bu uzun sürdü, özetlemek gerekirse:
(<*>)
sadece değiştirilmiş bir işlev uygulamasıdır, bu nedenle onu "ap" veya "uygula" olarak okuyabilir veya normal işlev uygulaması gibi tamamen kaldırabilirsiniz.
(<*>)
ayrıca zipWith
listeleri kabaca genelleştirir , böylece onu "zip functors with" olarak okuyabilirsiniz fmap
, "bir functor ile eşleme" şeklinde okumaya benzer .
İlki, Applicative
tip sınıfının amacına daha yakın - adından da anlaşılacağı gibi - bu yüzden tavsiye ederim.
Aslında, kaldırılan tüm uygulama operatörlerinin özgürce kullanılmasını ve telaffuz edilmemesini teşvik ediyorum :
(<$>)
, tek bağımsız değişkenli bir işlevi bir Functor
(<*>)
, çoklu argüman işlevini bir Applicative
(=<<)
, Monad
mevcut bir hesaplamaya giren bir işlevi bağlayan
Her üçü de, özünde, sadece normal işlev uygulaması, biraz baharatlanmış.