在Haskell中返回类型的子集

时间:2018-07-21 23:50:37

标签: haskell typeclass dependent-type liquid-haskell

我试图将函数的返回类型限制为(实物)构造函数的子集。我可以使用typeclasses来限制输入类型,但是当我对返回类型尝试如下所示的相同技术时,会出现Couldn't match type ‘s’ with ‘'A’错误。

是否有一种方法可以限制下面的bar函数以返回SomeASomeB

Liquid Haskell似乎是一种替代方案,但似乎只能使用DataKindsGADTs和/或TypeInType之类的东西来实现。

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

module Test where

data State = A | B | C

class AorB (s :: State)
instance AorB 'A
instance AorB 'B

data Some (s :: State) where
  SomeA :: Some 'A
  SomeB :: Some 'B
  SomeC :: Some 'C

-- This works
foo :: AorB s => Some s -> Bool
foo aorb = case aorb of
  SomeA -> True
  SomeB -> False

-- This fails to compile with ""Couldn't match type ‘s’ with ‘'A’""
bar :: AorB s => Some s
bar = SomeA

2 个答案:

答案 0 :(得分:4)

这里有几件事。如果使用-Wall (应该!)进行编译,则会发现foo的定义给出了详尽的模式警告。而且应该这样,因为您定义AorB的方式是“开放的”。也就是说,另一个模块中的某个人可以自由声明

instance AorB 'C

,然后您对foo的定义将突然变得无效,因为它无法处理SomeC的情况。要获得所需的内容,您应该使用封闭类型族

type family IsAorB s where
    IsAorB 'A = 'True
    IsAorB 'B = 'True
    IsAorB _  = 'False

该家族完全在State上定义。然后,我们将像这样定义您之前的AorB约束:

type AorB s = IsAorB s ~ 'True

但是,稍后我们需要以咖喱形式使用AorB,类型同义词不允许使用。有一个声明可讽刺的同义词的习惯用法,有点笨拙,但这是我们目前拥有的最好的习惯:

class    (IsAorB s ~ 'True) => AorB s
instance (IsAorB s ~ 'True) => AorB s

无论如何,有了这个新定义,您会发现foo的穷尽模式警告消失了。好。

现在继续您的问题。您定义的麻烦(为清楚起见添加了明确的forall

bar :: forall s. AorB s => Some s
bar = SomeA

是允许我们实例化bar @B并赋予我们

bar @B :: AorB B => Some B
bar = SomeA

AorB B是可以满足的,所以我们应该能够得到Some B,对吗?但不是根据您的实现。因此,这在逻辑上是错误的。您可能希望返回一个已存在量化的 s,换句话说,您希望bar函数选择它是哪个s,而不是调用者。非正式地

bar :: exists s. AorB s <AND> Some s

也就是说,bar选择一个s,然后返回Some s,以及AorB s持有的一些证据。这已不再是一个暗示,我们将不会把任何责任bar选为AB上证明给呼叫者,呼叫者不知道选择了什么。

Haskell不直接支持存在类型,但是我们可以使用GADT对其进行建模(确保使用PolyKindsConstraintKinds

data Ex c f where
     Ex :: c a => f a -> Ex c f

Ex c f可以理解为“存在a,使得c a持有,并且有一个值f a”。这是存在的,因为变量a没有出现在Ex c f中,它被构造方法隐藏了。现在我们可以实现bar

bar :: Ex AorB Some
bar = Ex SomeA

我们可以通过将约束传递给您的foo来测试约束是否正确传播:

test :: Bool
test = case bar of Ex s -> foo s

你去了。

也就是说,我只想说一个明智的设计

bar :: Some A

相反。类型签名应尽可能提供更多信息。不要隐藏您知道的信息-让抽象进行隐藏。当您隐藏有关您的假设/论据的信息时,您的签名就会更强;当您隐藏搜索结果时,就是说它变得更弱。使其坚固。

答案 1 :(得分:1)

这是基于@luqui的答案的完整工作代码,以供参考:

{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleInstances #-}

module Test2 where

data State = A | B | C

type family IsAorB (s :: State) where
  IsAorB 'A = 'True
  IsAorB 'B = 'True
  IsAorB _  = 'False

-- type AorB s = IsAorB s ~ 'True

class (IsAorB s ~ 'True) => AorB s
instance (IsAorB s ~ 'True) => AorB s

data Some (s :: State) where
  SomeA :: Some 'A
  SomeB :: Some 'B
  SomeC :: Some 'C

data Ex c f where
  Ex :: c a => f a -> Ex c f

bar :: Ex AorB Some
bar = Ex SomeA