Вы совершенно правы: эту задачу нужно решать рекурсивно. Фактически, вы должны иметь возможность писать isEmpty
, используя одну строку для каждого конструктора (то есть 5 строк для каждого из Empty
, Symbol
, Star
, And
и Or
) с рекурсивными вызовами обычно используется везде, где в определении конструктора появляется RegExpr
. Таким образом, строка, обрабатывающая Symbol
, не будет рекурсивной, но строка для Or
будет включать два рекурсивных вызова.
Ключ не должен совпадать со значениями c в подвыражениях из, скажем, конструктора Or
. То есть вместо того, чтобы пытаться сопоставить Or _ Empty
и всевозможные другие варианты Or
, используйте общее сопоставление Or
, которое связывает две альтернативы с подвыражениями, а затем рекурсивно работайте с этими подвыражениями. Например, с такой строкой, как:
isEmpty (Or r1 r2) = ... isEmpty r1 ... isEmpty r2 ...
, вы рекурсивно получите доступный результат isEmpty r1
, который сообщает вам, может ли r1
соответствовать пустому входу, и аналогично результат isEmpty r2
, который сообщает вам, может ли r2
соответствовать пустому вводу.
Каждая строка должна быть Haskell переводом логического оператора о связанном конструкторе, где isEmpty r
означает, что регулярное выражение r
может соответствовать пустой строке. Итак, когда у нас будет isEmpty (Or r1 r2)
(т.е. когда Or r1 r2
соответствует пустой строке)? Ну, это точно, когда либо isEmpty r1
, либо isEmpty r2
, а определение - это просто Haskell перевод этого logi c:
-- `Or` matches empty if either alternative matches empty
isEmpty (Or r1 r2) = isEmpty r1 || isEmpty r2