[英]Reader Monad clarification
我試圖理解讀者monad但似乎無法理解bind(>> =)在這個monad中的作用。
這是我正在分析的實現:
newtype Reader e a = Reader { runReader :: (e -> a) }
instance Monad (Reader e) where
return a = Reader $ \e -> a
(Reader r) >>= f = Reader $ \e -> runReader (f (r e)) e
(Reader r)
代替(Reader ra)
。 (f (re))
,它的目的是什么? 非常感謝幫助我。
我的第一個問題是,為什么Reader部分應用於綁定的左側?
(Reader r)
代替(Reader ra)
。
事實並非如此。 Reader
使用完全飽和,必須如此。 但是,我可以理解你的困惑......請記住,在Haskell中,類型和值位於不同的名稱空間中,並且使用data
或newtype
定義數據類型會在兩個名稱空間中將新名稱帶入范圍。 例如,請考慮以下聲明:
data Foo = Bar | Baz
這個定義綁定了三個名字, Foo
, Bar
和Baz
。 但是,等號左側的部分綁定在類型命名空間中,因為Foo
是一個類型,右側的構造函數綁定在值命名空間中,因為Bar
和Baz
本質上是值。
所有這些東西都有類型,這有助於可視化。 Foo
有一種 ,實質上是“類型級別的東西”,而Bar
和Baz
都有一種類型。 這些類型可以寫成如下:
Foo :: *
Bar :: Foo
Baz :: Foo
......其中*
是那種類型。
現在,考慮一個稍微復雜的定義:
data Foo a = Bar Integer String | Baz a
再一次,這個定義綁定了三個名字: Foo
, Bar
和Baz
。 Foo
再次位於類型命名空間中, Bar
和Baz
位於值命名空間中。 然而,它們的類型更精細:
Foo :: * -> *
Bar :: Integer -> String -> Foo a
Baz :: a -> Foo a
這里, Foo
是一個類型構造函數,因此它本質上是一個類型級函數,它接受一個類型( *
)作為參數。 同時, Bar
和Baz
是接受各種值作為參數的價值級函數。
現在,回到Reader
的定義。 暫時避免使用記錄語法,我們可以按如下方式重新表述:
newtype Reader r a = Reader (r -> a)
這會綁定類型命名空間中的一個名稱和值命名空間中的一個名稱,但令人困惑的部分是它們都被命名為Reader
! 但是,在Haskell中完全允許這樣做,因為名稱空間是分開的。 在這種情況下,每個Reader
都有一種/類型:
Reader :: * -> * -> *
Reader :: (r -> a) -> Reader r a
請注意,類型級Reader
有兩個參數,但值級Reader
只接受一個。 當您對值進行模式匹配時,您正在使用值級構造函數(因為您正在解構使用相同構造函數構建的值),並且該值僅包含一個值(因為它必須是,因為Reader
是一個newtype
),所以模式只綁定一個變量。
在這部分定義中發生了什么:
(f (re))
,它的目的是什么?
Reader
本質上是一種組成許多函數的機制,這些函數都采用相同的參數。 這是一種避免必須在任何地方處理值的方法,因為各種實例將自動執行管道。
要理解>>=
for Reader
的定義,讓我們專門研究>>=
to Reader
的類型:
(>>=) :: Reader r a -> (a -> Reader r b) -> Reader r b
為了清楚起見,我們還可以將Reader ra
擴展為r -> a
,以便更好地直觀了解類型的實際含義 :
(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b)
為了討論,讓我們在這里命名參數:
(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b)
(>>=) f g = ...
讓我們暫時考慮一下。 我們給出了兩個功能, f
和g
,我們預計將產生,其產生類型的值的函數b
從類型的值a
。 我們只有一種方法來產生一個b
,那就是通過調用g
。 但是為了打電話給g
,我們必須有一個a
,而我們只有一種方法來獲得a
:call f
! 我們可以調用f
,因為它只需要一個我們已經擁有的r
,所以我們可以開始將函數連接在一起以生成我們需要的b
。
這有點令人困惑,因此可以直觀地看到這種價值流:
+------------+
| input :: r |
+------------+
| |
v |
+--------------+ |
| f input :: a | |
+--------------+ |
| |
v v
+------------------------+
| g (f input) input :: b |
+------------------------+
在Haskell中,這看起來像這樣:
f >>= g = \input -> g (f input) input
...或者,重新命名一些東西以匹配您問題中的定義:
r >>= f = \e -> f (r e) e
現在,我們需要重新引入一些包裝和解包,因為真正的定義是在Reader
類型上,而不是(->)
直接。 這意味着我們需要添加Reader
包裝器和runReader
解包Reader
一些用法,否則定義是相同的:
Reader r >>= f = Reader (\e -> runReader (f (r e)) e)
在這一點上,你可以檢查你的直覺: Reader
是一種在很多函數之間繞過一個值的方法,在這里我們組成了兩個函數r
和f
。 因此,我們應該需要傳遞的值的兩倍,這是我們做的:有兩個用途e
上述定義。
為了回答你的功能, Monad
是類型的類 * -> *
。
[Int] :: *
=整數列表 [] :: * -> *
= list Reader ea :: *
=具有環境e的讀者導致a Reader e :: * -> *
=環境e的讀者 Reader :: * -> * -> *
=讀者 當我們說Reader
有一個monad實例時 ,我們很糟糕,當我們的意思是任何環境的Reader
都有一個monad實例 。
(同樣,當w
是Monoid
時, Writer w
是Monad
, Writer
不是Monad
)。
要回答你的第二個問題,就Reader ea
和函數e -> a
更容易思考。
函數具有相同的monad定義,但沒有newtype
包裝器和unwrappers。 認為Reader = (->)
:
instance Monad ((->) e) where
return x = \_ -> x -- alternatively, return = const !
r >>= f = \e -> f (r e) e
然而, join
定義可能是最有見地的:
join :: Reader e (Reader e a) -> Reader e a
但是有裸功能:
join :: (e -> e -> a) -> (e -> a)
join f e = f e e
如果我們為Reader
編寫它,我們必須在正確的位置添加runReader
和Reader
(並將變量綁定移動到RHS上的lambda):
join f = Reader $ \e -> runReader (runReader f e) e
沒有部分申請。 Reader
是一個newtype
定義,它''包含' 一個值( runReader
),它是e -> a
類型的函數。 因此, Reader r
只是模式匹配Reader
包裝器中的功能。 r
綁定到e -> a
類型的函數。
f
是一個函數,就像r
。 re
使用值e
調用函數r
,然后使用該函數調用的結果調用f
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.