这是另一个Haskell通过类别理论的问题。
让我们以一个简单而众所周知的例子为例。 fmap
?
因此,fmap :: (a -> b) -> f a -> f b
忽略了f
实际上是Functor
的事实。据我了解,(a -> b) -> f a -> f b
只是(a -> b) -> (f a -> f b)
的语法糖;因此得出结论:
(1) fmap
是产生函数的函数。
现在, Hask 也包含函数,因此(a -> b)
,尤其是(f a -> f b)
是Hask的一个对象(因为对象的Hask是定义明确的Haskell类型-一种数学集合-的确存在每个可能的(a -> b)
的类型a
的集合,对吗?)。因此,再次:
(2) (a -> b)
是Hask的对象。
现在发生了奇怪的事情:fmap
很明显是Hask的态射,因此它是一个函数,它需要另一个函数并将其转换为另一个函数; 最终功能尚未应用。
因此,从(f a -> f b)
到f b
,还需要Hask的态射。对于类型为i
的每个项目a
,存在定义为apply_i :: (f a -> f b) -> f b
的同态\f -> f (lift i)
,其中lift i
是使用以下方法构建f a
的方法特别是i
。
另一种查看方式是GHC
样式:(a -> b) -> f a -> f b
。与我上面所写的相反,(a -> b) -> f a
映射到Hask的常规对象。但是这样的观点与基本的Haskell公理相矛盾-没有多元函数,而是应用了(咖喱的)替代方法。
在这一点上,我想问一下:(a -> b) -> f a -> f b
是否应该是(a -> b) -> (f a -> f b) -> f b
,为简单起见而加糖,还是我在这里真的缺少了非常重要的东西?
答案 0 :(得分:6)
(a -> b) -> f a -> f b
应该是(a -> b) -> (f a -> f b) -> f b
,为简单起见加糖
不。我认为您所缺少的并不是您的错,这只是在非常特殊的情况下,(a -> b) -> (f a -> f b)
中的中间箭头可以称为 morphism ,外(a -> b) -> (f a -> f b)
可以。 Functor类的一般情况是(使用伪语法)
class (Category (──>), Category (~>)) => Functor f (──>) (~>) where
fmap :: (a ──> b) -> f a ~> f b
因此,它把箭头表示为──>
的类别中的态射映射到~>
类别中的射态,但是这种射态映射本身只是一个 function 。您的权利,在 Hask 中,功能箭头与态射箭头的箭头相同,但是从数学上讲,这是一个相当简陋的场景。
答案 1 :(得分:4)
fmap
实际上是整个射影的一个家族。 Hask 中的变态总是从一种具体类型到另一种具体类型。如果函数具有具体的参数类型和具体的返回类型,则可以将其视为同态。类型为Int -> Int
的函数表示 Hask 中从Int
到Int
的一个态(实际上是一个内态)。 fmap
,但类型为Functor f => (a -> b) -> f a -> f b
。看不到具体的类型!我们只有类型变量和拟运算符=>
要处理。
请考虑以下一组具体的函数类型。
Int -> Int
Char -> Int
Int -> Char
Char -> Char
进一步,考虑以下类型构造函数
[]
Maybe
应用于[]
的 Int
返回一个我们可以调用List-of-Ints
的类型,但是我们通常只调用[Int]
。 (当我刚开始使用函子时,最令人困惑的事情之一是我们只是没有单独的名称来引用各种类型构造函数产生的类型;输出只是由对其求值的表达式来命名。){{ 1}}返回我们刚刚调用的类型,Maybe Int
。
现在,我们可以定义一堆类似于以下的功能
Maybe Int
在 Hask 中,每一个都是不同的形态,但是当我们在Haskell中定义它们时,会有很多重复。
fmap_int_int_list :: (Int -> Int) -> [Int] -> [Int]
fmap_int_char_list :: (Int -> Char) -> [Int] -> [Char]
fmap_char_int_list :: (Char -> Int) -> [Char] -> [Int]
fmap_char_char_list :: (Char -> Char) -> [Char] -> [Char]
fmap_int_int_maybe :: (Int -> Int) -> Maybe Int -> Maybe Int
fmap_int_char_maybe :: (Int -> Char) -> Maybe Int -> Maybe Char
fmap_char_int_maybe:: (Char -> Int) -> Maybe Char -> Maybe Int
fmap_char_char_maybe :: (Char -> Char) -> Maybe Char -> Maybe Char
fmap_int_int_list f xs = map f xs
fmap_int_char_list f xs = map f xs
fmap_char_int_list f xs = map f xs
fmap_char_char_list f xs = map f xs
fmap_int_int_maybe f x = case x of Nothing -> Nothing; Just y -> Just (f y)
fmap_int_char_maybe f x = case x of Nothing -> Nothing; Just y -> Just (f y)
fmap_char_int_maybe f x = case x of Nothing -> Nothing; Just y -> Just (f y)
fmap_char_char_maybe f x = case x of Nothing -> Nothing; Just y -> Just (f y)
的类型不同时,定义没有不同,仅f
/ x
的类型不同时。这意味着我们可以定义以下 polymorphic 函数
xs
每个代表 Hask 中的一组射态。
fmap_a_b_list f xs = map f xs
fmap_a_b_maybe f x = case x of Nothing -> Nothing; Just y -> Just (f y)
本身是一个笼统的术语,我们用来指代所有多态函数所引用的特定于构造函数的态射。
通过这种方式,我们可以更好地了解fmap
。
鉴于fmap :: Functor f => (a -> b) -> f a -> f b
,我们首先来看一下fmap f
的类型。例如,我们可能会发现f
,这意味着f :: Int -> Int
必须返回fmap f
或fmap_int_int_list
中的一个,但是我们不确定哪个。因此,它返回类型为fmap_int_int_maybe
的 constrained 函数。一旦将该函数应用于类型为Functor f => (Int -> Int) -> f Int -> f Int
或[Int]
的值,我们最终将获得足够的信息来知道实际上是哪个形态学。
答案 2 :(得分:3)
现在发生了奇怪的事情:fmap很明显是Hask的一种形态,因此它是一个函数,它需要另一个函数并将其转换为另一个函数;最终功能尚未应用。
因此,从(f a-> f b)到f b还需要一个Hask的态射。对于类型为a的每个项i,都有一个态射apply_i ::(fa-> fb)-> fb定义为\ f-> f(提升i),其中,提升i是一种建立带有特定i的fa的方法。
类别理论中的应用概念以CCC's - Cartesian Closed Categories的形式建模。如果您具有自然双射(X×Y,Z)≅(X,Y⇒Z),则类别为CCC。
尤其意味着存在一个自然变换(评估),其中[Y,Z] :(Y⇒Z)×Y→Z,这样对于每一个g:X×Y→Z都有ag: X→(Y⇒Z)这样,g = g×id; [Y,Z]。所以当你说
因此,从(f a-> f b)到f b还需要一个Hask的态射。
从(f a -> f b)
到f b
,或者使用上面的符号,从(f a ⇒ f b)
到[f a,f b]:(f a ⇒ f b) × f a → f b
的方式。
要记住的另一个重要点是,类别理论中的“元素”不是原始概念。而是一个元素,其形式为→X,这是终端对象。如果取X =,则具有(Y,Z)≅(×Y,Z)≅(,Y⇒Z)。也就是说,态素g:Y→Z与元素g:→(Y⇒Z)具有双射性。
在Haskell中,这意味着功能正好是箭头类型的“元素”。因此,在Haskell中,将通过对y:→Y的h:→(Y⇒Z)求值来对应用h y
进行建模。也就是说,对(h)×y:→(Y⇒Z)×Y的求值由(h)×y; [Y,Z]:→Z组成。
答案 3 :(得分:2)
为完整起见,此答案的重点是各种评论中提到的问题,而其他答案则没有。
类型签名中的另一种查看方式是GHC风格:
(a -> b) -> f a -> f b
。与我上面所写的相反,(a -> b) -> f a
映射到Hask的常规对象。
->
是右关联的。既然如此,(a -> b) -> f a -> f b
实际上与(a -> b) -> (f a -> f b)
相同,并且在其中看到(a -> b) -> f a
就是语法上的混淆。没什么不同...
(++) :: [a] -> [a] -> [a]
...并不意味着部分应用(++)
会给我们一个[a]
列表(相反,它给我们提供了添加一些列表的功能)。
从这个角度来看,您提出的类别理论问题(例如,关于“需要从(f a -> f b)
到f b
的另一个Hask态射”)是一个单独的问题问题,Jorge Adriano's answer解决得很好。