DataKind联盟

时间:2019-02-16 05:03:48

标签: haskell data-kinds

我不确定这是否是正确的术语,但是是否可以声明接受数据类型“联合”的函数类型?

例如,我知道我可以执行以下操作:

{-# LANGUAGE DataKinds      #-}
{-# LANGUAGE GADTs          #-}

...

data Shape'
  = Circle'
  | Square'
  | Triangle'

data Shape :: Shape' -> * where
  Circle :: { radius :: Int} -> Shape Circle'
  Square :: { side :: Int} -> Shape Square'
  Triangle
    :: { a :: Int
       , b :: Int
       , c :: Int}
    -> Shape Triangle'

test1 :: Shape Circle' -> Int
test1 = undefined

但是,如果我想采用圆形或正方形的形状怎么办?如果我还想采用所有形状来实现单独的功能怎么办?

我是否可以定义一组要使用的Shape'种类型,还是可以对每个数据允许多个datakind定义?

编辑:

使用工会似乎无效:

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds       #-}
{-# LANGUAGE GADTs           #-}
{-# LANGUAGE KindSignatures  #-}
{-# LANGUAGE PolyKinds       #-}
{-# LANGUAGE TypeFamilies    #-}
{-# LANGUAGE TypeOperators   #-}

...

type family Union (a :: [k]) (r :: k) :: Constraint where
  Union (x ': xs) x = ()
  Union (x ': xs) y = Union xs y

data Shape'
  = Circle'
  | Square'
  | Triangle'

data Shape :: Shape' -> * where
  Circle :: { radius :: Int} -> Shape Circle'
  Square :: { side :: Int} -> Shape Square'
  Triangle
    :: { a :: Int
       , b :: Int
       , c :: Int}
    -> Shape Triangle'

test1 :: Union [Circle', Triangle'] s => Shape s -> Int
test1 Circle {} = undefined
test1 Triangle {} = undefined
test1 Square {} = undefined

上面的部分编译

4 个答案:

答案 0 :(得分:6)

这有点可怕,但是我想您可能需要使用Data.Type.Equality来证明它是圆形还是正方形:

test1 :: Either (s :~: Circle') (s :~: Square') -> Shape s -> Int

现在,用户必须给出一个额外的参数(“证明词”),说明它是哪个。

实际上,您可以通过以下方式使用证明术语的想法来“完善”布拉德姆的解决方案:

class MyOpClass sh where
    myOp :: Shape sh -> Int
    shapeConstraint :: Either (sh :~: Circle') (sh :~: Square')

现在没有人可以添加更多实例(除非他们使用undefined,这是不礼貌的。)

答案 1 :(得分:6)

(我认为)可以将类型族与ConstraintKindsPolyKinds一起使用,以一种相当干净的方式完成这样的事情:

type family Union (a :: [k]) (r :: k) :: Constraint where
  Union (x ': xs) x = ()
  Union (x ': xs) y = Union xs y

test1 :: Union [Circle', Triangle'] s => Shape s -> Int
test1 = undefined

上面的()是空约束(就像类型类约束的空“列表”一样)。

类型族的第一个“等式”利用类型族中可用的非线性模式匹配(它在左侧两次使用x)。类型族还利用以下事实:如果所有情况都不匹配,则不会给您有效的约束。

您还应该能够使用类型级别的布尔值来代替ConstraintKinds。那会比较麻烦,我认为最好避免在这里使用类型级别的布尔值(如果可以的话)。

旁注(我永远都记不清了,不得不为这个答案查询它):通过从Constraint导入GHC.Exts,可以看到Union

编辑:部分禁止无法访问的定义

这里是对它的修改,以(部分)禁止不可达的定义以及无效的调用。环形交叉路口稍微多一点,但似乎可行。

修改*以给出type family Union (a :: [k]) (r :: k) :: * where Union (x ': xs) x = () Union (x ': xs) y = Union xs y 而不是约束,如下所示:

()

类型的大小无关紧要,只要它具有您可以对其进行模式匹配的居民,那么我就退还test1 :: Shape s -> Union [Circle', Triangle'] s -> Int test1 Circle {} () = undefined test1 Triangle {} () = undefined -- test1 Square {} () = undefined -- This line won't compile 类型(单位类型)。

这是您将如何使用它:

x

如果忘记匹配它(例如,如果将变量名放在()上而不是在Union构造函数上匹配),则可能会定义一个无法到达的大小写。当您实际尝试达到这种情况时,它仍然会在调用站点上提供类型错误,但是(因此,即使您在test1 (Square undefined) ()参数上不匹配,调用Union也不会类型检查)。

请注意,似乎Shape自变量必须位于public class CallReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); String msg = "Phone state changed to " + state; if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) { String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER); msg += ". Incoming number is " + incomingNumber; // TODO This would be a good place to "Do something when the phone rings" ;-) } Toast.makeText(context, msg, Toast.LENGTH_LONG).show(); } } 自变量之后才能正常工作(无论如何,都已完全如此)。

答案 2 :(得分:1)

您可以使用类型类:

class MyOpClass sh where
    myOp :: Shape sh -> Int

instance MyOpClass Circle' where
    myOp (Circle r) = _

instance MyOpClass Square' where
    myOP (Square s) = _

对我来说,这并不是一个特别“完整”的解决方案-任何人都可以返回并添加另一个instance MyOpClass Triangle'-但我想不出任何其他解决方案。通过不导出类型类,您可能可以避免此问题。

答案 3 :(得分:0)

我注意到的另一种解决方案虽然很冗长,但它是创建一种具有功能布尔值列表的类型。然后,您可以在限制类型时对特征进行模式匹配:

-- [circleOrSquare] [triangleOrSquare]
data Shape' =
  Shape'' Bool
          Bool

data Shape :: Shape' -> * where
  Circle :: { radius :: Int} -> Shape (Shape'' True False)
  Square :: { side :: Int} -> Shape (Shape'' True True)
  Triangle
    :: { a :: Int
       , b :: Int
       , c :: Int}
    -> Shape (Shape'' False True)

test1 :: Shape (Shape'' True x) -> Int
test1 Circle {}   = 2
test1 Square {}   = 2
test1 Triangle {} = 2

在这里,三角形将不匹配:

    • Couldn't match type ‘'True’ with ‘'False’
      Inaccessible code in
        a pattern with constructor:
          Triangle :: Int -> Int -> Int -> Shape ('Shape'' 'False 'True),
        in an equation for ‘test1’
    • In the pattern: Triangle {}
      In an equation for ‘test1’: test1 Triangle {} = 2
   |
52 | test1 Triangle {} = 2
   |       ^^^^^^^^^^^

不幸的是,我认为您不能将此记录写为记录,这样可能会更清晰,并且避免了功能的排序。

这可能与类示例结合使用以提高可读性。