Learn You a Haskell有一个关于仿函数的例子。我可以阅读LYAH和文本,并弄清楚应该发生什么 - 但我不知道写这样的东西。我经常在Haskell中发现这个问题。
instance Functor (Either a) where
fmap f (Right x) = Right (f x)
fmap f (Left x) = Left x
然而,我很困惑..为什么不这个补充
instance Functor (Either a) where
fmap f (Right x) = Right (x)
fmap f (Left x) = Left (f x)
如果最高定义中没有使用f
,那么还有什么限制x
使其无法满足Left
答案 0 :(得分:41)
这是仿函数类:
class Functor f where
fmap :: (a -> b) -> f a -> f b
请注意,“f”本身是一个类型构造函数,因为它应用于fmap行中的类型变量。以下是一些清楚说明的例子:
类型构造函数:
IO
Maybe
Either String
类型:
IO Char
Maybe a
Either String String
“也许a”是具有一种类型构造函数(“Maybe”)和一种类型变量(“a”)的类型。它不是具体的东西,但它可用于多态函数的类型签名。
“Either”是一个带有两个类型参数的类型构造函数,所以即使你应用了一个(例如Either String
它仍然是一个类型构造函数,因为它可以采用另一个类型参数。
重点是:当您定义Functor
实例时,类型构造函数f
无法更改。这是因为它由同一个变量{{ 1}},作为f
的参数和结果。唯一允许更改的类型是应用于fmap
构造函数的类型。
当您撰写f
时,instance Functor (Either c)
声明Either c
填写f
。这为fmap提供了以下类型的实例:
fmap
使用fmap :: (a -> b) -> (Either c) a -> (Either c) b
的定义,获得此类型的唯一有用方法是将Either
值应用于函数。请记住,“Either”有两个可能的值,可能有不同的类型。这里Right
值的类型为'c',所以你不能将它应用于函数(期望'a')[1],结果也不正确因为你是留下Left
,与类定义不匹配。
用“Either c”替换“f”以使用“Either c”实例获得fmap的上述类型签名后,接下来编写实现。有两种情况需要考虑,左派和右派。类型签名告诉我们左侧的类型“c”不能改变。我们也没有办法改变价值,因为我们不知道它实际上是什么类型。我们所能做的就是不管它:
Either b a
对于右侧,类型签名表示我们必须从类型为“a”的值更改为类型为“b”的值。第一个参数是一个完全相同的函数,因此我们使用带有输入值的函数来获取新的输出。将两者放在一起给出了完整的定义
fmap f (Left rval) = Left rval
这里有一个更通用的原则,这就是为什么编写一个调整左侧的Functor实例是不可能的,至少使用Prelude定义。从上面复制一些代码:
instance Functor (Either c) where
fmap f (Right rval) = Right (f rval)
fmap f (Left lval) = Left lval
即使我们在实例定义中有一个类型变量'c',我们也不能在任何类方法中使用它,因为它在类定义中没有提到。所以你不能写
class Functor f where
fmap :: (a -> b) -> f a -> f b
instance Functor (Either c) where ...
leftMap和fmap的结果现在是leftMap :: (c -> d) -> Either c a -> Either d a
leftMap mapfunc (Left x) = Left (mapfunc x)
leftMap mapfunc (Right x) = Right x
instance Functor (Either c) where
--fmap :: (c -> d) -> Either c a -> Either d a
fmap = leftMap
。 (Either d) a
已更改为(Either c)
,但这是不允许的,因为无法在Functor类中表达它。为了表达这一点,你需要一个有两个类型变量的类,例如
(Either d)
在这个类中,由于左侧和右侧类型变量都在范围内,因此可以编写在任一侧(或两侧)上运行的方法。
class BiFunctor f where
lMap :: (a -> b) -> f a c -> f b c
rMap :: (c -> d) -> f a c -> f a d
biMap :: (a -> b) -> (c -> d) -> f a c -> f b d
虽然在实践中人们通常只为BiFunctor类编写“biMap”,如果需要左或右映射,则使用“id”作为其他函数。
[1]更确切地说,Left值的类型为'c',函数需要'a',但是类型检查器无法统一这些类型,因为'c'类型不在类的范围内定义
答案 1 :(得分:9)
左和右不是类型,Left x
和Right y
属于同一类型。他们只是Either的构造者。你可以考虑
Left :: c -> Either c d
Right :: d -> Either c d
您可以拥有2个fmap
声明,因为我们知道Left和Right是不同的值。这就像
g :: Int -> Int
g 1 = 2
g 2 = 4
g n = n
这里我们不能说1和2和n
是不同的“类型”只是因为模式匹配有效。
Functor类定义为
class Functor f where
fmap :: (a -> b) -> f a -> f b
请注意,a
和b
是任意类型。为清楚起见,我们将您实例中的a
重命名为c
,将函数f
重命名为func
。
instance Functor (Either c) where
fmap func (Right x) = Right (x)
fmap func (Left x) = Left (func x)
假设您的Either遵循默认定义
data Either c d = Left c | Right d
然后按照你的定义,
fmap func (Right x) = Right x
-- # (a -> b) -> f a f b
-- # f = Either c
这会强制a = b
和
fmap func (Left x) = Left (func x)
-- # (a -> b) -> f a f b
-- # f = Either c
强制c = a = b
。考虑到a
,b
和c
是独立的任意类型,两者都无效。
答案 2 :(得分:8)
好的,这是另一个非常简单的尝试。
你问为什么这不编译:
instance Functor (Either a) where
fmap f (Right x) = Right (x)
fmap f (Left x) = Left (f x)
因此,我们尝试通过尝试定义相同的函数来简化问题,而不将其作为类实例声明的一部分:
这给了我们
foo f (Right x) = Right (x)
foo f (Left x) = Left (f x)
确实编译了。 ghci告诉我们类型签名:
*Main> :t foo
foo :: (t1 -> a) -> Either t1 t -> Either a t
我们将重命名一些变量以获得更加统一的外观:
foo :: (a -> b) -> Either a c -> Either b c
这很有道理。它需要一个函数并将其应用于Either的左侧。
但是fmap的签名是什么?
*Main> :t fmap
fmap :: (Functor f) => (a -> b) -> f a -> f b
让我们用Either c
替换fmap
签名中的f(我将Either a
重命名为Either c
,以防止我们两个不同的a
混淆):
fmap :: (a -> b) -> Either c a -> Either c b
你看到了问题吗?您的函数完全有效 - 它的类型与Either a
的fmap必须具有的类型不同。
这是关于类型的一种美好的事情。鉴于fmap
的签名,{A}上的fmap
实际上只有一个有意义的实现。
有时,当我们幸运和谨慎时,我们可能会遇到类似的情况 - 给定一个类型签名,该函数几乎会自行编写。
修改:尝试回答以下问题。
1)没有“两个功能的组合”正在进行中。要获取fmap
超过Either a
的类型签名,只需浏览fmap
函数签名,并查看f
的每个位置,将其替换为Either a
。我们称之为fmap类型签名的“特化”。也就是说,它比普通类型的fmap 严格不那么通用 - 任何需要更专业类型的函数的地方,你可以传递一般类型的东西而没有任何问题。 / p>
2)你在左侧绘图的功能(我在上面的例子中命名为“foo”)就好了。它工作正常,它做你想要的。您无法将其命名为fmap
并在Functor实例中使用它。就个人而言,我将其命名为onLeft
或mapLeft
。
以下所有内容均可忽略/是供参考,但不建议将来阅读/实际使用:
如果想要获得非常技术性的,因为你可以在左侧和右侧进行映射(尽管你只能为后者声明Functor),要么不仅是一个Functor,而是一个Bifunctor。这在例如ekmett的Category-Extras库中提供(参见http://hackage.haskell.org/packages/archive/category-extras/0.44.4/doc/html/Control-Bifunctor.html)。
有许多很酷的东西,包括用程序计算,以及“折纸编程”更严格地使用bifunctors。你可以在这里阅读:http://lambda-the-ultimate.org/node/1360。但是,你可能不想,至少在你对Haskell更熟悉之前。这是计算机技术,肮脏,研究和非常酷,但不是根本来理解惯用的Haskell编程。
答案 3 :(得分:3)
(编辑以尝试更好地回答问题)
Either的定义是:
data Either a b = Left a | Right b
所以“Either”需要两个类型的参数。顺便说一下,技术上“Either”实际上不是一个类型而是一个类型构造函数;它需要类型参数来创建一个类型。
Functor的定义是:
class Functor f where
fmap :: (p -> q) -> f p -> f q
因此,在此类定义中,作为Functor实例的任何类型“f”必须采用类型参数。这没有宣布;它是从“f p”和“f q”推断出来的;因为“f”在这里给出了一个类型参数,所以它必须是一个带有一个的类型。
(注意:原来的定义使用了“a”和“b”而不是“p”和“q”。我使用不同的字母来保持与“Either ab”不同的东西,当我稍后到达时)< / p>
在大多数情况下,“f”是容器类型,如列表或树。所以例如你有
data Tree a = ...
instance Functor Tree where
fmap func_a2b tree_of_a = ... -- tree of b.
但是“Either”需要两个类型参数,那么我们如何才能将其纳入此方案?答案是类型可以像函数一样具有部分应用程序。在相同的方式 我可以声明一个函数
foo x y = ...
然后说“foo 2”以获得一个需要第二个参数的新函数,所以我可以说“Either a”来获得一个需要第二个类型参数的新类型。
现在看一下原始实例:
instance Functor (Either a) where ....
所以这里“Either a”是一个类型构造函数,它需要一个参数,就像Functor期望它的实例一样。所以“Either a”的“fmap”类型将是
fmap :: (p -> q) -> Either a p -> Either a q
所以现在在“where”子句中你必须给出具有这种类型的“fmap”的定义。您引用的第一个类型具有此类型,因为第二个类型参数用于“Right”构造函数,并且该函数应用于该构造函数。第二个将无法工作,因为它将具有类型
fmap :: (p -> q) -> Either p a -> Either q a
这不是Functor课所说的那样。
答案 4 :(得分:3)
虽然我最终会改编你的格式,但我会从稍微不同的格式开始,因为我认为它会让我的解释更加清晰。
让我们考虑一个不同的数据类型
data Choice a = Default Integer | Chosen a
-- This corresponds to your top, working, instance.
instance Functor Choice where
fmap f (Default i) = Default i
fmap f (Chosen a) = Chosen (f a)
应该清楚为什么这个实例有效。但是,以下内容如何:
-- Broken!
instance Functor Choice where
fmap f (Default i) = Default (f i)
fmap f (Chosen a) = Chosen a
你应该能够看到为什么这不起作用。 fmap
的类型为Functor f => (a -> b) -> f a -> f b
;在这种情况下,它是(a -> b) -> Choice a -> Choice b
。因此,f
参数的类型为a -> b
。但是,在第二个(失败的)实例声明中,您编写了f i
。我们知道,由于数据类型声明,i
必须是Integer
,因此我们无法对其应用f
。同样,由于a
的类型为a
,Chosen a
的类型为Chosen a
,而不是类型Chosen b
。因此,底部的Functor
实例无效。
嗯,Either
的顶级实例有效,因为,就像在Choice
示例中一样,它遵循类型。让我们看看它,并进行一些重命名:
instance Functor (Either c) where
fmap f (Left c) = Left c
fmap f (Right a) = Right (f a)
此实例声明不会为Functor
声明Either
的实例 - 它不能。作为Functor
实例的东西必须采用一个类型参数。因此,Int
不能成为仿函数,因为Int
不接受任何类型参数,但[]
和Maybe
可以是[a]
和{{1}是完整的类型。但是,Maybe a
需要两个类型参数:Either
。因此,此实例所做的声明Either a b
是任何可能的Either c
的仿函数。在实例声明的持续时间内,c
已修复。所以让我们来看看并添加类型(这不是合法的语法!):
c
由于instance Functor (Either c) where
fmap :: forall a b. (a -> b) -> (Either c) a -> (Either c) b
fmap f (Left (c :: c)) = Left c
fmap f (Right (a :: a)) = Right (f a :: b)
的类型为f
,但a -> b
的类型固定为c
,我们无法写c
;即使我们可以,也希望Left (f c)
保持不变,以便我们可以返回c
。同样,我们必须将(Either c) b
应用于f
才能获得a
类型的内容。
这也是你的底层实例不起作用的原因:你有一个函数需要适用于只应用于固定类型b
的任何类型,并且你保留需要的类型< / em>单独改造。我们再来看一下类型签名:
c
在这里,函数定义的第一部分尝试将函数instance Functor (Either c) where
fmap :: forall a b. (a -> b) -> (Either c) a -> (Either c) b
fmap f (Left (c :: c)) = Left (f c)
fmap f (Right (a :: a)) = Right a
应用于固定类型f :: a -> b
的某些东西,这不能正常工作,所以这已经失败了。但是让我们来看看它产生的类型。在这种情况下,我们希望(某种程度上)c
具有类型f c
,而b
将具有类型a
。在这种情况下,我们将返回a
类型的值,但仍然不允许这样做。
基本上,问题源于此。首先,请注意Either b a
在两个函数定义子句之间是相同的,因此它不能在行之间更改。其次,请注意我们修复f
,并声明 c
的实例。对于任何c
都是如此,但我们一次只看一个。最后,正因为如此,c
的参数是 not 参数化Left
期望的类型;它保证有一些固定类型f
。这意味着(a)您无法对其应用c
,并且(b)您必须将其应用于f
的论点,否则您将不会改变你期望改变的类型。
答案 5 :(得分:3)
不管你信不信,这不是魔术。这一切都与Either a b
类型Either b a
的类型不同。data Either a b = Left a | Right b
。以下是来自Prelude
instance Functor (Either a) where
fmap f (Right x) = Right (f x)
fmap f (Left x) = Left x
...注意首先是类型变量a,然后是b,并注意到我们只在Either Functor的声明中包含了一个:
instance Functor Maybe where
fmap = map
现在让我们看看Maybe Functor的定义
Maybe
这里没有类型变量,虽然Maybe Int
有一个类型参数(如*->*
)。我想要的是类型不是仿函数,类型构造函数是仿函数(仿函数有类f :: b -> c
)。
因此,让x
在有效的Either Functor版本中,来自(Left x)
的{{1}}类型为a
,这很好,因为{{1}这是一个仿函数,(Either a)
中的x
属于(Right x)
类型,因此b
的类型为(Right x)
,((Either a) b)
属于键入(Right (f x))
,因此fmap的类型为((Either a) c)
,视需要而定。
在您失败的版本中,我们(b->c) -> ((Either a) b) -> ((Either a) c)
中的x
不是(Right (x))
类型,而是a
类型,所以b
是<类型(Right (x))
的强>不,它不符合fmap的类型。
总结一下:有效的fmap出现了:((Either a) c)
,
但是那个不起作用的那个出现了:(b -> c) -> (Either a) b -> (Either a) c
并且这不是fmap的正确类型。
答案 6 :(得分:2)
希望这会有所帮助......
首先,一些背景:
1)Functor需要一个“类型构造函数”,一个(好吧,不是一个类型本身,...)类型,“需要”另一个常规类型给它形成一个“完整类型”,就像一个通用在Java / C ++中。
因此,例如,List
是一个Functor(顺便说一下),或Array
,因为(除此之外)完整类型不仅仅是{ {1}},但List
。所以,:
Functor采用“类型构造函数”,不完整类型。
2)List<A>
是一个构造函数类型,Haskell人(阅读:Edward Kmett,以及其他数学良好的全明星)称为bifunctor。它需要两种类型才能完成。例如,完全使用Either是:Either
这意味着(是的,是的,“呃!”)它是(Either Integer String
)整数或(Left
)字符串。因此,这意味着Right
是一个不完整的类型,当您使用Either Integer
或Left Integer
时
决定“b”应该是什么。
现在,为了有趣的部分!
最重要的东西是有效的,因为Right...b
使用了某种类型的构造函数,并将其与fmap
函数一起使用,以便从a -> b
到f a
创建类似的函数 - 手-Haskell中最喜欢的例子是列表,AKA地图f b
,其中Functor是:: (a -> b) -> ([a] -> [b])
部分。现在,使用像[ ]
这样的东西(让我们继续使用前面的Either a
),fmap的类型签名如下所示:
Either Integer
并且两个示例(来自顶部)显示了fmap对fmap :: (a -> b) -> (Either Integer a -> Either Integer b)
类型的代表值所做的事情,以获得Either Integer a
类型的值。
现在,你的-bottom-不起作用,因为:
Either Integer b
f
到a
s。b
类型必须是类型
整数,并保持整数(或
键入Float,并保持Float,什么
永远的类型是左之一
两种类型的Left
类型
你正在使用)。Either
类型必须是
无论什么类型的功能
取(“Right
”),然后转到该类型
该函数使用(“a
”)。它必须这样做(但你的东西没有 - 这就是为什么它不起作用),因为那是b
需要的类型。具体来说,你有这些方程式:
fmap
您的等式给出fmap f (Right x) = Right (x)
fmap f (Left x) = Left (f x)
类型:
fmap
这不仅不符合fmap :: (a -> b) -> Either c d -> Either c d
fmap :: (a -> b) -> Either a d -> Either b d
想要的东西,而且彼此之间甚至不一致!
对不起,我写了半本书,但我希望能给你一些见解。
答案 7 :(得分:2)
热门作品因为fmap::(b -> c) -> Either a b -> Either a c
和你的-bottom-不起作用,因为这需要fmap::(a -> c) -> Either a b -> Either a c
。但是,如果您将Either更改为
data Either' a b = Left' b | Right' a deriving (Eq, Show)
instance Functor (Either' a) where
fmap f (Right' x) = Right' (x)
fmap f (Left' x) = Left' (f x)
答案 8 :(得分:1)
您尝试编写的实例,我们暂时将其称为fmap2
,具有以下类型:
fmap2 :: (a -> b) -> Either a c -> Either b c
如果设置LANGUAGE
pragma TypeOperators
,GHC也接受
fmap2 :: (a -> b) -> (a `Either` c) -> (b `Either` c)
在一个理想的世界中,这也会起作用:
fmap2 :: (a -> b) -> (`Either` c) a -> (`Either` c) b
这将为(`Either` c)
提供一个Functor实例,但是正常运算符(及其部分)和类型运算符之间的相似性在这一点上会崩溃(除非我有一个GHC选项,我错过了!)
简而言之:你对仿函数的理解是可以接受的,但是由于缺乏类型级的lambda,你会感到很痛苦。
答案 9 :(得分:1)
Prelude> let f x y z = x + y * z f :: (Num a) => a -> a -> a -> a Prelude> :t f 1 f 1 :: (Num t) => t -> t -> t Prelude> :t f 1 2 f 1 2 :: (Num t) => t -> t Prelude> :t f 1 2 3 f 1 2 3 :: (Num t) => t
与类型相同的东西。当你说Either
那种类型是* -> * -> *
时(即它需要两种类型并生成类型),当你说Either a
种类是* -> *
和{{1}时它是Either a b
(顺便说一句*
和Monad a
要求Functor a
属于a
,我记得。
因此,当您说类型* -> *
表示仍然不完整的类型(需要绑定一些“参数”)时,Either a
在fmap :: (a -> b) -> f a -> f b
被{fmap :: (a -> b) -> (Either c) a -> (Either c) b
替换时变为f
1}}。