如何定义多元箭头?

时间:2019-05-11 15:31:19

标签: haskell arrows

此问题与this earlier one有点相关:

我正在尝试定义几个半依赖类型,它们使您可以跟踪函数的“单调性”(如Monotonicity Types文件中所述),以便程序员不必这样做手动(并且在将非单调操作传递给需要单调操作的操作时在编译时失败)。

更笼统地说:我想跟踪函数的某些“限定符”

基于answers to this earlier question,我已经能够定义“索引类别”和“索引箭头”,其中hh = f >>> g的限定词取决于{{1 }}和f

这很好,只要您仅使用单参数函数即可。但是,(并且普通箭头也有此问题),当您尝试创建带有多个参数的箭头时,具有以下选项。 (以g为例)

  1. 简单地(+) :: Int -> Int -> Int。由于此函数是咖喱函数,因此其结果类型将为arr (+)。这意味着只有第一个参数被提升到箭头上下文中,因为箭头“返回的函数少一个参数”。换句话说:其余参数未使用箭头上下文,因此这不是我们想要的。
  2. Arrow x => x Int (Int -> Int)。其结果类型将为arr (uncurry (+))。现在,这两个参数都已成为箭头的一部分,但是我们失去了例如部分应用它。对我来说还不清楚如何以独立于Arity的方式轻松进行操作:如果我们想要三个,四个,五个...参数函数怎么办?

我知道可以定义一个“递归类型类”来创建一个多元函数,例如在Rosettacode here中描述的那样。我试图定义一个更通用的函数包装类型,该类型可能会以这种方式工作,但是到目前为止,我还没有做到。我不知道如何在Arrow x => x (Int, Int) Int的类型类实例中正确识别a -> b是最终结果还是另一个函数b,以及如何提取和使用{{1}的限定词},如果是第二种情况。

这可能吗?我在这里想念什么吗?还是我完全走错了路,是否有另一种方法可以将 n参数函数提升为箭头,而不管 n 的值如何?

1 个答案:

答案 0 :(得分:3)

在这里,您可以定义arrowfy来将函数a -> b -> ...变成箭头a `r` b `r` ...(其中r :: Type -> Type -> Type是箭头类型)和函数{{1} }使用一个元组参数uncurry_将函数变成一个函数(然后可以使用(a, (b, ...)) -> z将其提升为任意箭头)。

arr :: (u -> v) -> r u v

两种方法都使用具有重叠实例的多参数类型类。一个函数实例,只要初始类型是函数类型,就会被选择;一个基本案例实例,只要它不是函数类型,就会被选择。

{-# LANGUAGE
    AllowAmbiguousTypes,
    FlexibleContexts,
    FlexibleInstances,
    MultiParamTypeClasses,
    UndecidableInstances,
    TypeApplications
  #-}

import Control.Category hiding ((.), id)
import Control.Arrow
import Data.Kind (Type)

关于-- Turn (a -> (b -> (c -> ...))) into (a `r` (b `r` (c `r` ...))) class Arrowfy (r :: Type -> Type -> Type) x y where arrowfy :: x -> y instance {-# OVERLAPPING #-} (Arrow r, Arrowfy r b z, y ~ r a z) => Arrowfy r (a -> b) y where arrowfy f = arr (arrowfy @r @b @z . f) instance (x ~ y) => Arrowfy r x y where arrowfy = id 语法的注释

这是TypeApplications语法,自GHC 8.0起可用。

arrowfy的类型为:

arrowfy @r @b @z

问题在于r模棱两可:在表达式中,上下文只能确定x和y,这不一定限制r。 @r批注允许我们显式专门化arrowfy。 请注意,arrowfy的类型参数必须以固定顺序出现:

arrowfy :: forall r x y. Arrowfy r x y => x -> y

GHC user guide on TypeApplications


例如,现在,如果您有箭头arrowfy :: forall r x y. ... arrowfy @r1 @b @z -- r = r1, x = b, y = z ,则可以编写此代码将其变成箭头:

(:->)

对于test :: Int :-> (Int :-> Int) test = arrowfy (+) ,有一个小的附加技巧,可以将n个参数的函数转换为n个元组的函数,而不是由一个幼稚的单元限制的(n + 1)个元组。现在,这两个实例都按函数类型编制了索引,而实际上正在测试的是结果类型是否为函数。

uncurry_

一些例子:

-- Turn (a -> (b -> (c -> ... (... -> z) ...))) into ((a, (b, (c, ...))) -> z)
class Uncurry x y z where
  uncurry_ :: x -> y -> z

instance {-# OVERLAPPING #-} (Uncurry (b -> c) yb z, y ~ (a, yb)) => Uncurry (a -> b -> c) y z where
  uncurry_ f (a, yb) = uncurry_ (f a) yb

instance (a ~ y, b ~ z) => Uncurry (a -> b) y z where
  uncurry_ = id

要点:https://gist.github.com/Lysxia/c754f2fd6a514d66559b92469e373352