[英]Functors don’t work with data types that require specific types
這很好用:
data Foo a = Foo a
instance Functor Foo where
fmap f (Foo s) = Foo (f s)
這會拋出一個錯誤:
data Foo = Foo String
instance Functor Foo where
fmap f (Foo s) = Foo (f s)
錯誤:
Kind mis-match The first argument of `Functor' should have kind `* -> *', but `Foo' has kind `*' In the instance declaration for `Functor Foo'
我在這里錯過了什么? 如果Foo
擁有特定類型,為什么我不能使用仿函數來包裝和展開Foo
?
UPDATE
我想我可以用另一種方式問:
data Foo = Foo String deriving(Show)
let jack = Foo "Jack"
-- Some functory thingy here
putStrLn $ show $ tail <$> jack
-- Foo "ack"
為什么我不能這樣做? 或者這個用例有另一種結構嗎?
那是因為Foo
需要一個單一的類型變量才能運行。
fmap
的類型是:
fmap :: Functor f => (a -> b) -> f a -> f b
現在嘗試將此專門用於你的Foo
:
(a -> b) -> Foo -> Foo
你能看出問題出在哪里嗎? 這些類型不僅僅匹配。 所以,要使Foo
成為一個仿函數,它必須是這樣的:
Foo a
所以當你專門為fmap它時,它有以下正確的類型:
(a -> b) -> Foo a -> Foo b
來自動態語言,你可能會看到Functor作為東西和fmap
的容器 ,作為轉換容器內部事物的方法。 但是在類別理論中,可以將Functor視為將類型轉換為另一種類型的方法,以及將這些類型的函數轉換為另一種類型的函數的方法。
想象一下,你有兩個不同的世界,一個是地球,一個虛擬世界,當地球上的每個身體/事物都有一個化身。 Functor不是化身,而是魔杖,它將一切變為其化身,但也將現實世界的每一個功能轉化為化身世界中的一個功能。
例如,使用我的魔杖,我可以將人變換為青蛙(或將字符串轉換為字符串列表),但我也可以將“更改人類帽子”的功能轉換為更改青蛙帽子“(或者將字符串變為大寫列表中的所有字符串)。
fmap
是將函數轉換為另一個函數的方式:您可以將其視為
一個帶有2個參數的函數 - 一個函數和一個容器 - 並將此函數應用於該容器的每個元素
而且作為一個帶有1個argmunt的函數 - 一個函數 - 返回一個帶容器並返回容器的函數。
從類型創建類型的方式不太明顯在第一個示例中,您可能只看到Foo String
作為新類型,但您也可以將Foo
視為一個超類函數,它采用String
類型並返回一個新類型: Foo String
。 這就是* -> * kind
。 Foo不是類型,而是從類型創建類型的超級函數。
在你的第二個例子中, Foo
不是類型創建者,而只是一個簡單類型(kind: *
),因此將它聲明為仿函數是沒有意義的。
如果你真的想在第二個例子中為普通Foo
定義fmap
,那就是定義一個真正的fmap
函數並為普通類型創建一個類型別名
data FooFunctor a = FooFunctor a
instance Functor Foofunctor where
fmap f (FooFunctor a) = FooFunctor (f a)
type Foo = FooFunctor String
fmap
的類型是通用的; 你不能限制它:
fmap :: Functor f => (a -> b) -> f a -> f b
那些a
S和b
s 必須是完全多態性(在您的約束Functor
實例),或者你沒有一個Functor
。 解釋原因的手工方式是因為Functor
必須遵守一些理論規則才能使它們與Haskell的其他數據類型相得益彰:
fmap id = id
fmap (p . q) = (fmap p) . (fmap q)
如果您有一個參數化多種類型的數據類型,即:
data Bar a b = Bar a b
您可以為Bar a
編寫Functor
實例:
instance Functor (Bar a) where
fmap f (Bar a b) = Bar a (f b)
您還可以為Bar
編寫Bifunctor
實例:
instance Bifunctor Foo where
first f (Bar a b) = Bar (f a) b
second f (Bar a b) = Bar a (f b)
......這也必須遵循一些法律(在鏈接頁面上列出)。
編輯:
您可以編寫自己的類來處理您正在尋找的行為類型,但它看起來像這樣:
class FooFunctor f where
ffmap :: (String -> String) -> f -> f
但是在這種情況下,為了覆蓋所有基礎,我們必須為我們可能擁有的“內部類型”(如String)的每一個排列創建新的整個類。
您還可以編寫一個類(稱為Endo
),它只允許在數據類型的“內部類型”上使用endomorphisms(類型為a -> a
函數),如下所示:
class Endo f where
emap :: (a -> a) -> f a -> f a
然后,如果您稍微更改了數據類型,並實例化了適當的Endo實例,例如
data Foo' a = Foo' a
type Foo = Foo' String
instance Endo Foo' where
emap f (Foo a) = Foo (f a)
...如果您編寫類型為Foo -> Foo
函數,則可以保證在使用emap
保留您正在映射的內部類型的“ emap
。 對hayoo的快速搜索顯示,這種類型的東西是相對常見的做法,但並不真正作為標准類型類存在。
一個完全符合你要求的類是MonoFunctor 。
type instance Element Foo = String
instance MonoFunctor Foo where
fmap f (Foo s) = Foo (f s)
head "Jack"
不是字符串"J"
,而是字符'J'
。 所以你自己的例子說明了為什么這不起作用; head <$> jack
必須提供Foo 'J'
,這不是Foo
類型的有效值,因為Foo
只能應用於String
值,而不能應用於Char
值。
這個用例的“其他一些構造”是為Foo
定義一個“map”函數,就像你試圖定義fmap一樣。 但是這個map函數不是 fmap,因為它必須有類型(String -> String) -> Foo -> Foo
。 因此,沒有必要(或可能)將Foo
作為Functor
的實例並命名您的映射函數fmap
; 你想要使用的映射函數根本就不是fmap。
請注意,這意味着您無法在Foo
值上映射任意函數; 只接受和返回字符串的函數(所以head
仍然沒有)。 你也不能將Foo
值傳遞給接受任何仿函數值的泛型函數; 這些函數可能會嘗試在不返回字符串的Foo
上fmap
函數; 他們被允許這樣做,因為他們指定他們需要仿函數 ,這正是定義仿函數的原因。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.