“Сопоставление с образцом” алгебраических конструкторов данных типа

Давайте рассмотрим тип данных со многими конструкторами:

data T = Alpha Int | Beta Int | Gamma Int Int | Delta Int

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

sameK (Alpha _) (Alpha _) = True
sameK (Beta _) (Beta _) = True
sameK (Gamma _ _) (Gamma _ _) = True
sameK _ _ = False

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

-- it’s easy to forget:
-- sameK (Delta _) (Delta _) = True

Вопрос состоит в том, как избежать шаблона в sameK? Или как удостовериться, что это проверяет на все T конструкторы?


Обходное решение, которое я нашел, должно использовать отдельные типы данных для каждого из конструкторов, происходя Data.Typeable, и объявляя класс общего типа, но мне не нравится это решение, потому что это намного менее читаемо, и иначе просто простой алгебраический тип работает на меня:

{-# LANGUAGE DeriveDataTypeable #-}

import Data.Typeable

class Tlike t where
  value :: t -> t
  value = id

data Alpha = Alpha Int deriving Typeable
data Beta = Beta Int deriving Typeable
data Gamma = Gamma Int Int deriving Typeable
data Delta = Delta Int deriving Typeable

instance Tlike Alpha
instance Tlike Beta
instance Tlike Gamma
instance Tlike Delta

sameK :: (Tlike t, Typeable t, Tlike t', Typeable t') => t -> t' -> Bool
sameK a b = typeOf a == typeOf b
13
задан sastanin 14 April 2010 в 08:49
поделиться

5 ответов

Посмотрите на модуль Data.Data, в частности на функцию toConstr . Наряду с {- # LANGUAGE DeriveDataTypeable # -} , вы получите однострочное решение, которое работает для любого типа, который является экземпляром Data.Data . Вам не нужно разбираться в SYB полностью!

Если по какой-то причине (застряли с Hugs?), Это не вариант, то это очень уродливый и очень медленный способ взлома. Он работает только в том случае, если ваш тип данных Показать может (например, с помощью извлечения (Показать) - что означает, например, отсутствие типов функций внутри).

constrT :: T -> String
constrT = head . words . show
sameK x y = constrT x == constrT y

constrT получает строковое представление внешнего конструктора значения T , показывая его, разбивая на слова и затем получая первое. Я даю явную сигнатуру типа, чтобы у вас не возникало соблазна использовать ее для других типов (и чтобы обойти ограничение мономорфизма).

Некоторые заметные недостатки:

  • Это ужасно ломается, когда ваш тип имеет инфиксные конструкторы (например, data T2 = Eta Int | T2: ^: T2 )
  • Если некоторые из ваших конструкторов имеют общий префикс, это будет происходить медленнее, поскольку приходится сравнивать большую часть строк.
  • Не работает с типами с настраиваемым показом , такими как многие типы библиотек.

Тем не менее, это Haskell 98 ... но это единственное, что я могу сказать по этому поводу!

10
ответ дан 1 December 2019 в 19:14
поделиться

Для этого вам понадобится библиотека универсальных шаблонов, например Scrap Your Boilerplate или uniplate.

Если вы не хотите быть таким жестким, вы можете использовать решение Дэйва Хинтона вместе с ярлыком пустой записи:

...
where f (Alpha {}) = 0
      f (Beta {}) = 1
      f (Gamma {}) = 2

Таким образом, вам не нужно знать, сколько аргументов имеет каждый конструктор. Но, очевидно, все же оставляет желать лучшего.

10
ответ дан 1 December 2019 в 19:14
поделиться

В некоторых случаях может помочь библиотека «Scrap Your Boilerplate».

http://www.haskell.org/haskellwiki/Scrap_your_boilerplate

2
ответ дан 1 December 2019 в 19:14
поделиться

Другой возможный способ:

sameK x y = f x == f y
  where f (Alpha _)   = 0
        f (Beta _)    = 1
        f (Gamma _ _) = 2
        -- runtime error when Delta value encountered

Ошибка во время выполнения - не идеальный вариант, но лучше, чем молча дать неправильный ответ.

14
ответ дан 1 December 2019 в 19:14
поделиться

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

В этом счастливом примере я бы не только использовал решение Дэйва Хинтона, но и наложил бы прагму INLINE на вспомогательную функцию f.

1
ответ дан 1 December 2019 в 19:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: