我最近发现了singletons的Data.Promotion
一半。它具有大量类型系列,允许在类型级别进行基本上任意计算。我有几个关于使用的问题:
($)
,(%$)
,($$)
之间有什么区别,它们是否与:++$
,:.$
等有关?这些实际上是中缀运营商吗?我是under the impression所有中缀类型构造函数都必须以:
开头。
我正在尝试在列表上映射构造函数:
{-# LANGUAGE DataKinds, TypeOperators, PolyKinds #-}
import Data.Promotion.Prelude
data Foo a b
type MyList = '[Int, Double, Float]
-- expects one more argument to `Foo`
type FooList1 b = Map ((Flip Foo) $ b) MyList
-- parse error on the second `b`
type FooList2 b = Map (($ b) :. Foo) MyList
但是我在使用多参数类型构造函数时遇到了麻烦。想法?
我能够用Data.Promotion
替换我用type family ListToConstraint (xs :: [Constraint]) :: Constraint
type instance ListToConstraint '[] = ()
type instance ListToConstraint (x ': xs) = (x, ListToConstraint xs)
编写的所有类型函数,除了这个:
Constraint
{{1}}类型是否会出现某种神奇现象,会阻止其作为嵌套对进行操作?
答案 0 :(得分:6)
正如评论中所解释的那样,类型级别的中缀函数不再需要以冒号开头的语法要求。所以,是的,所有这些都是中缀运营商。没有两个中缀运算符彼此自动相关,但单例库在内部使用一些命名约定,将用于 defunctionalization 的符号(见下文)与其正常对应物相关联。
由于类型族不能部分应用,但数据类型可以,因此会产生大量问题。这就是为什么单例库使用一种称为 defunctionalization 的技术:对于每个部分应用的类型函数,它定义了一种数据类型。然后有一个名为Apply
的(非常大且开放的)类型系列,它采用所有这些数据类型来表示部分应用的函数和合适的参数,并执行实际的应用程序。
类型函数的这种去功能化表示的类型是
TyFun k1 k2 -> *
出于各种原因(顺便说一句,对所有这些的一个很好的介绍在Richard Eisenberg的博客文章"Defunctionalization for the win"中),而相应的“普通”类型函数的类型将是
k1 -> k2
现在,单例中的所有高阶类型函数都需要使用defunctionalized参数。例如,Map
的种类是
Map :: (TyFun k k1 -> *) -> [k] -> [k1]
而不是
Map :: (k -> k1) -> [k] -> [k1]
现在让我们来看看你正在使用的功能:
Flip :: (TyFun k (TyFun k1 k2 -> *) -> *) -> k1 -> k -> k2
第一个参数是类k -> k1 -> k2
的defunctionalized curried函数,它将它转换为类k1 -> k -> k2
的普通类型函数。
此外:
($) :: (TyFun k1 k -> *) -> k1 -> k
这只是我上面提到的Apply
的同义词。
现在让我们来看看你的例子:
type FooList1 b = Map ((Flip Foo) $ b) MyList -- fails
这里有两个问题:首先,Foo
是一种数据类型,而不是Flip
期望的非功能化符号。其次,Flip
是一个类型族,需要三个参数,但只提供一个。我们可以通过应用TyCon2
解决第一个问题,它采用普通的数据类型并将其转换为一个功能化的符号:
TyCon2 :: (k -> k1 -> k2) -> TyFun k (TyFun k1 k2 -> *) -> *
对于第二个问题,我们需要单身人士为我们定义的Flip
的部分应用之一:
FlipSym0 :: TyFun (TyFun k1 (TyFun k2 k -> *) -> *)
(TyFun k2 (TyFun k1 k -> *) -> *)
-> *
FlipSym1 :: (TyFun k1 (TyFun k2 k -> *) -> *)
-> TyFun k2 (TyFun k1 k -> *) -> *
FlipSym2 :: (TyFun k1 (TyFun k2 k -> *) -> *)
-> k2 -> TyFun k1 k -> *
Flip :: (TyFun k (TyFun k1 k2 -> *) -> *)
-> k1 -> k -> k2
如果仔细观察,FlipSymN
是Flip
部分应用于N
参数,Flip
对应于虚FlipSym3
所需的表示。在示例中,Flip
应用于一个参数,因此更正的示例变为
type FooList1 b = Map ((FlipSym1 (TyCon2 Foo)) $ b) MyList
这有效:
GHCi> :kind! FooList1 Char
FooList1 Char :: [*]
= '[Foo Int Char, Foo Double Char, Foo Float Char]
第二个例子类似:
type FooList2 b = Map (($ b) :. Foo) MyList
在这里,我们遇到以下问题:同样,Foo
必须使用TyCon2
转换为功能化的符号;诸如$ b
之类的运算符部分在类型级别上不可用,因此解析错误。我们必须再次使用Flip
,但这次FlipSym2
,因为我们将其应用于运算符$
和b
。哦,但是$
被部分应用,所以我们需要一个对应$
的符号,带有0个参数。这在单例中以$$
形式提供(对于符号运算符,defunctionalized符号附加$
s)。最后,:.
也部分应用:它需要三个运算符,但只有两个运算符。所以我们从:.
转到:.$$$
(三美元,因为一美元对应0
,三美元对应2
)。总而言之:
type FooList2 b = Map ((FlipSym2 ($$) b) :.$$$ TyCon2 Foo) MyList
再次,这有效:
GHCi> :kind! FooList2 Char
FooList2 Char :: [*]
= '[Foo Int Char, Foo Double Char, Foo Float Char]
我可能是盲目的,但我认为这不包含在单身人士中,而单身人士并不关心Constraint
。不过,这是一个有用的功能。它在library I'm currently working on。它仍然未完成,大多数没有记录,这就是为什么我还没有发布它。