我一直试图了解Funask在Haskell中的含义,为此我想要一个Functor的例子,没有任何其他属性。
我提出的工作示例是
data MyFunctor a b = MyFunctor a b deriving Show
instance Functor (MyFunctor a) where
fmap g (MyFunctor a b) = MyFunctor a $ g (b)
我猜这是一个半实用的Functor,因为左值可以安全地存储,同时操作正确的值。然后我希望在操作之前将左值替换为正确的值。所以...
Main> fmap (+2) MyFunctor 2 5
MyFunctor 5 7
更改实例声明以执行此操作会产生错误。
data MyFunctor a b = MyFunctor a b deriving Show
instance Functor (MyFunctor a) where
fmap g (MyFunctor a b) = MyFunctor b $ g (b)
我不明白,或者知道要搜索什么来找到类似的问题来帮助我。
C:\Haskell\func.hs:3:28: error:
* Couldn't match type `a1' with `a'
`a1' is a rigid type variable bound by
the type signature for:
fmap :: forall a1 b. (a1 -> b) -> MyFunctor a a1 -> MyFunctor a b
at C:\Haskell\func.hs:3:3
`a' is a rigid type variable bound by
the instance declaration at C:\Haskell\func.hs:2:10
Expected type: MyFunctor a b
Actual type: MyFunctor a1 b
* In the expression: MyFunctor b $ g (b)
In an equation for `fmap':
fmap g (MyFunctor a b) = MyFunctor b $ g (b)
In the instance declaration for `Functor (MyFunctor a)'
* Relevant bindings include
b :: a1 (bound at C:\Haskell\func.hs:3:23)
a :: a (bound at C:\Haskell\func.hs:3:21)
g :: a1 -> b (bound at C:\Haskell\func.hs:3:8)
fmap :: (a1 -> b) -> MyFunctor a a1 -> MyFunctor a b
(bound at C:\Haskell\func.hs:3:3)
答案 0 :(得分:5)
然后我希望在操作之前将左值替换为正确的值。
对于Functor来说,这是非法的事情:GHC正确地告诉你这些类型没有成功。特别是,MyFunctor
值的左侧部分可能与右侧部分的类型不同,如MyFunctor 5 "hello"
中所示。由于fmap
必须仅对您的类型的最后一个类型参数进行操作,因此必须单独留下第一部分。让我们看一个更具体的例子。
回想一下fmap
的类型是
fmap :: Functor f => (a -> b) -> f a -> f b
假设你有fmap
的对象:
obj :: MyFunctor Int String
那么f
调用fmap f obj
的类型必须是什么?要统一所涉及的类型,我们必须
f :: (String -> a)
和
fmap f obj :: MyFunctor Int a
所以你可以看到不可能"替换"第一个,Int,字段,第二个字符串字段的前一个值:该地方唯一允许的是之前的内容,一个Int!
现在,您可以想象如果您更改MyFunctor定义以使两个字段具有相同类型,则可以使类型工作正常:
data MyFunctor a = MyFunctor a a
-- Also illegal
instance Functor MyFunctor where
fmap f (MyFunctor a b) = MyFunctor b (f b)
但您也无法执行此操作,因为b
和f b
可能属于不同的类型,并且调用者可以选择要使用的f
,因此您的实现fmap
可能不认为它们是相同的。
事实上,对于任何给定的数据类型,Functor最多只有一个合法的定义:如果你找到一个合法的定义,你可以确定任何其他定义都是非法的:要么类型不匹配起来,否则它会打破两个Functor法则之一:
fmap id == id
fmap f . fmap g == fmap (f . g)
如果仍然排列类型,你怎么可能违反其中一条法律?通过操作不属于fmap
结构的部分结构。例如,通常会有人写一个(坏!)Functor,如下所示:
data Counter a = Counter Int a
instance Functor Counter where
fmap f (Counter n x) = Counter (n + 1) (f x)
但是这打破了两个Functor定律,因为它允许你计算被调用fmap
的次数,这应该是一个未暴露的细节。
fmap id (Counter 0 0) == Counter 1 0
(fmap tail . fmap tail) /= fmap (tail . tail)
答案 1 :(得分:2)
首先,你真的很想要了解这一点。它是Haskell所有令人惊叹的事物的基础之一。
第二,我认为有更好的方法来理解Functor
而不是尝试自己创建一个实例。最好的方法之一是先使用使用 fmap
,然后就可以找到许多现有Functor
个实例。 Prelude中已经有很多。查看它们的源代码,了解它们是如何实例化的。这将使你对自己的直觉有所了解。
您创建的类型"例如,上面实际上已经在前奏中了。它被称为(,)
。 (错误地说这是Either
之前的事情 - 它不是,因为那种类型data Either a b = Left a | Right b
抱歉。)
你想要做的是先了解Prelude中的大约10个Functor
。但是,不要感受Functor
部分。确保您了解基础数据类型和结构。出于这个原因,除非你真正理解类型,代数数据类型(总和和产品),多参数类型和类型类(不是如何使用或实例化类型类,只是它们是什么),否则这将是徒劳的。 ,以及该机制如何运作。)
如果你想直接介绍使用这些东西的基础知识,我会说通过我帮助的工作来完成这项工作:http://happylearnhaskelltutorial.com)
使用 Functor
个实例并创建它们之间存在很大差异。我认为你会对他们的意思有更好的直觉,如果你使用它们的话。只有这样,恕我直言,你应该亲自实例化一个。此时,查阅法律也是一个好主意,因为它们很重要。这应该清除你对它们是什么或它们可能是什么的任何误解。
然而,为了让你走上这条道路,它们是关于在某个结构的最浅层中应用函数。该结构是所讨论类型的结构。因此,在List
中,它将成为列表中的所有"值" (因为想要一种更好的方式来描述列表),因为如果你看一下List
的定义类型,你会发现它实际上是列表中最浅层的。但要理解为什么,你需要真正理解Haskell中的列表类型。
因此,我会查看以下类型的工作方式Functor
:Maybe
,List
(又名[]
),Either
,{{ 1}},Data.Map
,(->)
。 (,)
(玫瑰树),Data.Tree
中的Identity
类型(只是您可以拥有的最简单的参数化代数数据类型,它包含其值:Control.Monad.Identity
)。搜索更多(有堆!)
祝你玩得愉快!
答案 2 :(得分:1)
错误信息非常详细,并讲述了整个故事。
首先,它告诉我们fmap
的类型,在该实例中应该是
fmap :: forall a1 b. (a1 -> b) -> MyFunctor a a1 -> MyFunctor a b
因此,如果f
是任何函数a1 -> b
,我们必须使fmap f
具有类型MyFunctor a a1 -> MyFunctor a b
。
请注意返回类型是MyFunctor a b
。实际上,这就是GHC所期望的:
Expected type: MyFunctor a b
但相反,它找到了别的东西:
Actual type: MyFunctor a1 b
怎么可能?好吧,fmap
返回MyFunctor b (g b)
而g b
的类型为b
(正确,如预期的那样),但b
(值)的类型为a1
而不是预期的a
。
更实际上,fmap
只能更改b
中的MyFunctor a b
组件,它不会影响a
组件。这可以从fmap
的类型签名中看出。
答案 3 :(得分:0)
我认为你不能像那样交换价值观。您已声明MyFunctor
包含通用类型a
和b
的两个值。 这些不一定是同一类型。例如,您可以创建一个这样的值:
MyFunctor "foo" 42
当您声明Functor (MyFunctor a)
的实例时,您基本上已经说过,对于任何通用类型a
,您可以将MyFunctor a b
映射到MyFunctor a c
。
例如,如果a
为String
,如上例所示,如果您有一个函数MyFunctor String b
,则可以将MyFunctor String c
映射到b -> c
。
您无法交换参数,因为第一个值必须保留其类型。
如果c
是除String
以外的任何其他类型,则在上述示例中无法执行此操作。