簡體   English   中英

函數不適用於需要特定類型的數據類型

[英]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值傳遞給接受任何仿函數值的泛型函數; 這些函數可能會嘗試在不返回字符串的Foofmap函數; 他們被允許這樣做,因為他們指定他們需要仿函數 ,這正是定義仿函數的原因。

暫無
暫無

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

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