2016-01-16 3 views
3

Say У меня есть функция:тестирования функции, которые возвращают Maybe Monad

safeHead :: [a] -> Maybe a 
safeHead [] = Nothing 
safeHead xs = Just $ head xs 

И тест:

describe "Example.safeHead" $ do 
    it "returns the head" $ do 
    safeHead [1,2,3] `shouldBe` Just 1 

    it "returns Nothing for an empty list" $ 
    safeHead [] `shouldBe` Nothing 

Это, однако, производит:

No instance for (Eq a0) arising from a use of ‘shouldBe’ 
The type variable ‘a0’ is ambiguous 
Note: there are several potential instances: 
    instance (Eq a, Eq b) => Eq (Either a b) 
    -- Defined in ‘Data.Either’ 
    instance forall (k :: BOX) (s :: k). Eq (Data.Proxy.Proxy s) 
    -- Defined in ‘Data.Proxy’ 
    instance (GHC.Arr.Ix i, Eq e) => Eq (GHC.Arr.Array i e) 
    -- Defined in ‘GHC.Arr’ 
    ...plus 88 others 
In the second argument of ‘($)’, namely 
    ‘safeHead [] `shouldBe` Nothing’ 
In a stmt of a 'do' block: 
    it "returns Nothing for an empty list" 
    $ safeHead [] `shouldBe` Nothing 
In the second argument of ‘($)’, namely 
    ‘do { it "returns the head" 
     $ do { safeHead [...] `shouldBe` Just 1 }; 
     it "returns Nothing for an empty list" 
     $ safeHead [] `shouldBe` Nothing }’ 

Почему? И как я могу это исправить?

+0

Тип 'safeHead [] \' shouldBe \ 'Nothing' неоднозначен, потому что нечего сообщать компилятору, какой тип элемента использовать при сравнении элементов списка (конечно, это не имеет значения, поскольку там не являются элементами, но компилятор этого не знает). Вы можете исправить это, указав явный тип: 'safeHead [] \' shouldBe \ '(Nothing :: Maybe Int)'. – user2407038

ответ

2
shouldBe    :: (Eq a, Show a) => a -> a -> Expectation 
safeHead    ::   [a] -> Maybe a 
[]      ::   [a] 
safeHead []    ::     Maybe a 
Nothing     ::     Maybe a 
shouldBe (safeHead []) :: (Eq a, Show a) => Maybe a -> Expectation 

shouldBe (safeHead []) Nothing ::      Expectation -- but what's `a`? 

Как вы можете видеть, a является полностью двусмысленным. Это может быть любой тип, который имеет Show и Eq экземпляров. Это также часть сообщения об ошибке:

 
No instance for (Eq a0) arising from a use of ‘shouldBe’ 
The type variable ‘a0’ is ambiguous 

Так выбрать один:

it "returns Nothing for an empty list" $ 
    safeHead [] `shouldBe` (Nothing :: Maybe()) 

Пока вы на него, используйте QuickCheck, чтобы убедиться, что safeHead работает как head:

it "returns Just (head xs) for a non-empty list" $ property $ \(NonEmpty xs) -> 
    safeHead xs `shouldBe` (Just (head xs) :: Maybe Integer) 
4

Как комментировал user2407038, компилятор не знает, как создать экземпляр a. Исправление, которое он предложил, вероятно, самое лучшее - вы должны указать тип a явно.

Но для полноты картины я хотел бы отметить, что существует другое решение, extended default rules:

{-# LANGUAGE ExtendedDefaultRules #-} 

describe "Example.safeHead" $ do 
    it "returns the head" $ do 
    safeHead [1,2,3] `shouldBe` Just 1 

    it "returns Nothing for an empty list" $ 
    safeHead [] `shouldBe` Nothing 

Расширение модифицирует the standard defaulting rules включать больше случаев, например, Eq type класс.

Добавлено: После некоторого размышления я немного пересмотрю свой ответ. Результаты модульного тестирования полиморфных функций не должны зависеть от конкретного способа создания экземпляров переменных типа. Вероятно, расширенные правила по умолчанию - это правильная вещь в тестах? Я никогда не использовал их в настоящем коде, поэтому я не могу сказать точно, но это определенно стоит подумать.

+0

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

+0

@AbrahamP в принципе да, меньше набрав, вероятно, единственные плюсы :) – Yuras

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