[英]How to define the `pure` function on `Applicative`?
《第一原理的Haskell編程》一書有一項練習,要求我舉例說明Constant a
上的Applicative
。
我嘗試了這個:
newtype Constant a b =
Constant { getConstant :: a }
deriving (Eq, Ord, Show)
instance Functor (Constant a) where
fmap f (Constant x) = Constant x
instance Monoid a => Applicative (Constant a) where
pure x = Constant { getConstant = x }
Constant { getConstant = x } <*> Constant { getConstant = y } =
Constant { getConstant = x <> y }
這不會編譯。 我潛入了Data.Functor.Constant
的源代碼 ,它具有以下定義:
pure _ = Constant mempty
即使閱讀了本書中的“應用程序”一章,我也不真正理解Applicative
及其pure
功能。
我知道這可能是一個大問題,但有人可以解釋純粹做什么,以及為什么他們只定義pure
使用一些mempty
在Monoid
? 以及為什么這個:
pure x = Constant { getConstant = x }
不編譯? 我以為pure
會將a
類型a
某個值轉換為fa
類型a
某個值,並且在此示例中f
只是Constant
。 為什么它與純粹的Identity a
?
您的主要麻煩在於種類。 我將嘗試為您解釋為什么您的代碼不起作用,但有關更多詳細信息,我建議閱讀有關種類的出色文章 。
當您詢問有關ghci中的Applicative的信息時,請考慮返回的第一行:
λ> :info Applicative
class Functor f => Applicative (f :: * -> *) where
看到f :: * -> *
位嗎? 它告訴您期望的種類。 對種類一無所知? 我會給你最簡單的方法。 每次添加參數類型時,基本上就是在告訴您需要“另一種類型來構建類型”。
例如,當您說Maybe a
,您說“要擁有Maybe,我需要一個a”。 或者,當您編寫Either ab
,您說“我的Either類型取決於類型a和類型b”。 您可以通過在ghci中使用:kind
或:k
來獲取有關種類的信息。 考慮:
λ> :kind Bool
Bool :: *
λ> :kind Maybe
Maybe :: * -> *
λ> :kind Either
Either :: * -> * -> *
每個“ *”代表一種類型。 Bool
是一種簡單的類型。 Maybe
所有人都期待另一種類型。 Either
所有人都期待另一種類型。 請注意輸入時的區別:
λ> :kind Maybe Bool
Maybe Bool :: *
現在考慮:
λ> :kind Either Bool
Either Bool :: * -> *
看到這種* -> *
嗎? 當詢問有關Applicative
信息時,這正是我們所看到的。 (對Functor
和Monad
也有同樣的期望)。
這意味着typeclass將僅對最新的參數類型進行操作。 對於Either
也是如此。 如你看到的:
λ> let fun = (++ " !")
λ> fun <$> Left "Oops"
Left "Oops"
這什么也不做,因為Functor
的Either
是不是這兩種類型的任一函子:它只有在它的最后型(仿函數b
的Either ab
)。 使用簡單的Functor
和fmap
(或此處的infix版本<$>
),我只能在Either ab
的b上進行操作,這就是為什么這個可以起作用的原因:
λ> fun <$> Right "Oops"
Right "Oops !"
現在,回到您想做的事情。 您有一個新的newtype Constant ab
,因此Constant
具有* -> * -> *
。 現在讓我們來看一下:info Applicative
的第二行,它將為我們提供pure
的簽名:
class Functor f => Applicative (f :: * -> *) where
pure :: a -> f a
現在,始終考慮到這一點:在a
在簽名pure
是不是 a
的Constant ab
。 更糟糕的是,在這個例子中, a
在pure
是b
你的Constant
。 因為如果您考慮善意,並且專門研究此簽名,則會得到:
pure :: b -> Constant a b
但這不是您在做什么,不是嗎? 新類型中存儲的是a
類型。 並且您正在嘗試將類型b
放入內部。 由於a
和b
可以不同,所以它將不起作用。
至於“純粹的作用”的“大問題”,這確實是一個問題,我將給您一個答案的開始。
pure
是一種使朴素的值a
進入Applicative f
。 如簽名所示: a -> fa
。 那沒有幫助嗎? 好的,將您的Applicative
視為“上下文”(我使用了一個非常籠統的詞,因為Applicatives是一個非常籠統的概念)。 “上下文”可能是:我正在一個可能失敗的世界( Maybe
)中工作。 可能是:我在一個對一個問題有很多答案(即List
或[]
)的世界中工作。 它可以有很多很多的東西-在您的示例中,這是一個上下文,在此上下文中,任何內容都不會被計算,並且常量將始終返回。 問題在於,在您的示例中,不可能“猜測”什么是常數。
如我們所見,常量(您的上下文)不是value 。 這不是a
的pure
。 它是f
的一部分。 這就是為什么實現使用Monoid
和mempty
:您需要一種獲取默認上下文的方法,並且Monoid
始終具有可用的默認值。
最后, Applicative
很難 。 因此,不立即了解它們是完全正常的。 專注於閱讀類型,嘗試了解編譯器告訴您的內容,它將變得更加容易。 慢慢地重讀本章,慢慢來。
看類型。
data Const a b = MkConst a
例如,
> MkConst 1 :: Const Int Bool
> MkConst 1 :: Const Int Float
> MkConst 1 :: Const Int [(Integer, Maybe [()])]
下一個,
instance Functor (Const a) where
fmap f (MkConst x) = MkConst x
什么? .... 可是等等; 其實是
instance Functor (Const a) where
-- fmap :: (b -> c) -> (Const a b) -> (Const a c)
fmap f (MkConst x ) = (MkConst x )
現在更清楚了,不是嗎? Const a
是一個函子。 在容器范式下,在Functor f => fb
, f
是外殼, b
是內部的東西。 而f ~ Const a
是空殼 。 懷着什么本來可以,但是沒有。 但這僅僅是文學。 在日常的范式和隱喻下,事情不必說得通。 唯一重要的是-類型-適合。
不要試圖把所有的事情都弄清楚。 把它放在紙上。 這是非常重要的建議,我希望自己早一點。 不要跳過任何事情; 根據需要重復多次。
同樣的,
instance Monoid a => Applicative (Const a) where
-- pure :: Applicative f => b -> f b
-- pure :: Applicative (Const a) => b -> (Const a b)
pure x = (MkConst x )
等一下 是x :: a
還是x :: b
? 擦在其中。 它是后者,但您要使其成為前者。
Const
是空殼 ,還記得嗎? 即使將x
在其中,將x
放置在此處仍會保持空白。 換句話說, x
本身被忽略,只有它的類型起作用:
-- pure :: (Monoid a, Applicative (Const a)) =>
-- b -> (Const a b)
pure x = (MkConst mempty )
(我在這里為類型和數據構造函數使用了不同的名稱,以消除歧義,這一開始可能會造成混淆。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.