[英]How do I make my custom data type an instance of Functor?
我有這種數據類型:
data F a b = F (a -> b)
我的任務是使這個類型成為Functor
class 的一個實例。我知道如何用通常的例子來做到這一點。 即Maybe
。
通常,給定的 function 應用於從包裝器中提取的值。 然后將結果替換為相同的包裝器類型。 在Maybe
類型為Just
的情況下的包裝器。
但是,我的數據類型表示a -> b
類型的 function。 我不明白如何將這個概念轉移到使用包裝的 function。創建這個數據類型的原因以及實現這個想法的方法都讓我迷失了。
我假設我沒有完全理解仿函數背后的概念,或者只是錯過了給定任務背后的意圖,我必須專門定義和使用上面的自定義數據類型......
請幫助我更正以下...
instance Functor (F a) where
我認為其他答案解決了一旦知道要編寫的實例后如何編寫實例的問題,但並沒有給出很好的直覺來確定您首先應該編寫的實例。 在這個答案中,我將嘗試這樣做。
我將從非常簡單的開始。 最簡單的事情可能是:一個甚至沒有任何值的仿函數真的非常容易fmap
。
data F0 a = F0
instance Functor F0 where fmap f F0 = F0
好的,讓我們稍微加強我們的游戲。 如果我們只有一個值呢? 好吧,我們只需將 function 應用於該值。 明智的。
data F1 a = F1 a
instance Functor F1 where fmap f (F1 a0) = F1 (f a0)
一旦我們得到兩個值,應該發生什么可能就不是那么明顯了。 但我要聲明,最自然的做法是將傳入的 function 應用於兩個值:
data F2 a = F2 a a
instance Functor F2 where fmap f (F2 a0 a1) = F2 (f a0) (f a1)
對於三個,我們要將f
應用於所有值:
data F3 a = F3 a a a
instance Functor F3 where fmap f (F3 a0 a1 a2) = F3 (f a0) (f a1) (f a2)
該模式繼續如下:無論其中一個類型包含多少a
, fmap
正確的做法是將f
應用於所有這些類型。
事實上,這個模式非常有規律,我認為我們可以將它抽象出來。 讓我們編寫具有零、一、二、三等值(而不是字段)的類型。 所以:
data N0
data N1 = N1_0
data N2 = N2_0 | N2_1
data N3 = N3_0 | N3_1 | N3_2
這些類型根本沒有參數化。 N3
不像F3 a
那樣是值的集合,它只是一個索引。 事實上,如果我們願意,我們可以寫一個索引 function:
index3 :: F3 a -> N3 -> a
index3 (F3 a0 a1 a2) n = case n of
N3_0 -> a0
N3_1 -> a1
N3_2 -> a2
不過,這有點令人沮喪。 我們需要為每個尺寸創建一個新的索引 function。 如果事情能更統一一點就好了——一旦我們有了索引類型,我們就想自動獲取一個容器,其中包含這些值作為索引。 我們該怎么做? 好吧,一種方法是只存儲索引 function 本身; 例如, index3
的預應用版本。 所以,對於我們的新類型F
,我們將有
F N3 a ~= N3 -> a
我們新的與大小無關的索引將只返回 function。舉個簡單的例子,之前我們可能會寫x = F3 5 16 (-92)
,現在我們將這樣寫:
x ~= \n -> case n of
N3_0 -> 5
N3_1 -> 16
N3_2 -> (-92)
您可以看到如何將其視為預應用索引。 快速思考fmap
,我們可以使用上面的定義: fmap f (F3 a0 a1 a2) = F3 (f a0) (f a1) (f a2)
。 所以:
fmap f x ~= \n -> case n of
N3_0 -> f 5
N3_1 -> f 16
N3_2 -> f (-92)
你可能想在這里暫停一下,想想你如何編寫一個 function 來對x
對一些特定的f
進行這種轉換,比如\v -> v+1
左右。 因此它的類型類似於foo:: (N3 -> Int) -> (N3 -> Int)
。 請注意, foo
不必進行任何模式匹配; x
,我們將作為它的第一個參數傳遞給foo
,它已經有感興趣的模式匹配,所以foo
應該能夠使用x
作為它的“模式匹配器”。
讓我們寫下我們的新類型:
data F n a = F (n -> a)
好的。 現在我已經帶您沿着花園小徑前進,我們可以對仿函數實例應該做什么有一個扎實的想法。 它應該將傳入的 function 應用於所有值,無論它們的索引如何!
憑借這種直覺,我懷疑實例本身會很自然地出現(盡管可能並不容易!很難弄清楚自然選擇是什么)。
instance Functor (F n) where
fmap f (F index) = F (\n -> {- ... -})
function 已經是Functor
的一個實例。 因此,您可以使用DeriveFunctor
擴展:
{-# LANGUAGE DeriveFunctor #-}
data F a b = F (a -> b) deriving Functor
如果你想手動實現一個仿函數的實例,你可以看看類型。 如果您為 function 實現此功能,則fmap
的簽名應為:
fmap :: Functor f => (b -> c) -> f b -> f c
因此對於f ~ (->) a
,這意味着:
fmap :: (b -> c) -> ((->) a b) -> ((->) a c) -- fmap for (->) a
或更緊湊:
fmap :: (b -> c) -> (a -> b) -> (a -> c) -- fmap for (->) a
這種fmap
只有一種直接的實現。 如果您隨后要實現它, instance Functor (F a)
,唯一的區別是從F
數據構造函數中解包 function 並將結果包裝回F
數據構造函數中。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.