Подтвердить что ты не робот

Проверьте, соответствует ли значение конструктору

Скажем, у меня есть такой тип данных:

data NumCol = Empty |
              Single Int |
              Pair Int Int |
              Lots [Int]

Теперь я хочу отфильтровать элементы, соответствующие данному конструктору, от [NumCol]. Я могу написать это, скажем, Pair:

get_pairs :: [NumCol] -> [NumCol]
get_pairs = filter is_pair
    where is_pair (Pair _ _) = True
          is_pair _ = False

Это работает, но не является общим. Мне нужно написать отдельную функцию для is_single, is_lots и т.д.

Вместо этого я могу написать:

get_pairs = filter (== Pair)

Но это работает только для конструкторов типов, которые не принимают аргументов (т.е. Empty).

Итак, вопрос в том, как я могу написать функцию, которая принимает значение и конструктор, и возвращает, соответствует ли значение конструктору?

4b9b3361

Ответ 1

Не менее get_pairs можно определить относительно просто, используя вместо этого определение списка:

get_pairs xs = [x | [email protected] {} <- xs]

Для более общего решения конструкторов сопоставления вы можете использовать призмы из пакета lens:

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens
import Control.Lens.Extras (is)

data NumCol = Empty |
              Single Int |
              Pair Int Int |
              Lots [Int]

-- Uses Template Haskell to create the Prisms _Empty, _Single, _Pair and _Lots
-- corresponding to your constructors
makePrisms ''NumCol

get_pairs :: [NumCol] -> [NumCol]
get_pairs = filter (is _Pair)

Ответ 2

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

Предупреждение о стрельбе:

{-# LANGUAGE GADTs, DataKinds, KindSignatures,
    TypeFamilies, PolyKinds, FlexibleInstances,
    PatternSynonyms
#-}

Шаг первый: определите версии тегов на уровне типов.

data TagType = EmptyTag | SingleTag | PairTag | LotsTag

Шаг второй: определить свидетелей уровня ценности для представления тэгов уровня. Библиотека Ричарда Эйзенберга Singletons сделает это за вас. Я имею в виду что-то вроде этого:

data Tag :: TagType -> * where
  EmptyT   :: Tag EmptyTag
  SingleT  :: Tag SingleTag
  PairT    :: Tag PairTag
  LotsT    :: Tag LotsTag

И теперь мы можем сказать, какие вещи мы ожидаем найти, связанные с данным тегом.

type family Stuff (t :: TagType) :: * where
  Stuff EmptyTag   = ()
  Stuff SingleTag  = Int
  Stuff PairTag    = (Int, Int)
  Stuff LotsTag    = [Int]

Итак, мы можем реорганизовать тип, который вы впервые подумали о

data NumCol :: * where
  (:&) :: Tag t -> Stuff t -> NumCol

и используйте PatternSynonyms, чтобы восстановить поведение, которое вы имели в виду:

pattern Empty        = EmptyT   :&  ()
pattern Single  i    = SingleT  :&  i
pattern Pair    i j  = PairT    :&  (i, j)
pattern Lots    is   = LotsT    :&  is

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

Но мы можем говорить только о тегах.

data Ex :: (k -> *) -> * where  -- wish I could say newtype here
  Witness :: p x -> Ex p

Теперь Ex Tag - это тип "тегов времени выполнения с аналогией типа". Он имеет экземпляр Eq

instance Eq (Ex Tag) where
  Witness EmptyT   ==  Witness EmptyT   = True
  Witness SingleT  ==  Witness SingleT  = True
  Witness PairT    ==  Witness PairT    = True
  Witness LotsT    ==  Witness LotsT    = True
  _                ==  _                = False

Кроме того, мы можем легко извлечь тег a NumCol.

numColTag :: NumCol -> Ex Tag
numColTag (n :& _) = Witness n

И это позволяет нам соответствовать вашей спецификации.

filter ((Witness PairT ==) . numColTag) :: [NumCol] -> [NumCol]

Возникает вопрос, действительно ли ваша спецификация вам нужна. Дело в том, что обнаружение тега дает вам представление об этом материале тега. Тип вывода [NumCol] не оправдывает того факта, что вы знаете, что у вас есть только пары.

Как вы можете затянуть тип своей функции и доставить ее?

Ответ 3

Один подход заключается в использовании модуля DataTypeable и Data.Data. Этот подход основан на двух экземплярах автоматически генерируемых экземпляров типов, которые содержат метаданные о типе для вас: Typeable и Data. Вы можете получить их с помощью {-# LANGUAGE DeriveDataTypeable #-}:

data NumCol = Empty |
          Single Int |
          Pair Int Int |
          Lots [Int] deriving (Typeable, Data)

Теперь мы имеем функцию toConstr, которая, учитывая значение, дает нам представление своего конструктора:

toConstr :: Data a => a -> Constr

Это упрощает сравнение двух терминов только по их конструкторам. Единственная оставшаяся проблема в том, что нам нужно сравнить значение, когда мы определяем наш предикат! Мы всегда можем просто создать фиктивное значение с помощью undefined, но это немного уродливо:

is_pair x = toConstr x == toConstr (Pair undefined undefined)

Итак, последнее, что мы сделаем, это определить удобный маленький класс, который автоматизирует это. Основная идея состоит в том, чтобы называть toConstr по нефункциональным значениям и возвращать на любые функции, сначала передавая в undefined.

class Constrable a where
  constr :: a -> Constr

instance Data a => Constrable a where
  constr = toConstr

instance Constrable a => Constrable (b -> a) where
  constr f = constr (f undefined)

Это зависит от FlexibleInstance, OverlappingInstances и UndecidableInstances, поэтому он может быть немного злым, но, используя (в) знаменитую теорему о глазном яблоке, все должно быть хорошо. Если вы не добавите больше экземпляров или не попытаетесь использовать его с чем-то, что не является конструктором. Тогда это может взорваться. Насильно. Нет promises.

Наконец, со злым, аккуратно содержащимся, мы можем написать оператор "equal by constructor":

(=|=) :: (Data a, Constrable b) => a -> b -> Bool
e =|= c = toConstr e == constr c

(Оператор =|= является немного мнемоническим, потому что конструкторы синтаксически определены с помощью |.)

Теперь вы можете написать почти то, что вам нужно!

filter (=|= Pair)

Кроме того, возможно, вы захотите отключить ограничение мономорфизма. Фактически, здесь список расширений, которые я включил, можно использовать:

{-# LANGUAGE DeriveDataTypeable, FlexibleInstances, NoMonomorphismRestriction, OverlappingInstances, UndecidableInstances #-}

Да, это много. Но это то, что я готов жертвовать ради дела. Не писать дополнительные undefined s.

Честно говоря, если вы не возражаете полагаться на lens (но мальчик - это зависимость от doozy), вы должны просто пойти с призрачным подходом. Единственное, что мне рекомендовать, это то, что вы можете использовать класс с названием Data.Data.Data.