2017-10-13 67 views
9

我在閱讀Purescript通過示例並介紹了讀者單元的部分。示例如下:瞭解閱讀器monad

createUser :: Reader Permissions (Maybe User) 
createUser = do 
    permissions <- ask 
    if hasPermission "admin" permissions 
    then map Just newUser 
    else pure Nothing 

對我而言,令人困惑的部分是ask函數。簽名是:

ask :: forall r. Reader r r 

它看起來好像它創建了一個讀者憑空

當我念叨State單子,它與它的get功能相同的概念。並解釋如下文字:

該狀態是由狀態monad的數據構造函數隱藏的函數參數實現的,所以沒有明確的引用來傳遞。

我猜這是關鍵,而同樣的事情,與讀者發生在這裏,但我不明白它是如何工作...

在上面的例子中運行通過runReader,如何提供的值是否突然顯示爲ask的結果? Haskell文檔ask說:檢索monad環境。但我的困惑是從哪裏?我看到它的方式,價值傳遞到runReader,得到存儲某處,並得到它 - 你叫ask ...但這是沒有意義的。

雖然示例是Purescript,但我猜猜任何Haskell識字人員也能夠回答,因此Haskell標籤。

+2

'Reader a b'是'a - > b'的一個包裝。所以,問問::。讀者a'最終只是'forall a'類型的一個值。 a - > a',並在其周圍包裝。 –

回答

8

我目前沒有PureScript環境,所以我會嘗試從Haskell的角度回答,並希望它有所幫助。

一個讀者真的只是一個圍繞一個函數「包裝」,所以當你得到一個Reader r r,你真的只從rr吸引讀者;換句話說,功能r -> r

可以召喚功能憑空的,因爲,如果你是一個柏拉圖主義者,我想他們永遠存在......

當您使用do符號,你是「單子」內,所以上下文r是隱含的。換句話說,您調用返回r值的函數,並且當您使用箭頭時,您只需獲取該上下文。

+2

Upvoted。從稀薄的空氣中召喚這樣一個「r→r」功能的方式可能不值一提,就是說出咒語「id」。 (而'id'是_only_這樣的函數,這要歸功於參數性。) –

+0

好的,所以在我的情況下,Reader是Permissions - > Maybe User的包裝。當我運行'runreader createUser permissions'時,'createUser'正文中的'ask'如何知道返回與我傳遞給'runReader'相同的'permissions'?我敢肯定,這個問題是完全荒謬的,因爲我看着這一切都是錯誤的......但請嘗試幫助我伸直我的大腦。 – kaqqao

+1

@kaqqao'Reader Permissions(Maybe User)'只是'Permissions - >(Maybe User)'上的'包裝器',所以你的整個'createUser''值'實際上是一個函數(但函數是值,所以這很酷)。當你調用'runReader'時,你不僅必須傳遞'createUser',而且還要傳遞'r'的值 - 在這種情況下是'Permissions'值。 'runReader'然後用你傳遞的'Permissions'值來調用包裝函數。 HTH。 –

1

你可以通過執行一些替換來說服你自己,它的工作原理。首先看看createUser的簽名。讓我們「展開」的Reader定義:

createUser :: Reader Permissions (Maybe User) 
{- definition of Reader -} 
createUser :: ReaderT Permissions Identity (Maybe User) 

ReaderT類型只有一個數據構造:ReaderT (r -> m a),這意味着createUser是計算結果爲ReaderT (Permissions -> Identity (Maybe User))類型的值的術語。正如你所看到的,它只是一個標有ReaderT的函數。它無需憑空創建任何內容,但在調用該函數時將獲得類型Permissions的值。

現在我們來看看您遇到的問題。你知道do符號只是語法糖,並表達:

do permissions <- ask 
    if hasPermission "admin" permissions 
    then map Just newUser 
    else pure Nothing 

desugars到

ask >>= \permissions -> 
    if hasPermission "admin" permissions 
    then map Just newUser 
    else pure Nothing 

要理解這是什麼一樣,你將不得不查找ask>>=pure定義爲ReaderT。讓我們進行另一輪取代:

ask >>= \permissions -> ... 
{- definition of ask for ReaderT -} 
ReaderT pure >>= \permissions -> ... 
{- definition of >>= for ReaderT -} 
ReaderT \r -> 
    pure r >>= \a -> case (\permissions -> ...) a of ReaderT f -> f r 
{- function application -} 
ReaderT \r -> 
    pure r >>= \a -> 
    case (if hasPermission "admin" a 
      then map Just newUser 
      else pure Nothing) of ReaderT f -> f r 
{- definition of pure for Identity -} 
ReaderT \r -> 
    Identity r >>= \a -> 
    case (if hasPermission "admin" a 
      then map Just newUser 
      else pure Nothing) of ReaderT f -> f r 
{- definition of >>= for Identity -} 
ReaderT \r -> 
    (\a -> 
    case (if hasPermission "admin" a 
      then map Just newUser 
      else pure Nothing) of ReaderT f -> f r) r 
{- function application -} 
ReaderT \r -> 
    case (if hasPermission "admin" r 
     then map Just newUser 
     else pure Nothing) of ReaderT f -> f r 

正如你所看到的,createUser顯然只是ReaderT的線程通過你的表達式值(「環境」)包裹的功能。 runReader解開函數並用提供的參數調用它:

runReader :: forall r a. Reader r a -> r -> a 
runReader (ReaderT f) r = f r