簡體   English   中英

翻轉函子實例 Haskell

[英]Flip functor instance Haskell

我需要為Flip數據類型編寫 Functor 實例:

data K a b = K a
newtype Flip f a b = Flip (f b a) deriving (Eq, Show)

instance Functor (Flip K a) where
fmap=undefined

class給我的解決方案是:

instance Functor (Flip K a) where
    fmap f (Flip (K b)) = Flip (K (f b))

我真的不明白這里發生了什么,我開始懷疑我對數據類型和仿函數的整體理解。 我的理解是這樣的(如果有任何錯誤請糾正我):

  • K是一種將 2 個參數轉換為結構K a (僅保留第一個參數)的數據類型
  • Flip是一種數據類型,它將 3 arguments 轉換為一個結構
  • 因為在fmap:: (a-> b) -> fa -> fb中, fa有種類* ,要編寫Flip的 Functor 實例,我們將其寫在Flip的最后一個類型上。 aka fa在某種程度上是“常量”,我們為類型b編寫仿函數。 我會寫這樣的東西:
instance Functor (Flip f a) where
   fmap f (Flip x y z) = fmap Flip x y (f z)

我知道那是完全錯誤的,但我不確定為什么。

另外,為什么我們要將K帶入 Flip 的 Functor 實例中? 有人可以徹底解釋提出這個解決方案的過程以及為什么它是正確的嗎?

  • K是一種將 2 個參數轉換為結構K a (僅保留第一個參數)的數據類型

這不太對。 K ab是一種使用兩個參數形成的數據類型,但說它“將它們變成”任何東西並不正確。 相反,它只是向世界聲明現在存在一種新類型: K ab “所以呢?” 你可能會問。 那么,數據類型的后半部分定義了如何生成這種類型的新值。 那部分說,“你可以用這個 function 創建一個類型為K ab的新值,我將調用K類型a -> K ab 。” 認識到類型K構造函數K之間的區別非常重要。

因此,並不是K “只保留第一個參數”——而是構造函數K (它是一個函數)恰好沒有采用任何類型為b的 arguments。


  • Flip是一種數據類型,它將 3 arguments 轉換為一個結構

正如上面所說,這不太對。 Flip聲明聲明可以有Flip fab類型的值,唯一的方法是使用具有fba -> Flip fab類型的構造函數Flip

如果您想知道我是如何得出構造函數KFlip的類型簽名的,它實際上並不神秘,您可以通過在 GHCi 中鍵入:t K:t Flip來仔細檢查。 這些類型完全根據數據類型聲明的右側分配。 另外,請注意類型名稱和構造函數不必相同 例如,考慮這種數據類型:

data Foo a = Bar Int a | Foo String | Baz a a

這聲明了一個具有三個構造函數的類型Foo a

Bar :: Int -> a -> Foo a
Foo :: String -> Foo a
Baz :: a -> a -> Foo a

基本上,構造函數名稱后面的每個類型都是 arguments,按順序排列到構造函數。


  • 因為在fmap:: (a-> b) -> fa -> fb中, fa有種類* ,要編寫Flip的 Functor 實例,我們將其寫在Flip的最后一個類型上。 aka fa在某種程度上是“常量”,我們為類型b編寫仿函數。

這基本上是對的! 你也可以說f有種類* -> * 因為Flip有種類(* -> *) -> * -> * -> * ,你需要給它提供兩個類型 arguments (第一個種類* -> *和第二個種類* )讓它正確種類。 前兩個 arguments 在實例中變為固定值(在某種程度上是“常量”)。


我會這樣寫:......我知道那是完全錯誤的,但我不確定為什么。

您的實例完全錯誤的原因是您將類型構造函數混淆了。 (Flip xyz)放在模式 position 中是沒有意義的,因為構造函數Flip只接受一個參數——記住,它的類型是Flip:: fba -> Flip fab :所以你想寫點東西像:

instance Functor (Flip f a) where
   fmap f (Flip fxa) = ...

現在,您要為...填寫什么? 你有一個值fxa:: fxa ,你有一個 function f:: x -> y ,你需要生成一個fya類型的值。 老實說,我不知道該怎么做。 畢竟,typ fxa值是多少? 我們不知道f是什么?!


另外,為什么我們要將K帶入Flip的 Functor 實例中? 有人可以徹底解釋提出這個解決方案的過程以及為什么它是正確的嗎?

