2012-08-17 4 views
2
data Foo = Bar1 
     | Bar2 Foo Foo 
     | Bar3 Foo 
     | Bar4 Foo Foo Foo 

Теперь предположим, что кто-то создал дерево Foo, и я хочу проверить, действительны ли аргументы значения Foo. Правила о аргументах конструкторов являются:Как выразить соответствие шаблону для независимых комбинаций значений данных?

  • Bar2 Bar1 Foo
  • Bar3 (Bar2|Bar3|Bar4)
  • Bar4 Bar1 Bar1 (Bar1|Bar4)

Я знаю, конструктор значения и только хочу, чтобы проверить непосредственные аргументы, ничего рекурсивного. Пример:

bar2 Bar1 Bar1  = True 
bar2 Bar1 (Bar2 {}) = True 
bar2 Bar1 (Bar3 _) = True 
bar2 Bar1 (Bar4 {}) = True 
bar2 _ _ = False 

И, например, аналогично для Bar4:

bar4 Bar1 Bar1 Bar1  = True 
bar4 Bar1 Bar1 (Bar4 {}) = True 
bar4 _ _ _ = False 

Как я могу выразить эти условия наиболее кратко? Листинг всех комбинаций в некоторых случаях слишком много. И, насколько мне известно, «OR» -syntax для сопоставления шаблонов не существует.

UPDATE

Я приспособил решение Даниила и пришел к этому:

data Foo = Bar1 
     | Bar2 Foo Foo 
     | Bar3 Foo 
     | Bar4 Foo Foo Foo 
     deriving (Data, Typeable) 

bar2 a b = a `isOf` [Bar1] && b `isOf` [Bar1,Bar2{},Bar3{},Bar4{}] 
bar4 a b c = [a,b] `areOf` [Bar1] && c `isOf` [Bar1,Bar4{}] 

isOf l r = toConstr l `elem` map toConstr r 
areOf l r = all (`isOf` r) l 

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

+0

Являются ли эти комбинации единственными законными видами использования 'Bar2',' Bar3' и 'Bar4'? (I.e - это условия, проверяющие, что они являются «законными»). Если это так, то, вероятно, лучше попытаться кодировать условия в системе типов, поэтому нет возможности их разбить. – huon

+0

Да, я тоже думал о том, чтобы делать это с типами. Но мои попытки еще не были успешными. Я попробовал это с GADT. – letmaik

+2

Просто заметьте, вы можете упростить шаблон для 'Bar2' как' bar2 Bar1 _ = True'. –

ответ

2

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

isBar1 (Bar1 {}) = True 
isBar1 _ = False 

-- ... 

isBar4 (Bar4 {}) = True 
isBar4 _ = False 

valid (Bar1)  = True 
valid (Bar2 a b) = isBar1 a 
valid (Bar3 a)  = not (isBar1 a) 
valid (Bar4 a b c) = isBar1 a && isBar1 b && (isBar1 c || isBar4 c) 

Другого вариантом является, чтобы написать функцию, которая возвращает некоторые рассказывающие данные, который был использован конструктор - скажем, пользовательский типа, как data Constructor = CBar1 | CBar2 | CBar3 | CBar4, или, как я буду сделайте ниже, что-то немного хакерское, например Int.

constr (Bar1 {}) = 1 
constr (Bar2 {}) = 2 
constr (Bar3 {}) = 3 
constr (Bar4 {}) = 4 

valid (Bar1)  = True 
valid (Bar2 a b) = constr a == 1 
valid (Bar3 a)  = constr a /= 1 
valid (Bar4 a b c) = constr a == 1 && constr b == 1 && constr c `elem` [1,4] 
+1

Первое решение, вероятно, самое легкое, но я немного колеблюсь в создании этих дополнительных функций *. Что касается второго солетона, можно ли это сделать также с материалом Constr из Data.Data? Или Типичный? Я еще не испытывал этих двух вещей. – letmaik

+0

@neo Да, 'deriving Typeable', по сути, записывает эти функции для вас, хорошая точка! –

+0

Хмм, я не уверен, что 'Typeable' действительно очень помогает здесь. Я всегда могу преобразовать значение в 'Constr', но как бы сравнить его с самоопределяемым' Constr'? например 'valid (Bar2 a b) = toConstr a == ??' – letmaik

5

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

data Foo = Foo1 Bar1 | Foo2 Bar2 | Foo3 Bar3 | Foo4 Bar4 
data Bar1 = Bar1 
data Bar2 = Bar2a Bar1 Foo 
data Bar3 = Bar3a Bar2 | Bar3b Bar3 | Bar3c Bar4 
data Bar4 = Bar4a Bar1 Bar1 Bar1 | Bar4b Bar1 Bar1 Bar4 

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

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


Edit: Возможно другое решение было бы использовать тип фантом для мечения конструкторов с помощью GADTs:

{-# LANGUAGE GADTs #-} 

data FBar1 
data FBar2 
data FBar3 
data FBar4 

data Foo a where 
    Bar1 :: Foo FBar1 
    Bar2 :: Foo FBar1 -> Foo b -> Foo FBar2 
    Bar3a :: Foo FBar2 -> Foo FBar3 
    Bar3b :: Foo FBar3 -> Foo FBar3 
    Bar3c :: Foo FBar4 -> Foo FBar3 
    Bar4a :: Foo FBar1 -> Foo FBar1 -> Foo FBar4 
    Bar4b :: Foo FBar1 -> Foo FBar4 -> Foo FBar4 

Я не уверен, если это решение не будет создавать больше проблем, чем он решает. Например, это не возможно, чтобы написать функцию, как это:

construct :: Int -> FooAny X 
construct 0 = Bar1 
construct 1 = Bar2 Bar1 Bar1 

так X должны быть как FBar1 и FBar2. Вы должны были бы existentials для этого, например, чтобы обернуть его как

data FooAny where 
    FooAny :: Foo a -> FooAny 

construct :: Int -> FooAny 
construct 0 = FooAny $ Bar1 
construct 1 = FooAny $ Bar2 Bar1 Bar1 
+0

Хм, я тоже об этом подумал, но, к сожалению, мне нужен единственный рекурсивный тип данных и не могу его разделить. – letmaik