'。和有什么不一样?和“ <<<”执行功能组合时?

时间:2018-08-27 22:41:10

标签: haskell hindley-milner

我正在尝试在Haskell中执行函数组合,但是我不确定哪个运算符是正确的运算符。

文档包含以下两个类型签名:

(.) :: (b -> c) -> (a -> b) -> a -> c 
(<<<) :: Category cat => cat b c -> cat a b -> cat a c 

这两个选项之间的区别很明显是Category cat的存在/不存在,但是此注释表示什么?我该如何使用该信息来选择一个运算符而不是另一个运算符?

在比较其他两个运算符时,我还注意到上述两个签名的第三个变体:

(>>) :: forall a b. m a -> m b -> m b 
(>>>) :: Category cat => cat a b -> cat b c -> cat a c

forall注释是什么意思– >>用于第三种情况?

2 个答案:

答案 0 :(得分:6)

首先,您应该认识到(.)是在Prelude中以特定于函数的形式定义的

(.) :: (b -> c) -> (a -> b) -> a -> c 

以及Category类提供的更通用的功能:

class Category cat where
    -- | the identity morphism
    id :: cat a a

    -- | morphism composition
    (.) :: cat b c -> cat a b -> cat a c

(->)的{​​{1}}实例清楚地表明,这两个函数是相同的:

Category

instance Category (->) where id = GHC.Base.id (.) = (GHC.Base..) 的定义清楚地表明,它只是(<<<)的同义词

(.)

旨在与-- | Right-to-left composition (<<<) :: Category cat => cat b c -> cat a b -> cat a c (<<<) = (.) 运算符对称。您可以编写(>>>)f >>> g,以您的特定用途为准。


g <<< f是一个完全不同的运算符(至少,只要您对单子理论不做过多研究即可)。 (>>)(>>)的一个版本,它忽略第一个操作数的结果,仅将其用于效果。

(>>=)

答案 1 :(得分:3)

纯粹的语法差异(尽管肤浅可能实际上是最常见的用例)是<<<的优先级低于.

infixr 9 Control.Category..
infixr 1 Control.Category.<<<

这类似于普通函数应用程序f x(绑定比任何中缀都紧,因此基本上是infixl 10)与使用$运算符(如{{1})之间的区别},其优先级最低f $ x。这意味着,您可以选择在表达式中哪一个需要较少的括号。如果您想编写由一些infix表达式定义的函数,infixr 0 $会很方便;在使用lenses时经常会发生这种情况。

从理论上讲更有趣的是,<<<版本不仅适用于函数,而且适用于其他类别的态射。一个简单的示例是category of coercions:如果您有Category包装的值的列表,而您想获取基础表示,则newtype在列表上的效率会很低–这会创建整个列表的副本,但其中包含精确的相同的运行时信息。强制使您可以始终使用原始列表,而无需绕过类型系统-编译器将在每个点上跟踪元素在列表的“视图”中具有哪种类型。强制实际上不是函数,它们在运行时始终是无操作的,但是它们可以像函数一样组成(例如,从Product Int强制为map,然后从Int强制为{ {3}}。

对于其他示例,Haskellers通常将引用Sum Int类别。它们包含格式为Int的函数,其中a -> m b是monad。虽然您不能直接撰写例如mreadFile :: FilePath -> IO String,因为firstFileInDirectory :: FilePath -> IO FilePathFilePath之间不匹配,因此您可以 Kleisli编写它们:

IO FilePath

可以写同样的东西

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单引号或用{ do=<<运算符。通过 still ,您只需选择不同的monad(<=<IO或简单地ST)就可以抽象出纯净或不纯净的计算。
显然,有些专家解析器是Identity,但不能写成monad,但它们并没有真正流行起来-似乎优点是无法平衡不太直观的样式。

在数学中,还有更多有趣的类别,但不幸的是,由于并非每种Haskell类型都可以是一个对象,这些类别往往无法表示为Arrows。我想举一个例子,Kleisli,其对象只是代表矢量空间的Haskell类型,例如CategoryDouble(Double, Double)。线性映射本质上是矩阵,但不仅具有类型检查的域和共域维,而且还具有表示具有特殊含义的不同空间的选项,例如防止将位置矢量添加到重力场矢量。而且由于向量不需要直接由数字数组表示,因此您可以为每个应用程序优化表示,例如用于要对其进行机器学习的压缩图像数据。

线性映射不是InfiniteSequence Double实例,但它们是category of linear mappings的实例。