以下是如何定义 arrowfy 将函数 a -> b -> ... 转换为箭头 a `r` b `r` ...(其中 r :: Type -> Type -> Type 是您的箭头类型),以及定义函数 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
关于arrowfy @r @b @z 语法的旁注
这是 TypeApplications 语法,自 GHC 8.0 起可用。
arrowfy的类型是:
arrowfy :: forall r x y. Arrowfy r x y => x -> y
问题在于 r 不明确:在表达式中,上下文只能确定 x 和 y,而这并不一定会限制 r。
@r 注解允许我们明确地专门化arrowfy。
请注意,arrowfy 的类型参数必须以固定顺序出现:
arrowfy :: forall r x y. ...
arrowfy @r1 @b @z -- r = r1, x = b, y = z
(GHC user guide on TypeApplications)
现在,例如,如果你有一个箭头(:->),你可以写这个把它变成一个箭头:
test :: Int :-> (Int :-> Int)
test = arrowfy (+)
对于uncurry_,有一个额外的小技巧,可以将n-argument 函数转换为n-tuple 上的函数,而不是(n+1)-tuples 被一个你会天真地理解的单元所覆盖。两个实例现在都按函数类型进行索引,实际测试的是结果类型是否为函数。
-- 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
一些例子:
testUncurry :: (Int, Int) -> Int
testUncurry = uncurry_ (+)
-- combined with arr
testUncurry2 :: (Int, (Int, (Int, Int))) :-> Int
testUncurry2 = arr (uncurry_ (\a b c d -> a + b + c + d))
完整要点:https://gist.github.com/Lysxia/c754f2fd6a514d66559b92469e373352