gpt4 book ai didi

unit-testing - 单元测试功能数据结构的几种实现,无需重复代码

转载 作者:行者123 更新时间:2023-12-01 08:30:17 25 4
gpt4 key购买 nike

作为函数数据类型分配的一部分,我们被要求在 Haskell 中给出不同的队列实现,下面给出了其中的两个。

来自 OO 世界,第一个 react 是让他们实现一个通用接口(interface),以便他们可以例如分享测试代码。从我们在 Haskell 上读到的内容来看,这转化为两种数据类型,它们是通用类型类的实例。这部分相当简单:

data SimpleQueue a = SimpleQueue [a]
data FancyQueue a = FancyQueue ([a], [a])

class Queue q where
empty :: q a
enqueue :: a -> q a -> q a
dequeue :: q a -> (a, q a)

instance Queue SimpleQueue where
empty = SimpleQueue []
enqueue e (SimpleQueue xs) = SimpleQueue $ xs ++ [e]
dequeue (SimpleQueue (x:xs)) = (x, SimpleQueue xs)

instance Queue FancyQueue where
empty = FancyQueue ([], [])
enqueue e (FancyQueue (h, t)) =
if length h > length t
then FancyQueue (h, e:t)
else FancyQueue (h ++ reverse (e:t), [])
dequeue (FancyQueue ((e:h), t)) =
if length h > length t
then (e, FancyQueue (h, t))
else (e, FancyQueue (h ++ reverse t, []))

经过大量的折腾,我们得出了以下编写测试用例(使用 HUnit)的工作方式,该测试用例使用相同的函数 f 测试两种实现:

f :: (Queue q, Num a) => q a -> (a, q a)
f = dequeue . enqueue 4

makeTest = let (a, _) = f (empty :: SimpleQueue Int)
(b, _) = f (empty :: FancyQueue Int)
in assertEqual "enqueue, then dequeue" a b

test1 = makeTest

main = runTestTT (TestCase test1)

正如代码所示,我们非常感兴趣的是让函数 makeTest 将测试函数作为参数,这样我们就可以使用它来生成多个测试用例而无需复制代码将函数应用到他们身上:

makeTest t = let (a, _) = t (empty :: SimpleQueue Int)
(b, _) = t (empty :: FancyQueue Int)
in assertEqual "enqueue, then dequeue" a b

test1 = makeTest f

main = runTestTT (TestCase test1)

但是,编译失败并出现错误

queue.hs:52:30:
Couldn't match expected type `FancyQueue Int'
with actual type `SimpleQueue Int'
In the first argument of `t', namely `(empty :: SimpleQueue Int)'
In the expression: t (empty :: SimpleQueue Int)
In a pattern binding: (a, _) = t (empty :: SimpleQueue Int)

我们的问题是是否有某种方法可以使这项工作:是否可以编写一个函数来生成我们的单元测试?一个接受一个函数并将其应用于两个实现的方式,以避免重复应用该函数的代码?此外,非常欢迎对上述错误进行解释。

编辑

根据以下答案,我们最终得出以下结论:

{-# LANGUAGE RankNTypes #-}

import Test.HUnit

import Queue
import SimpleQueue
import FancyQueue

makeTest :: String -> (forall q a. (Num a, Queue q) => q a -> (a, q a)) -> Assertion
makeTest msg t = let (a, _) = t (empty :: SimpleQueue Int)
(b, _) = t (empty :: FancyQueue Int)
in assertEqual msg a b

test1 = makeTest "enqueue, then dequeue" $ dequeue . enqueue 4
test2 = makeTest "enqueue twice, then dequeue" $ dequeue . enqueue 9 . enqueue 4
test3 = makeTest "enqueue twice, then dequeue twice" $ dequeue . snd . dequeue . enqueue 9 . enqueue 4

tests = TestList $ map (\ test -> TestCase test) [test1, test2, test3]

main = runTestTT tests

我想知道 makeTest 上的类型注释是否是正确的编写方式?我试着摆弄它,但这是我唯一可以开始工作的事情。只是我认为部分 (Num a, Queue q) => 应该总是在类型本身之前。但也许这只是一个约定?或者对于更高级别的类型来说一切都不同?无论如何,可以这样写类型吗?

另外,这里并不重要,但出于好奇;使用此扩展是否会影响性能(显着)?

最佳答案

是的,您需要一个名为 Rank2Types 的语言扩展。它允许这样的功能

makeTest :: (forall q a. (Num a, Queue q) => q a -> (a, q a)) -> Assertion
makeTest t = let (a, _) = t (empty :: SimpleQueue Int)
(b, _) = t (empty :: FancyQueue Int)
in assertEqual "enqueue, then dequeue" a b

现在您要确保收到的函数是多态的,因此您可以将它应用于 SimpleQueueFancyQueue

否则,Haskell 会将 t 的第一个参数与 SimpleQueue 统一起来,然后当你尝试将它用于 FancyQueue 时会生气>。换句话说,默认情况下,Haskell 使函数参数是单态的。为了使它们具有多态性,尽管您必须使用显式签名,但 Haskell 不会推断它。

要使用此扩展程序,您需要启用它

{-# LANGUAGE RankNTypes #-}

在文件的顶部。见 here有关此扩展程序的作用及其工作原理的详细说明。

对修改的回应

这就是正确输入的方式。 Haskell 隐式转

foo :: Show a => a -> b -> c

进入

foo :: forall a b c. Show a => a -> b -> c

对于更高等级的类型,您将 forall 移动到 lambda 中,并且约束随之移动。您不能将约束一直放在左侧,因为相关的类型变量甚至不在范围内。

关于unit-testing - 单元测试功能数据结构的几种实现,无需重复代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20621301/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com