数据提升语法

时间:2014-06-04 22:24:18

标签: haskell ghc type-promotion

我最近发现了singletonsData.Promotion一半。它具有大量类型系列,允许在类型级别进行基本上任意计算。我有几个关于使用的问题:

  1. ($)(%$)($$)之间有什么区别,它们是否与:++$:.$等有关?这些实际上是中缀运营商吗?我是under the impression所有中缀类型构造函数都必须以:开头。

  2. 我正在尝试在列表上映射构造函数:

    {-# 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 
    

    但是我在使用多参数类型构造函数时遇到了麻烦。想法?

  3. 我能够用Data.Promotion替换我用type family ListToConstraint (xs :: [Constraint]) :: Constraint type instance ListToConstraint '[] = () type instance ListToConstraint (x ': xs) = (x, ListToConstraint xs) 编写的所有类型函数,除了这个:

    Constraint

    {{1}}类型是否会出现某种神奇现象,会阻止其作为嵌套对进行操作?

1 个答案:

答案 0 :(得分:6)

  1. 正如评论中所解释的那样,类型级别的中缀函数不再需要以冒号开头的语法要求。所以,是的,所有这些都是中缀运营商。没有两个中缀运算符彼此自动相关,但单例库在内部使用一些命名约定,将用于 defunctionalization 的符号(见下文)与其正常对应物相关联。

  2. 由于类型族不能部分应用,但数据类型可以,因此会产生大量问题。这就是为什么单例库使用一种称为 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
    

    如果仔细观察,FlipSymNFlip部分应用于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]
    
  3. 我可能是盲目的,但我认为这不包含在单身人士中,而单身人士并不关心Constraint。不过,这是一个有用的功能。它在library I'm currently working on。它仍然未完成,大多数没有记录,这就是为什么我还没有发布它。