一个纯粹的句法差异,尽管它实际上可能是最常见的用例,但它的<<<
优先级低于.
:
infixr 9 Control.Category..
infixr 1 Control.Category.<<<
这类似于普通函数应用程序f x
(它比任何中缀绑定更紧密,所以基本上是infixl 10
)和使用具有最低优先级的$
运算符 like之间的区别。这意味着,您可以选择表达式中需要较少括号的表达式。当您想要编写本身由某些中缀表达式定义的函数时,它会很方便;这在使用镜头时经常发生。f $ x
infixr 0 $
<<<
理论上更有趣的是,该Category
版本不仅适用于函数,还适用于其他类别的态射。一个简单的例子是强制的类别:如果你有一个newtype
-wrapped 值的列表,并且你想得到底层的表示,那么它是低效的map
在列表上——这将创建整个列表的副本,但其中包含完全相同的运行时信息。强制允许您一直使用原始列表,但不会绕过类型系统——编译器将在每个点跟踪列表的哪个“视图”中的元素具有什么类型。强制不是真正的函数——它们在运行时总是无操作——但它们可以像函数一样组合(例如从Product Int
toInt
强制,然后从Int
to强制Sum Int
)。
对于其他示例,Haskellers 通常会引用Kleisli
类别。这些包含形式的函数a -> m b
,其中m
是 monad。虽然你不能直接组成例如readFile :: FilePath -> IO String
with firstFileInDirectory :: FilePath -> IO FilePath
,因为FilePath
and之间存在不匹配IO FilePath
,你可以Kleisli 组成它们:
import Control.Monad
main = writeFile "firstfileContents.txt" <=< readFile <=< firstFileInDirectory
$ "src-directory/"
可以写出同样的东西
import Control.Arrow
main = runKleisli ( Kleisli (writeFile "firstfileContents.txt")
<<< Kleisli readFile
<<< Kleisli firstFileInDirectory
) $ "src-directory/"
有什么意义?好吧,它允许您对不同的类别进行抽象,从而使代码既可以与纯函数一起使用,也可以与函数一起使用IO
。但坦率地说,我认为Kleisli
在激励使用其他类别方面做得很糟糕:任何你可以用 Kleisli 箭头写的东西,如果用标准的一元do
符号来写,或者只用=<<
or<=<
操作符写,通常都更易读。这仍然IO
允许您通过选择不同的单子(ST
或简单地)来抽象可能是纯的或不纯的计算Identity
。
显然有一些专业的解析器是Arrows
但不能写成 monads,但它们还没有真正流行起来——似乎优势并不能平衡不太直观的风格。
在数学中,还有更多有趣的类别,但不幸的是,这些类别往往不能用Category
es 表示,因为并非所有 Haskell 类型都可以是 object。我想举的一个例子是线性映射的类别,它的对象只是代表向量空间的 Haskell 类型,例如Double
or(Double, Double)
或InfiniteSequence Double
. 线性映射本质上是矩阵,但不仅具有类型检查的域和共域维度,而且还具有表示具有特定含义的不同空间的选项,例如防止将位置向量添加到重力场向量。而且由于向量不需要用数字数组逐字表示,您可以为每个应用程序优化表示,例如,对于您想要进行机器学习的压缩图像数据。
线性映射不是Category
实例,但它们是受约束类别的实例。