一个纯粹的语法差异,尽管表面上可能实际上是最常见的用例,但<<< 的优先级低于.:
infixr 9 Control.Category..
infixr 1 Control.Category.<<<
这类似于普通函数应用程序f x(它比任何中缀绑定更紧密,所以它基本上是infixl 10)和使用$ 运算符(如f $ x)之间的区别,它的优先级最低@987654335 @。这意味着,您可以选择表达式中需要较少括号的表达式。 <<< 当你想组合由一些中缀表达式定义的函数时很方便;这在使用 lenses 时经常发生。
更有趣的是,Category 版本不仅适用于函数,还适用于其他井的态射,类别。一个简单的例子是category of coercions:如果你有例如newtype-wrapped 值的列表,并且您想要获取底层表示,在列表上使用map 将是低效的——这将创建整个列表的副本,但其中包含完全相同的运行时信息。强制允许您一直使用原始列表,但不会绕过类型系统——编译器将在每个点跟踪列表的哪个“视图”中的元素具有什么类型。强制并不是真正的函数——它们在运行时总是无操作——但它们可以像函数一样组合(例如,从Product Int 强制转换为Int,然后从Int 强制转换为Sum Int)。
对于其他示例,Haskellers 通常会引用Kleisli 类别。它们包含a -> m b 形式的函数,其中m 是一个monad。虽然你不能直接作曲,例如readFile :: FilePath -> IO String 和 firstFileInDirectory :: FilePath -> IO FilePath,因为 FilePath 和 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 表示法或仅使用 =<< 或<=< 运营商。 仍然允许您通过选择不同的 monad(IO、ST 或简单的 Identity)来抽象可能是纯的或不纯的计算。
显然有一些专业的解析器是 Arrows,但不能写成 monad,但它们并没有真正流行起来——似乎优势并不能平衡不太直观的风格。
在数学中,还有很多有趣的类别,但不幸的是,这些类别往往不能用Categoryes 来表达,因为并不是每个 Haskell 类型都可以是一个对象。我想举一个例子是category of linear mappings,它的对象只是代表向量空间的Haskell类型,例如Double或(Double, Double)或InfiniteSequence Double。线性映射本质上是矩阵,但不仅具有类型检查的域和共域维度,而且还具有表示具有特定含义的不同空间的选项,例如防止将位置矢量添加到重力场矢量。而且由于向量不需要用数字数组字面表示,因此您可以为每个应用程序优化表示,例如用于您想要进行机器学习的压缩图像数据。
线性映射不是Category 的实例,而是constrained category 的实例。