如何指定带约束的类型?

时间:2016-11-02 02:09:16

标签: haskell typeclass

我想在一个简单的演示程序中定义一组测试,其中每个测试都在本地定义,但所有测试都可以在一个标准位置打印。

例如;

t1 = ("Sqrt(4)", sqrt(4.0))
...
t2 = ("sumList:", sum [1,2,3,4])
...
t3 = ("Description", value)
...

因此,每个测试的类型为:(String,value),对于各种值类型,所有这些值(仅)必须是 Show 类的成员。

然后,对于测试的摘要,循环:

test (msg, val) = do print $ msg ++ " :: " ++ show val
tests ts        = mapM test ts

这会编译并分配以下类型:

test :: Show a => ([Char], a) -> IO ()
tests :: (Traversable t, Show a) => t ([Char], a) -> IO (t ())

只有在第二个参数的所有测试具有相同类型时才有效。我假设它以某种方式将类型特化为实际遇到的参数类型,即使它们都是可显示的。

因此,他们可以改变第二个参数的实际类型,我尝试过这样的事情(伪代码):

type ATest = (Show a) => (String, a)

因为那不起作用,我试过了:

{-# LANGUAGE RankNTypes #-}
type ATest = forall a. (Show a) => (String, a)

编译,但仍然在value参数的任何变化上失败。

此外,我想从打印它们的循环中抽象出测试类型,但我不能用它来转换:

   test :: Show a => ([Char], a) -> IO ()
to
   test :: ATest -> IO ()

基本思想是在测试循环的定义中为测试定义和使用多态类型。所以也许是数据结构;

data (Show a) => ATest =  Test (String,a)

但这也失败了,尽管它确实给出了正确的想法;所有测试都有一个共同的结构,在Show类型类中有第二个值。

这是什么方法?

2 个答案:

答案 0 :(得分:4)

让我们开始评论testtests的推断类型:

test :: Show a => ([Char], a) -> IO ()
tests :: (Traversable t, Show a) => t ([Char], a) -> IO (t ())
     

只有所有测试具有相同的类型才有效   第二个论点。我假设它以某种方式专门化类型   实际遇到的参数类型,即使它们都是   节目'能够

这是预期的。列表的所有元素(或任何其他可遍历的元素)必须具有相同的类型。它甚至不是'#34;专门化":一旦类型检查器捕获你试图例如从IntString汇总一个列表,它会立即抛出:

GHCi> [3 :: Int, "foo"]

<interactive>:125:12: error:
    • Couldn't match expected type ‘Int’ with actual type ‘[Char]’
    • In the expression: "foo"
      In the expression: [3 :: Int, "foo"]
      In an equation for ‘it’: it = [3 :: Int, "foo"]

绕过这种方法的直接方法是为元素分配一种忽略值之间无关的差异的类型。这正是你试图通过引入forall来做的事情 - 在你的情况下,你试图说明对你的第二个元素唯一重要的是有一个{{1他们的实例:

Show

你提到这种方法&#34;仍然在价值论证的任何变化上失败&#34;。我无法重现这种特定的失败模式:实际上,我甚至无法说明{-# LANGUAGE RankNTypes #-} type ATest = forall a. (Show a) => (String, a) 列表的类型:

ATest

GHC即将拒绝拒绝:由于GHCi> :set -XRankNTypes GHCi> type ATest = forall a. (Show a) => (String, a) GHCi> -- There is no special meaning to :{ and :} GHCi> -- They are merely a GHCi trick for multi-line input. GHCi> :{ GHCi| glub :: [ATest] GHCi| glub = [("Sqrt(4)", sqrt(4.0)),("sumList:", sum [1,2,3,4])] GHCi| :} <interactive>:145:9: error: • Illegal polymorphic type: ATest GHC doesn't yet support impredicative polymorphism • In the type signature: glub :: [ATest] 只是一种类型的同义词,ATest会扩展为[ATest]。列表类型构造函数的参数中的[forall a. (Show a) => (String, a)]需要一个名为impredicative polymorphism的功能,is not supported by GHC。为了避免遇到这种情况,我们需要定义一个正确的数据类型,而不仅仅是一个同义词,这是你在最后的尝试中尝试做的事情 - 除了你仍然像以前一样需要forall

forall

这最终按预期工作:

GHCi> -- We need a different extension in this case.
GHCi> :set -XExistentialQuantification 
GHCi> data ATest = forall a. Show a => ATest (String, a)
GHCi> :{
GHCi| glub :: [ATest]
GHCi| glub = [ATest ("Sqrt(4)", sqrt(4.0)),ATest ("sumList:", sum [1,2,3,4])]
GHCi| :}
GHCi> 

总的来说,建议在以这种方式承诺使用存在量化之前寻找替代方案,因为它往往比它的价值更麻烦(对于这个讨论的引言,参见例如问题和How to convert my thoughts in OOP to Haskell?)中的所有答案。但是,在这种情况下,您要做的就是方便地指定要运行的测试列表,使用此策略似乎是明智的。在Testing QuickCheck properties against multiple types?中,你可以看到一个与我们在这里使用QuickCheck非常相似的例子,这是一个完整的Haskell测试库。

答案 1 :(得分:3)

在这种情况下,不需要弯曲类型系统。你只需要

t1 = ("Sqrt(4)", show $ sqrt(4.0))
...
t2 = ("sumList:", show $ sum [1,2,3,4])
...
t3 = ("Description", show $ value)
...

由于你可以用测试结果做的唯一事情是展示它,你也可以马上做。由于懒惰,在需要结果之前不会进行实际的演出调用。

如果你有一个包含许多方法的类型类,那么存在类型可能会给你带来一些边际优势。