Okur monad çok karmaşık ve işe yaramaz görünüyor. Java veya C ++ gibi zorunlu bir dilde, yanılmıyorsam okuyucu monad için eşdeğer bir kavram yoktur.
Bana basit bir örnek verip bunu biraz netleştirebilir misin?
Okur monad çok karmaşık ve işe yaramaz görünüyor. Java veya C ++ gibi zorunlu bir dilde, yanılmıyorsam okuyucu monad için eşdeğer bir kavram yoktur.
Bana basit bir örnek verip bunu biraz netleştirebilir misin?
Yanıtlar:
Korkmayın! Okuyucu monad aslında o kadar karmaşık değildir ve kullanımı çok kolay bir yardımcı programa sahiptir.
Bir monad'a yaklaşmanın iki yolu vardır: sorabiliriz
İlk yaklaşımdan, okuyucu monad bazı soyut tiptedir
data Reader env a
öyle ki
-- Reader is a monad
instance Monad (Reader env)
-- and we have a function to get its environment
ask :: Reader env env
-- finally, we can run a Reader
runReader :: Reader env a -> env -> a
Peki bunu nasıl kullanacağız? Okuyucu monad, yapılandırma bilgilerini bir hesaplamadan (örtük) geçirmek için iyidir.
Bir hesaplamada çeşitli noktalarda ihtiyaç duyduğunuz bir "sabit" e sahip olduğunuzda, ancak gerçekten aynı hesaplamayı farklı değerlerle yapabilmek istiyorsanız, o zaman bir okuyucu monad kullanmalısınız.
Okuyucu monadları, OO insanlarının bağımlılık enjeksiyonu dediği şeyi yapmak için de kullanılır . Örneğin, negamax algoritması, iki oyunculu bir oyunda bir pozisyonun değerini hesaplamak için sıklıkla (yüksek oranda optimize edilmiş formlarda) kullanılır. Algoritmanın kendisi hangi oyunu oynadığınızı önemsemiyor, ancak oyunda "sonraki" pozisyonların hangileri olduğunu belirleyebilmeniz ve mevcut pozisyonun bir zafer pozisyonu olup olmadığını söyleyebilmeniz dışında.
import Control.Monad.Reader
data GameState = NotOver | FirstPlayerWin | SecondPlayerWin | Tie
data Game position
= Game {
getNext :: position -> [position],
getState :: position -> GameState
}
getNext' :: position -> Reader (Game position) [position]
getNext' position
= do game <- ask
return $ getNext game position
getState' :: position -> Reader (Game position) GameState
getState' position
= do game <- ask
return $ getState game position
negamax :: Double -> position -> Reader (Game position) Double
negamax color position
= do state <- getState' position
case state of
FirstPlayerWin -> return color
SecondPlayerWin -> return $ negate color
Tie -> return 0
NotOver -> do possible <- getNext' position
values <- mapM ((liftM negate) . negamax (negate color)) possible
return $ maximum values
Bu daha sonra herhangi bir sonlu, deterministik, iki oyunculu oyunla çalışacaktır.
Bu kalıp, gerçekten bağımlılık enjeksiyonu olmayan şeyler için bile kullanışlıdır. Finans alanında çalıştığınızı varsayalım, bir varlığı fiyatlandırmak için karmaşık bir mantık tasarlayabilirsiniz (bir türev ürünü), bu tamamen iyi ve iyidir ve kokuşmuş monadlar olmadan yapabilirsiniz. Ancak daha sonra, programınızı birden çok para birimiyle başa çıkacak şekilde değiştirirsiniz. Anında para birimleri arasında dönüştürme yapabilmeniz gerekir. İlk girişiminiz bir üst düzey işlevi tanımlamaktır
type CurrencyDict = Map CurrencyName Dollars
currencyDict :: CurrencyDict
spot fiyatlar almak için. Daha sonra bu sözlüğü kodunuzda çağırabilirsiniz .... ama bekleyin! Bu işe yaramaz! Para birimi sözlüğü değişmezdir ve bu nedenle yalnızca programınızın ömrü boyunca değil, derlendiği andan itibaren aynı olmalıdır ! Ee ne yapıyorsun? Okuyucu monadını kullanmak bir seçenek olabilir:
computePrice :: Reader CurrencyDict Dollars
computePrice
= do currencyDict <- ask
--insert computation here
Belki de en klasik kullanım durumu tercümanları uygulamaktır. Ancak buna bakmadan önce, başka bir işlevi tanıtmamız gerekiyor.
local :: (env -> env) -> Reader env a -> Reader env a
Tamam, yani Haskell ve diğer işlevsel diller lambda hesabına dayanmaktadır . Lambda hesaplamasının şöyle görünen bir sözdizimi vardır:
data Term = Apply Term Term | Lambda String Term | Var Term deriving (Show)
ve bu dil için bir değerlendirici yazmak istiyoruz. Bunu yapmak için, terimlerle ilişkili bağlamaların bir listesi olan bir ortamı takip etmemiz gerekecek (aslında bu, statik kapsam belirleme yapmak istediğimiz için kapanışlar olacaktır).
newtype Env = Env ([(String, Closure)])
type Closure = (Term, Env)
İşimiz bittiğinde, bir değer (veya bir hata) almalıyız:
data Value = Lam String Closure | Failure String
Öyleyse, tercümanı yazalım:
interp' :: Term -> Reader Env Value
--when we have a lambda term, we can just return it
interp' (Lambda nv t)
= do env <- ask
return $ Lam nv (t, env)
--when we run into a value, we look it up in the environment
interp' (Var v)
= do (Env env) <- ask
case lookup (show v) env of
-- if it is not in the environment we have a problem
Nothing -> return . Failure $ "unbound variable: " ++ (show v)
-- if it is in the environment, then we should interpret it
Just (term, env) -> local (const env) $ interp' term
--the complicated case is an application
interp' (Apply t1 t2)
= do v1 <- interp' t1
case v1 of
Failure s -> return (Failure s)
Lam nv clos -> local (\(Env ls) -> Env ((nv, clos) : ls)) $ interp' t2
--I guess not that complicated!
Son olarak, önemsiz bir ortamı geçerek kullanabiliriz:
interp :: Term -> Value
interp term = runReader (interp' term) (Env [])
Ve işte bu. Lambda hesabı için tamamen işlevsel bir yorumlayıcı.
Bunun hakkında düşünmenin diğer yolu sormaktır: Nasıl uygulanıyor? Cevap şudur: Okur monad aslında tüm monadlar arasında en basit ve en zarif olanıdır.
newtype Reader env a = Reader {runReader :: env -> a}
Okuyucu, işlevler için sadece süslü bir isimdir! Daha önce tanımlamıştık, runReaderpeki ya API'nin diğer bölümleri? Her Monadbiri aynı zamanda bir Functor:
instance Functor (Reader env) where
fmap f (Reader g) = Reader $ f . g
Şimdi, bir monad almak için:
instance Monad (Reader env) where
return x = Reader (\_ -> x)
(Reader f) >>= g = Reader $ \x -> runReader (g (f x)) x
ki bu çok korkutucu değil. askgerçekten basit:
ask = Reader $ \x -> x
ise localo kadar kötü değil:
local f (Reader g) = Reader $ \x -> runReader g (f x)
Tamam, öyleyse okuyucu monad sadece bir işlevdir. Neden Reader'a sahipsin? İyi soru. Aslında buna ihtiyacın yok!
instance Functor ((->) env) where
fmap = (.)
instance Monad ((->) env) where
return = const
f >>= g = \x -> g (f x) x
Bunlar daha da basit. Dahası, fonksiyonların sırasına göre asksadece idve localsadece fonksiyon kompozisyonu!
Reader, monad türü sınıfın belirli bir uygulaması olan bir işlev mi? Bunu daha önce söylemek kafamın biraz daha az karışmasına yardımcı olabilirdi. İlk önce anlamıyordum. Yarı yolda, "Oh, eksik değeri sağladığınızda size istenen sonucu verecek bir şeyi geri getirmenize izin veriyor" diye düşündüm. Bunun yararlı olduğunu düşündüm, ancak aniden bir işlevin tam olarak bunu yaptığını fark ettim.
localFonksiyonu olsa biraz daha açıklama ihtiyacı var ..
(Reader f) >>= g = (g (f x))?
x?
Reader monad'ın varyantlarının her yerde olduğunu kendi başıma keşfedene kadar senin gibi şaşkın olduğumu hatırlıyorum . Nasıl keşfettim? Çünkü üzerinde küçük varyasyonlar olduğu ortaya çıkan bir kod yazmaya devam ettim.
Örneğin, bir noktada tarihsel değerleri ele almak için bazı kodlar yazıyordum ; zamanla değişen değerler. Bunun çok basit bir modeli, zaman noktalarından zamanın o noktasındaki değere kadar olan işlevlerdir:
import Control.Applicative
-- | A History with timeline type t and value type a.
newtype History t a = History { observe :: t -> a }
instance Functor (History t) where
-- Apply a function to the contents of a historical value
fmap f hist = History (f . observe hist)
instance Applicative (History t) where
-- A "pure" History is one that has the same value at all points in time
pure = History . const
-- This applies a function that changes over time to a value that also
-- changes, by observing both at the same point in time.
ff <*> fx = History $ \t -> (observe ff t) (observe fx t)
instance Monad (History t) where
return = pure
ma >>= f = History $ \t -> observe (f (observe ma t)) t
ApplicativeEğer varsa o örneği aracı employees :: History Day [Person]ve customers :: History Day [Person]bunu yapabilirsiniz:
-- | For any given day, the list of employees followed by the customers
employeesAndCustomers :: History Day [Person]
employeesAndCustomers = (++) <$> employees <*> customers
Yani, Functorve Applicativebize geçmişleri ile çalışmak için düzenli, tarihsel olmayan fonksiyonları uyum sağlamasına olanak.
Monad örneği, sezgisel olarak işlev dikkate alınarak anlaşılır (>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c. Bir tür a -> History t bişlevi, bir değer ageçmişini eşleyen bir işlevdir b; örneğin, olabilir getSupervisor :: Person -> History Day Supervisorve getVP :: Supervisor -> History Day VP. Yani Monad örneği Historybunun gibi işlevler oluşturmakla ilgilidir; örneğin, sahip oldukları e-posta geçmişini getSupervisor >=> getVP :: Person -> History Day VPalan işlevdir .PersonVP
Bu Historymonad aslında tamamen aynı Reader. History t agerçekten aynıdır Reader t a(ile aynıdır t -> a).
Başka bir örnek: Son zamanlarda Haskell'de OLAP tasarımlarının prototipini oluşturuyorum . Buradaki fikirlerden biri, bir boyut kümesinin kesişimlerinden değerlere bir eşleme olan "hiperküp" fikridir. Yine başlıyoruz:
newtype Hypercube intersection value = Hypercube { get :: intersection -> value }
Hiperküpler üzerinde yaygın bir işlem, bir hiperküpün karşılık gelen noktalarına çok-yerli skaler fonksiyonlar uygulamaktır. Bunu, aşağıdakiler için bir Applicativeörnek tanımlayarak elde edebiliriz Hypercube:
instance Functor (Hypercube intersection) where
fmap f cube = Hypercube (f . get cube)
instance Applicative (Hypercube intersection) where
-- A "pure" Hypercube is one that has the same value at all intersections
pure = Hypercube . const
-- Apply each function in the @ff@ hypercube to its corresponding point
-- in @fx@.
ff <*> fx = Hypercube $ \x -> (get ff x) (get fx x)
Az önce Historyyukarıdaki kodu kopyaladım ve isimleri değiştirdim. Anlayabileceğiniz gibi, Hypercubeaynı zamanda adil Reader.
Devam ediyor. Örneğin, Readerbu modeli uyguladığınızda dil çevirmenleri de özetle :
ReaderaskReaderyürütme ortamı.localİyi bir benzetme, a'nın içinde "delikler" bulunan ve sizin neden bahsettiğimizi bilmenizi engelleyen bir Reader r atemsilidir . Sadece delikleri doldurmak için bir an sağladığınızda gerçek bir tane alabilirsiniz . Bunun gibi tonlarca şey var. Yukarıdaki örneklerde, "geçmiş", bir zaman belirtene kadar hesaplanamayacak bir değerdir, bir hiperküp, bir kesişme belirtene kadar hesaplanamayan bir değerdir ve bir dil ifadesi, Değişkenlerin değerlerini sağlayana kadar hesaplanmayacaktır. Aynı zamanda size neden aynı şey olduğuna dair bir sezgisellik verir , çünkü böyle bir fonksiyon sezgisel olarak eksik bir an'dır .aaarReader r ar -> aar
Yani Functor, Applicativeve Monadörnekleri, Reader"an aeksik olan " türünde herhangi bir şeyi modellediğiniz durumlar için çok yararlı bir genellemedir rve bu "eksik" nesneleri tamamlanmış gibi ele almanıza izin verir.
Yine aynı şeyi söylemenin başka bir yolu: Bir Reader r atüketir o şeydir rve üretir ave Functor, Applicativeve Monadörnekleri ile çalışmak için temel kalıplardır Readers. Functor= bir Readerbaşkasının çıktısını değiştiren yapmak Reader; Applicative= iki Readers'yi aynı girişe bağlayın ve çıkışlarını birleştirin; Monad= a'nın sonucunu inceleyin ve Readerbaşka bir tane oluşturmak için kullanın Reader. localVe withReaderişlevleri = bir hale Readerdeğiştirir diğerine girdi o Reader.
GeneralizedNewtypeDerivingtüretmek uzantısı Functor, Applicative, Monadbunların altında yatan türlerine göre newtypes için vs..
Java veya C ++ 'da herhangi bir değişkene her yerden sorunsuz olarak erişebilirsiniz. Kodunuz çok iş parçacıklı hale geldiğinde sorunlar görünür.
Haskell'de değeri bir işlevden diğerine aktarmanın yalnızca iki yolu vardır:
fn1 -> fn2 -> fn3işlevi fn2aralarından geçmesi parametre gerekmeyebilir fn1için fn3.Reader monad, yalnızca işlevler arasında paylaşmak istediğiniz verileri aktarır. İşlevler bu verileri okuyabilir, ancak değiştiremez. Okuyucu monadını yapan tek şey bu. Neredeyse hepsi. Gibi işlevler de vardır local, ancak ilk kez asksyalnızca bağlı kalabilirsiniz .
donotasyonu yazarken bulmanın çok kolay olmasıdır; bu, saf bir işleve dönüştürülmekten daha iyi olacaktır.
wherecümle ile eklenmişse , değişkenleri geçirmenin 3. yolu olarak kabul edilecek mi?