Позволяет говорят, что у нас есть это описание типа:
data D a = A a | B a | C a | D a | E a | F a
и хочу определить функцию по нему, которая делит конструкторов данных на 2 набора. Было бы хорошо записать что-то как этот:
g x | x `is` [A,B,C] = 1
| x `is` [D,E,F] = 2
вместо того, чтобы соответствовать на каждом конструкторе отдельно.
Там какой-либо путь состоит в том, чтобы достигнуть этого? Я посмотрел на uniplate, но не мог найти способ сделать это.
Я бы хотел, чтобы в паттернах Haskell был способ указать "OR" двух паттернов, подобно |
в OCaml:
(* ocaml code *)
let g x = match x with
A v | B v | C v -> 1
| C v | D v | E v -> 2
Если вам часто требуется сопоставление одного и того же набора конструкторов, вспомогательная функция может быть самым простым решением. Например:
getAbc :: D a -> Maybe a
getAbc (A v) = Just v
getAbc (B v) = Just v
getAbc (C v) = Just v
getAbc _ = Nothing
С такой вспомогательной функцией определение g
можно упростить следующим образом:
g x = g_ (getAbc x)
where
g_ (Just v) = 1
g_ Nothing = 2
Или, используя функцию возможно
:
g = maybe 2 (\v -> 1) . getAbc
Это немного похоже на взлом, но как насчет этого, используя Data.Data
и тип «заполнитель»?
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Data
data X = X deriving (Show, Data, Typeable)
data D a = A a | B a | C a | D a a | E a a | F a a
deriving (Show, Data, Typeable)
matchCons :: (Data a) => D a -> [D X] -> Bool
matchCons x ys = any ((== xc) . toConstr) ys
where xc = toConstr x
g :: (Data a) => D a -> Int
g x | matchCons x [A X, B X, C X] = 1
| matchCons x [D X X, E X X, F X X] = 2
Обратите внимание, что это позволяет избежать проблема сигнатуры типа / арности другого конструктора. Вероятно, есть и более чистый способ сделать что-то подобное.
Я попытался обобщить ответ @KennyTM с помощью:
data D a = A a | B a | C a a | D
deriving (Show, Eq, Functor)
class AutoBind a where
bindSome :: forall b . (a -> b) -> b
instance AutoBind Bool where bindSome f = f False
instance Num a => AutoBind a where bindSome f = f 0
class AutoConst a b | a -> b where {- bind until target type -}
bindAll :: a -> b
instance AutoBind a => AutoConst (a -> b) b where bindAll = bindSome
instance (AutoBind a, AutoConst b c) => AutoConst (a -> b) c where bindAll = bindAll . bindSome
isCons :: (Eq (f a), AutoBind a, AutoConst b (f a), Functor f) => f a -> b -> Bool
isCons x y = fmap (bindSome const) x == bindAll y
Но по какой-то причине он не работает для конструктора C
Изменить: если все конструкторы имеют одинаковый тип полей, вы можете злоупотребить Функтором:
{-# LANGUAGE DeriveFunctor #-}
data D a = A a | B a | C a | D a | E a | F a
deriving (Eq, Functor)
isCons :: (Eq (f Int), Functor f) => f a -> (Int -> f Int) -> Bool
isCons k s = fmap (const 42) k == s 42
is :: (Eq (f Int), Functor f) => f a -> [Int -> f Int] -> Bool
is k l = any (isCons k) l
g :: D a -> Int
g x | x `is` [A,B,C] = 1
| x `is` [D,E,F] = 2
Вы можете попробовать
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Data
data D a = A a | B a | C a | D a | E a | F a
deriving (Typeable, Data)
g :: Data a => D a -> Int
g x | y `elem` ["A","B","C"] = 1
| y `elem` ["D","E","F"] = 2
where y = showConstr (toConstr x)