我們在上面看到我們不能為任意的f編寫Functor實例,但我們可以做的是為特定f編寫它。 事實證明, K就是這樣一個有效的特定f 讓我們嘗試讓它工作:

instance Functor (Flip K a) where
  fmap f (Flip kxa) = ...

f是任意的時,我們被困在這里,但現在我們知道kxa:: K xa 請記住,生成類型K xa的值的唯一方法是使用構造函數K 因此,這個值kxa必須是使用該構造函數創建的,因此我們可以將其拆分為: kxa ⩳ K x' where x':: x 讓我們先輸入 go 並將其放入我們的模式中:

  fmap f (Flip (K x')) = ...

現在我們可以取得進展了! 我們需要生成一個Flip K ay類型的值。 唔。 生成Flip類型值的唯一方法是使用Flip構造函數,所以讓我們從這里開始:

  fmap f (Flip (K x')) = Flip ...

Flip K ay類型的Flip構造函數采用K ya類型的值。 產生其中之一的唯一方法是使用K構造函數,所以讓我們添加:

  fmap f (Flip (K x')) = Flip (K ...)

K ya類型的K構造函數采用類型y的值,因此我們需要在此處提供類型y的值。 我們有一個值x':: x和一個 function f:: x -> y 將第一個插入第二個可以得到我們需要的值:

  fmap f (Flip (K x')) = Flip (K (f x'))

只需將x'重命名為b ,您就擁有了老師提供的代碼。

DDub在他們的回答中寫道:

你有一個值fxa:: fxa ,你有一個 function f:: x -> y ,你需要生成一個fya類型的值。 老實說,我不知道該怎么做。 畢竟,什么是fxa類型的值? 我們不知道f是什么?!

我同意,但我想補充一點。 你的老師關於如何處理這個問題的想法很酷(當你試圖寫下一些反例時,像K這樣的東西會派上用場,比如這里),但是,我認為我們可以使這段代碼更廣泛。 我使用Data.Bifunctor

那么,什么是Bifunctor 它們正如它們的名字所說: * -> * -> *類型(我們有時也稱其為雙函子,但它們不是一回事),它允許映射到它的兩個 arguments(來自源代碼的片段):

class Bifunctor p where
  -- | Map over both arguments at the same time.
  --
  -- @'bimap' f g ≡ 'first' f '.' 'second' g@
  bimap :: (a -> b) -> (c -> d) -> p a c -> p b d
  bimap f g = first f . second g
  {-# INLINE bimap #-}

  -- | Map covariantly over the first argument.
  --
  -- @'first' f ≡ 'bimap' f 'id'@
  first :: (a -> b) -> p a c -> p b c
  first f = bimap f id
  {-# INLINE first #-}

  -- | Map covariantly over the second argument.
  --
  -- @'second' ≡ 'bimap' 'id'@
  second :: (b -> c) -> p a b -> p a c
  second = bimap id
  {-# INLINE second #-}

所以,這就是我 go 對此的看法:

instance Bifunctor f => Functor (Flip f a) where
    fmap x2y (Flip fxa) = Flip (first x2y fxa)

說到你老師的代碼,這是一個非常好的想法,但由於K是一個Bifunctor ,所以范圍更窄:

instance Bifunctor K where
    bimap f _g (K a) = K (f a)

合法的:

bimap id id (K a) = K (id a) = id (K a)

正如上面鏈接中所說,只寫下bimap ,這是我們唯一需要擔心的法則。

我們只需要使用理智有用的命名,突然間一切都變得簡單明了(而不是折磨和扭曲):

data K b a = MkK b                     -- the type (K b a)      "is" just (b)
newtype Flip f a b = MkFlip (f b a)    -- the type (Flip f a b) "is" (f b a)
                     deriving (Eq, Show)

instance Functor (Flip K a) where
    -- fmap :: (b -> c) -> Flip K a b -> Flip K a c
    fmap g (MkFlip (MkK b)) = MkFlip (MkK (g b))
        --        MkK b :: K b a
        --  MkFlip (_ :: K b a) :: Flip K a b

現在看着這個,我們的腦海里甚至沒有一個問題出現,沒有一個我們無法立即解決的疑問。

在教學時對類型和數據構造函數使用相同的名稱,以及對“f”函數和“f”函數使用f ,這純粹是對學生的濫用

只有當您厭倦了所有的Mk並且覺得它們對您沒有任何幫助時,您才可以像專家通常做的那樣安全、輕松地扔掉它們。

暫無
暫無

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

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