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, runReader
peki ya API'nin diğer bölümleri? Her Monad
biri 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. ask
gerçekten basit:
ask = Reader $ \x -> x
ise local
o 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 ask
sadece id
ve local
sadece 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.
local
Fonksiyonu 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
Applicative
Eğ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, Functor
ve Applicative
bize 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 b
işlevi, bir değer a
geçmişini eşleyen bir işlevdir b
; örneğin, olabilir getSupervisor :: Person -> History Day Supervisor
ve getVP :: Supervisor -> History Day VP
. Yani Monad örneği History
bunun gibi işlevler oluşturmakla ilgilidir; örneğin, sahip oldukları e-posta geçmişini getSupervisor >=> getVP :: Person -> History Day VP
alan işlevdir .Person
VP
Bu History
monad aslında tamamen aynı Reader
. History t a
gerç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 History
yukarıdaki kodu kopyaladım ve isimleri değiştirdim. Anlayabileceğiniz gibi, Hypercube
aynı zamanda adil Reader
.
Devam ediyor. Örneğin, Reader
bu modeli uyguladığınızda dil çevirmenleri de özetle :
Reader
ask
Reader
yürütme ortamı.local
İyi bir benzetme, a'nın içinde "delikler" bulunan ve sizin neden bahsettiğimizi bilmenizi engelleyen bir Reader r a
temsilidir . 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 .a
a
a
r
Reader r a
r -> a
a
r
Yani Functor
, Applicative
ve Monad
örnekleri, Reader
"an a
eksik olan " türünde herhangi bir şeyi modellediğiniz durumlar için çok yararlı bir genellemedir r
ve bu "eksik" nesneleri tamamlanmış gibi ele almanıza izin verir.
Yine aynı şeyi söylemenin başka bir yolu: Bir Reader r a
tüketir o şeydir r
ve üretir a
ve Functor
, Applicative
ve Monad
örnekleri ile çalışmak için temel kalıplardır Reader
s. Functor
= bir Reader
başkasının çıktısını değiştiren yapmak Reader
; Applicative
= iki Reader
s'yi aynı girişe bağlayın ve çıkışlarını birleştirin; Monad
= a'nın sonucunu inceleyin ve Reader
başka bir tane oluşturmak için kullanın Reader
. local
Ve withReader
işlevleri = bir hale Reader
değiştirir diğerine girdi o Reader
.
GeneralizedNewtypeDeriving
türetmek uzantısı Functor
, Applicative
, Monad
bunları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 -> fn3
işlevi fn2
aralarından geçmesi parametre gerekmeyebilir fn1
iç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 asks
yalnızca bağlı kalabilirsiniz .
do
notasyonu yazarken bulmanın çok kolay olmasıdır; bu, saf bir işleve dönüştürülmekten daha iyi olacaktır.
where
cümle ile eklenmişse , değişkenleri geçirmenin 3. yolu olarak kabul edilecek mi?