Можно ли отлаживать сопоставление с образцом в функции Haskell? - PullRequest
8 голосов
/ 30 марта 2012

Я определил тип

data Expr = 
    Const Double
    | Add Expr Expr
    | Sub Expr Expr

и объявил его как экземпляр Eq класса типов:

instance Eq Expr where
    (Add (Const a1) (Const a2)) == Const b = a1+a2 == b
    (Add (Const a1) (Const a2)) == (Add (Const b1) (Const b2)) = a1+a2 == b1 + b2

Конечно, оценка выражения Sub (Const 1) (Const 1) == Const 0 не удастся. Как я могу отладить во время выполнения процесс сопоставления с образцом, чтобы определить, что он не работает? Я хотел бы увидеть, как Хаскелл принимает аргументы == и просматривает шаблоны. Возможно ли это вообще?

Ответы [ 3 ]

3 голосов
/ 31 марта 2012

изменить: дать реальный ответ на вопрос ...

Я считаю, что самый простой способ увидеть, какие шаблоны соответствуют друг другу, это добавить trace операторов, например, так:

import Debug.Trace

instance Eq Expr where
    (Add (Const a1) (Const a2)) == Const b = trace "Expr Eq pat 1" $ a1+a2 == b
    (Add (Const a1) (Const a2)) == (Add (Const b1) (Const b2)) = trace "Expr Eq pat 2" $ a1+a2 == b1 + b2
    -- catch any unmatched patterns
    l == r = error $ "Expr Eq failed pattern match. \n l: " ++ show l ++ "\n r: " ++ show r

Если вы не включите окончательный оператор для отлова каких-либо несопоставимых шаблонов, вы получите исключение времени выполнения, но я считаю, что более полезно посмотреть, какие данные вы получаете. Тогда обычно просто понять, почему он не соответствует предыдущим шаблонам.

Конечно, вы не хотите оставлять это в рабочем коде. Я только вставляю следы по мере необходимости, затем удаляю их, когда закончу. Вы также можете использовать CPP, чтобы исключить их из производственных сборок.

Я также хочу сказать, что я считаю, что сопоставление с образцом - неправильный путь для этого. Вы закончите комбинаторным взрывом числа паттернов, который быстро становится неуправляемым. Если вы хотите сделать экземпляр Float для Expr, например, вам понадобится еще несколько примитивных конструкторов.

Вместо этого, вы, вероятно, имеете функцию интерпретатора interpret :: Expr -> Double, или, по крайней мере, можете написать ее. Тогда вы можете определить

instance Eq Expr where
  l == r = interpret l == interpret r

Путем сопоставления с образцом вы по существу переписываете свою интерпретирующую функцию в экземпляре Eq. Если вы хотите создать экземпляр Ord, вам придется переписать функцию интерпретации еще раз.

2 голосов
/ 31 марта 2012

Если вы хотите получить примеры того, как сопоставление может потерпеть неудачу, вы можете взглянуть на QuickCheck руководстве (размер тестовых данных) есть пример создания и тестирования рекурсивных типов данных, которые, кажется, идеально соответствуют вашим потребностям.

В то время как флаг -Wall дает вам списокшаблонов, которые не совпадают, прогон QuickCheck дает вам примеры входных данных, которые приводят ваше предложение к провалу.Например, если я напишу генератор для вашего Expr и введу в quickCheck предложение prop_expr_eq :: Expr -> Bool, которое проверяет, равен ли Expr самому себе, я очень быстро получу Const 0.0 в качестве первого примеранесоответствующего ввода.

import Test.QuickCheck
import Control.Monad

data Expr = 
    Const Double
    | Add Expr Expr
    | Sub Expr Expr
    deriving (Show)

instance Eq Expr where
    (Add (Const a1) (Const a2)) == Const b = a1+a2 == b
    (Add (Const a1) (Const a2)) == (Add (Const b1) (Const b2)) = a1+a2 == b1 + b2

instance Arbitrary Expr where
    arbitrary = sized expr'
      where
        expr' 0 = liftM Const arbitrary
        expr' n | n > 0 = 
            let subexpr = expr' (n `div` 2)
            in oneof [liftM Const arbitrary,
                      liftM2 Add subexpr subexpr,
                      liftM2 Sub subexpr subexpr]

prop_expr_eq :: Expr -> Bool
prop_expr_eq e = e == e

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

*Main> quickCheck prop_expr_eq
*** Failed! Exception: 'test.hs:(11,5)-(12,81): Non-exhaustive patterns in function ==' (after 1 test):  
Const 0.0

PS: Еще одно хорошее чтение о модульном тестировании с QuickCheck находится в бесплатной книге real world haskell .

1 голос
/ 31 марта 2012

Вы можете разбить ваш сложный шаблон на более простые шаблоны и использовать trace, чтобы увидеть, что происходит.Примерно так:

instance Eq Expr where
    x1 == x2 | trace ("Top level: " ++ show (x, y1)) True,
               Add x11 x12 <- x1,
               trace ("First argument Add: " ++ show (x11, x12)) True,
               Const a1 <- x11,
               trace ("Matched first Const: " ++ show a1) True,
               Const a2 <- x12,
               trace ("Matched second Const: " ++ show a2) True,
               Const b <- x2
               trace ("Second argument Const: " ++ show b) True
             = a1+a2 == b

Это немного отчаянно, но отчаянные времена требуют отчаянных мер.:) Когда вы привыкаете к Haskell, вам редко, если вообще нужно, нужно это делать.

...