簡體   English   中英

為 NonEmpty 編寫 Monad 和 Traversable 實例

[英]Writing Monad and Traversable instances for NonEmpty

我對實例定義有疑問。 我不能使定義符合單子和遍歷實例中的身份定律。 這里的類型:

data NotEmpty a = LastValue a | MidValue a (NotEmpty a) deriving Show
instance Functor NotEmpty where
    fmap f (LastValue a) = LastValue (f a) 
    fmap f (MidValue a b) = MidValue (f a) (fmap f b)
instance Applicative NotEmpty where
    pure = LastValue
    (LastValue f) <*> LastValue a = LastValue (f a)
    (LastValue f) <*> MidValue a b = pure (f a) 
    (MidValue f x) <*> MidValue a b =  MidValue (f a) (x <> b)
    (MidValue f x) <*> LastValue a =  LastValue (f a)
instance Monad NotEmpty where
    (LastValue a) >>= f = f a
    (MidValue a b) >>= f = f a
instance Foldable NotEmpty where
    foldMap f (LastValue a) = f a
    foldMap f (MidValue a b) = (f a) <> (foldMap f b)
instance Traversable NotEmpty where
    traverse f (LastValue a) = fmap LastValue (f a)
    traverse f (MidValue a b) = traverse f b

我試過了:

(MidValue a b) >>= f = MidValue a (b >>= f)

得到錯誤:

* Couldn't match type b' with a'
      b' is a rigid type variable bound by
        the type signature for:
          (>>=) :: forall a b. NotEmpty a -> (a -> NotEmpty b) -> NotEmpty b
        at LabWorks4.hs:87:19-21
      a' is a rigid type variable bound by
        the type signature for:
          (>>=) :: forall a b. NotEmpty a -> (a -> NotEmpty b) -> NotEmpty b
        at LabWorks4.hs:87:19-21
      Expected type: NotEmpty a
        Actual type: NotEmpty b
    * In the second argument of MidValue', namely (b >>= f)'
      In the expression: MidValue a (b >>= f)
      In an equation for `>>=':
          (MidValue a b) >>= f = MidValue a (b >>= f)
    * Relevant bindings include
        f :: a -> NotEmpty b (bound at LabWorks4.hs:88:24)
        b :: NotEmpty a (bound at LabWorks4.hs:88:17)
        a :: a (bound at LabWorks4.hs:88:15)
        (>>=) :: NotEmpty a -> (a -> NotEmpty b) -> NotEmpty b
          (bound at LabWorks4.hs:87:19)
   |
88 |     (MidValue a b) >>= f = MidValue a (b >>= f)
   |                                        ^^^^^^^

在遍歷中我嘗試過:

traverse f (MidValue a b) = fmap (MidValue a) (traverse f b)

我得到錯誤:

 * Couldn't match type b' with a'
      b' is a rigid type variable bound by
        the type signature for:
          traverse :: forall (f :: * -> *) a b.
                      Applicative f =>
                      (a -> f b) -> NotEmpty a -> f (NotEmpty b)
        at LabWorks4.hs:134:5-12
      a' is a rigid type variable bound by
        the type signature for:
          traverse :: forall (f :: * -> *) a b.
                      Applicative f =>
                      (a -> f b) -> NotEmpty a -> f (NotEmpty b)
        at LabWorks4.hs:134:5-12
      Expected type: f (NotEmpty a)
        Actual type: f (NotEmpty b)
    * In the second argument of fmap', namely (traverse f b)'
      In the expression: fmap (MidValue a) (traverse f b)
      In an equation for `traverse':
          traverse f (MidValue a b) = fmap (MidValue a) (traverse f b)
    * Relevant bindings include
        b :: NotEmpty a (bound at LabWorks4.hs:135:28)
        a :: a (bound at LabWorks4.hs:135:26)
        f :: a -> f b (bound at LabWorks4.hs:135:14)
        traverse :: (a -> f b) -> NotEmpty a -> f (NotEmpty b)
          (bound at LabWorks4.hs:134:5)
    |
135 |     traverse f (MidValue a b) = fmap (MidValue a) (traverse f b)

單子

您對 monad 定義的嘗試是不正確的。

(MidValue a b) >>= f = MidValue a (b >>= f)

假設您的MidValue ab的類型為NotEmpty Int 這意味着aInt類型,而bNotEmpty Int類型。 現在假設fInt -> NotEmpty String類型的 function 。 根據您的定義, b >>= f將是NonEmpty String類型。 這意味着在MidValue a (b >>= f)中, a類型為Int ,而(b >>= f)的類型為NonEmpty String 因此不可能以這種方式構造一個有效的類型。

相反,您應該定義一個串聯 function。 這可以通過為NotEmpty定義Semigroup的實例來完成,如下所示:

instance Semigroup (NotEmpty a) where
    LastValue a <> b   = MidValue a b
    MidValue a as <> b = MidValue a (as <> b)

接下來,您可以在Monad實例中使用(<>)

(MidValue a b) >>= f = f a <> (b >>= f)

可遍歷

您在此實例上的嘗試以與 monad 實例類似的方式失敗。

traverse f (MidValue a b) = fmap (MidValue a) (traverse f b)

在這里, MidValue a的類型為NotEmpty a -> NotEmpty a traverse fb的類型為Applicative f => f (NotEmpty b) 這兩種類型不可能與fmap的類型統一: Functor f => (a -> b) -> fa -> fb

因此,對於traverse f (MidValue ab) ,您已經發現需要遞歸調用traverse f for b 您可以為a做一些非常相似的事情,就像您在LastValue的情況下所做的那樣:

let a' = fmap pure (f a)
    b' = traverse f b

您現在有兩個具有相同類型的值: Applicative f => f (NotEmpty b) 最后一步是再次連接這兩個值。 最簡單的方法是使用liftA2 ,為此您需要將import Control.Applicative添加到文件頂部。 liftA2:: Applicative f => (a -> b -> c) -> fa -> fb -> f c可以通過提供 function a -> b -> c來組合兩個值,這兩個值都包裝在某個 Applicative 類型中a -> b -> c 在這種情況下,您只需將之前定義的串聯 function 提供給liftA2 該案的最終結果如下:

traverse f (MidValue a b) = let a' = fmap pure (f a)
                                b' = traverse f b
                            in liftA2 (<>) a' b'

您的代碼僅將f應用於列表的尾部。 正確的實現必須將f應用於頭部和尾部。

instance Monad NotEmpty where
    LastValue a >>= f = f a
    MidValue a b >>= f = f a <> (b >>= f)  -- assuming that NonEmpty is a Semigroup

instance Traversable NotEmpty where
    traverse f (LastValue a) = LastValue <$> f a
    traverse f (MidValue a b) = MidValue <$> f a <*> traverse f b

請注意, FunctorFoldableTraversable可以自動派生,因此您不需要自己編寫它們:

{-# LANGUAGE DeriveTraversable #-}

data NotEmpty a = LastValue a | MidValue a (NotEmpty a)
    deriving (Show, Functor, Foldable, Traversable)

暫無
暫無

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

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