#haskell
#haskell
Вопрос:
Я ищу лучший способ проверить, все ли значения данного типа равны.
Например, рассмотрим:
data Foo = Foo {a :: Int, b :: Int, c :: Int, d :: Int}
allFooEqual :: Foo -> Bool
allFooEqual foo = (a foo == b foo) amp;amp; (a foo == c foo) amp;amp; (a foo == d foo)
Это работает, но это не совсем масштабируемое решение. Есть ли более идиоматический способ выполнения этого типа действий, который мне не хватает?
Комментарии:
1. Этот вопрос кажется мне довольно странным. Зачем вам это нужно? Записи обычно не являются тем, что вам нужно для такой операции. Возможно, вам нужны векторы с индексом длины?
Ответ №1:
Альтернативный:
allFooEqual :: Foo -> Bool
allFooEqual (Foo xa xb xc xd) = all (xa==) [xb,xc,xd]
Это будет сравнивать xa == xb amp;amp; xa == xc amp;amp; xa == xd
.
Другой:
atMostOne :: Eq a => [a] -> Bool
atMostOne xs = and $ zipWith (==) xs (drop 1 xs)
allFooEqual :: Foo -> Bool
allFooEqual (Foo xa xb xc xd) = atMostOne [xa,xb,xc,xd]
Это будет сравнивать xa == xb amp;amp; xb == xc amp;amp; xc == xd
.
Доступ ко «всем Int
s в a Foo
» может быть выполнен с использованием фреймворка scrap-your-boilerplate или универсальных GHC, но это выглядит излишним, если у вас действительно много полей.
allFooEqual :: Foo -> Bool
allFooEqual f = atMostOne [ x
| Just x <- gmapQ (mkQ Nothing (Just :: Int -> Maybe Int)) f ]
Здесь так много вещей на уровне типов, что я настоятельно рекомендую отказаться от этого, если это действительно, действительно необходимо.
Комментарии:
1. Ага, сопоставление шаблонов для извлечения значений кажется мне гораздо лучшим решением. Спасибо! Я оставлю этот вопрос открытым на некоторое время, чтобы посмотреть, привлекает ли он какие-либо другие ответы, поскольку вы нажмете на него через ~ 5 минут.
2. Не могли бы вы подробнее остановиться на этой второй части? Это кажется полезным, но я никогда не видел его раньше.
3. Вы могли бы использовать
RecordWildCards
, как вallFooEqual (Foo {..}) = all (a==) [b,c,d]
4. @jkeuhlen Я кое-что добавил, но шаблонный шаблон не может быть легко обобщен. Если вы хотите изучить это, должны быть хорошие учебные пособия, но будьте готовы к нетривиальному путешествию. Я бы обошелся без этого, когда это возможно.
5. @ThomasM.DuBuisson Я не большой поклонник этого расширения, поскольку оно скрывает средства доступа и может использоваться только при наличии одной записи в области видимости.
Ответ №2:
Это, очевидно, излишне, но вот GHC.Generics
основанное на этом решение, которое позволяет вам автоматически генерировать класс FieldsMatch
типов, который предоставляет такую функцию fieldsMatch :: FieldsMatch a => a -> Bool
, которая возвращает true, если все поля одного и того же типа в вашей записи имеют одинаковое значение.
{-# LANGUAGE TypeOperators, ExistentialQuantification, DefaultSignatures,
FlexibleContexts #-}
module FieldsMatch (FieldsMatch(..)) where
import GHC.Generics
import Data.Typeable
-- `Some` is an existential type that we need to store each field
data Some = forall a. (Eq a, Typeable a) => Some a
-- This is the class we will be deriving
class FieldsMatch a where
-- in general, this is the type of `fieldsMatch`...
fieldsMatch :: a -> Bool
-- ... except the default implementation has slightly different constraints.
default fieldsMatch :: (Generic a, GetFields (Rep a)) => a -> Bool
fieldsMatch = noneDiffering . getFields . from
where
noneDiffering :: [Some] -> Bool
noneDiffering [] = True
noneDiffering (x:xs) = all (notDiffering x) xs amp;amp; noneDiffering xs
notDiffering :: Some -> Some -> Bool
Some x `notDiffering` Some y = case cast y of
Nothing -> True
Just z -> x == z
class GetFields f where
-- | This function takes the generic representation of a datatype and
-- recursively traverses it to collect all its fields. These need to
-- have types satisfying `Eq` and `Typeable`.
getFields :: f a -> [Some]
instance (GetFields a, GetFields b) => GetFields (a :*: b) where
getFields (l :*: r) = getFields l getFields r
instance (GetFields a, GetFields b) => GetFields (a : : b) where
getFields (L1 l) = getFields l
getFields (R1 r) = getFields r
instance GetFields U1 where
getFields U1 = []
instance (Typeable a, Eq a) => GetFields (K1 i a) where
getFields (K1 x) = [Some x]
instance GetFields a => GetFields (M1 i t a) where
getFields (M1 x) = getFields x
default fieldsMatch :: (Generic a, GetFields (Rep a)) => a -> Bool
fieldsMatch = noneDiffering . getFields . from
where
noneDiffering :: [Some] -> Bool
noneDiffering [] = True
noneDiffering (x:xs) = all (notDiffering x) xs || noneDiffering xs
notDiffering :: Some -> Some -> Bool
Some x `notDiffering` Some y = case cast y of
Nothing -> True
Just z -> x == z
Вы можете попробовать это в GHCi:
ghci> :set -XDeriveGeneric
ghci> data Foo b = Foo Int Int b Bool deriving (Generic)
ghci> instance (Eq b, Typeable b) => FieldsMatch (Foo b)
ghci> Foo 1 1 True True -- fields of the same type are equal
True
ghci> Foo 1 2 True (1,2) -- 1 /= 2 even though they are the same type
False