2017-02-20 5 views
2

Я создал библиотеку, которая создает последовательности, подобные спискам, и из которых реализованы многие функции стиля Prelude. Я хочу написать несколько тестовых примеров для этого, чтобы моя библиотека создавала правильный вывод, и я подумал, что самый простой способ сделать это - написать некоторые функции, преобразовать результаты в списки и сравнить их с результатами Prelude. Допустим, у нас есть это:Написание тестовых примеров в Haskell для разных типов

import qualified MyLibrary as ML 
import qualified Prelude as P 

Например, я может потребоваться следующее TestCase:

P.take 5 (P.enumFrom 1) == toList (ML.take 5 (ML.enumFrom 1)) 

Обратите внимание, что ML.enumFrom не выводит список, он выдает свою собственную тип данных.

Вышеупомянутые работы прекрасно, но обратите внимание, как я «повторяю себя» (TM). Я должен обеспечить, чтобы левая и правая стороны были одинаковыми, иначе мой тестовый сценарий ошибочен.

Есть ли хороший способ написать тестовые примеры, так что мне не нужно повторять себя?

+1

Я не думаю, что вы действительно «повторяетесь», когда ваш тест по определению «повторяется», чтобы сравнить два механизма. –

+0

В идеале все тесты принимают параметр, который определяет, какую функцию использовать, чтобы я мог просто иметь функцию 'doTest', которая похожа на' doTest test = test True == toList (test False) 'или что-то в этом роде, но я не знаю, t нашел способ сделать это без того, чтобы typechecker ненавидел меня. – Clinton

+0

@Clinton: Я думаю, что без создания экземпляра обычного класса или, возможно, с использованием шаблона haskell (с которым у меня нет опыта) вы не можете достичь этого – jakubdaniel

ответ

1

Первая проблема, что P.take и ML.take и т. Д. Выглядят одинаково - на самом деле они абсолютно несвязанные функции, и компилятор ничего не знает об их общем поведении. Так что, как @ jd823592 предложил нам необходимо сгруппировать их с классом типов (я использовал простую newtype оболочки, поэтому пример будет компилироваться):

import Prelude hiding (take, enumFrom) 
import qualified Prelude as P (take, enumFrom) 

newtype MySeq a = MySeq [a] 

class Sequence s where 
    take :: Int -> s a -> s a 
    enumFrom :: Enum a => a -> s a 
    toList :: s a -> [a] 

instance Sequence MySeq where 
    take n (MySeq xs) = MySeq (P.take n xs) 
    enumFrom n = MySeq (P.enumFrom n) 
    toList (MySeq xs) = xs 

instance Sequence [] where 
    take = P.take 
    enumFrom = P.enumFrom 
    toList = id 

Тогда мы попытаемся определить некоторые тесты, используя теперь унифицированным функции из определения класса. Они могут просто генерировать Sequence любого типа, и затем мы вынудим их создавать явные типы.

test1 = doTest (take 5 $ enumFrom 1) -- the part in brackets is polymorphic 

doTest :: (Eq a, Sequence s) => s a -> Bool 
doTest test = ??? 

Теперь вторая проблема заключается в том, что мы передаем полиморфную функцию в качестве параметра, а затем нужно создать его экземпляр с различными параметрами типа ([a] и MySeq a в данном случае). В стандартном Haskell 2010 невозможно, но мы можем воспользоваться Rank2 (or RankN) extension:

{-# LANGUAGE Rank2Types #-} 

<...> 

doTest :: forall a . Eq a => (forall s . Sequence s => s a) -> Bool 
doTest test = (test `asTypeOf` dummy1) == toList (test `asTypeOf` dummy2) where 
    dummy1 :: Eq a => [a] 
    dummy1 = undefined 
    dummy2 :: Eq a => MySeq a 
    dummy2 = undefined 

Это решение немного неуклюжая, но по-прежнему работает. Пожалуйста, не стесняйтесь улучшаться.

Смежные вопросы