簡體   English   中英

讀者Monad澄清

[英]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
  1. 我的第一個問題是,為什么Reader部分應用於綁定的左側? (Reader r)代替(Reader ra)
  2. 在這部分定義中發生了什么: (f (re)) ,它的目的是什么?

非常感謝幫助我。

我的第一個問題是,為什么Reader部分應用於綁定的左側? (Reader r)代替(Reader ra)

事實並非如此。 Reader使用完全飽和,必須如此。 但是,我可以理解你的困惑......請記住,在Haskell中,類型和值位於不同的名稱空間中,並且使用datanewtype定義數據類型會在兩個名稱空間中將新名稱帶入范圍。 例如,請考慮以下聲明:

data Foo = Bar | Baz

這個定義綁定了三個名字, FooBarBaz 但是,等號左側的部分綁定在類型命名空間中,因為Foo是一個類型,右側的構造函數綁定在值命名空間中,因為BarBaz本質上是值。

所有這些東西都有類型,這有助於可視化。 Foo一種 ,實質上是“類型級別的東西”,而BarBaz都有一種類型。 這些類型可以寫成如下:

Foo :: *
Bar :: Foo
Baz :: Foo

......其中*是那種類型。

現在,考慮一個稍微復雜的定義:

data Foo a = Bar Integer String | Baz a

再一次,這個定義綁定了三個名字: FooBarBaz Foo再次位於類型命名空間中, BarBaz位於值命名空間中。 然而,它們的類型更精細:

Foo :: * -> *
Bar :: Integer -> String -> Foo a
Baz :: a -> Foo a

這里, Foo是一個類型構造函數,因此它本質上是一個類型級函數,它接受一個類型( * )作為參數。 同時, BarBaz是接受各種值作為參數的價值級函數。

現在,回到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             =  ...

讓我們暫時考慮一下。 我們給出了兩個功能, fg ,我們預計將產生,其產生類型的值的函數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是一種在很多函數之間繞過一個值的方法,在這里我們組成了兩個函數rf 因此,我們應該需要傳遞的值的兩倍,這是我們做的:有兩個用途e上述定義。

為了回答你的功能, Monad是類型的 * -> *

  • [Int] :: * =整數列表
  • [] :: * -> * = list
  • Reader ea :: * =具有環境e的讀者導致a
  • Reader e :: * -> * =環境e的讀者
  • Reader :: * -> * -> * =讀者

當我們說Reader有一個monad實例時 ,我們很糟糕,當我們的意思是任何環境的Reader都有一個monad實例

(同樣,當wMonoid時, Writer wMonadWriter不是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編寫它,我們必須在正確的位置添加runReaderReader (並將變量綁定移動到RHS上的lambda):

join f = Reader $ \e -> runReader (runReader f e) e
  1. 沒有部分申請。 Reader是一個newtype定義,它''包含' 一個值( runReader ),它是e -> a類型的函數。 因此, Reader r只是模式匹配Reader包裝器中的功能。 r綁定到e -> a類型的函數。

  2. f是一個函數,就像r re使用值e調用函數r ,然后使用該函數調用的結果調用f

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM