«Схема сопоставления» из алгебраического типа данных конструкторов

StackOverflow https://stackoverflow.com/questions/2628104

Вопрос

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

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
Это было полезно?

Решение

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

Если по какой-то причине (застрял с объятиями?), Это не вариант, то вот очень уродливый и очень медленный взлом. Работает только в том случае, если ваш тип данных Showв состоянии (например, используя deriving (Show) - Это означает, что нет типов функций внутри, например).

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

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

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

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

Это сказал, это является Haskell 98 ... Но это о единственной хорошей вещи, которую я могу сказать об этом!

Другие советы

Еще один возможный способ:

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

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

Вам нужно будет использовать библиотеку Generics, как лома ваша котельная или UniPlate, чтобы сделать это в целом.

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

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

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

В некоторых случаях библиотека «Лом вашей котельной» поможет.

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top