GADT-ы могут быть полезны для этого типа вещей.Не уверен, насколько это практично, но вы можете сопоставить с шаблоном, и это не позволит вам пройти «пустой» (все «None») случай.Будучи «гетерогенной коллекцией» Spec
может иметь произвольную длину и может указывать элементы разных типов (кортежоподобных).
{-# LANGUAGE TypeOperators, EmptyDataDecls, GADTs, TypeFamilies #-}
data Empty
data NonEmpty
-- Infix forms of data type and constructor (looks nicer here)
infixr 7 :*:
data a :*: b
-- GADT definition of heterogeneous list
-- with 'e' parameter specifing possible "emptiness" of the result (if all items in the list are 'None')
data Spec a e where
(:*:) :: Spec a e1 -> Spec b e2 -> Spec (a :*: b) (Calc e1 e2)
None :: Spec a Empty
Some :: a -> Spec a NonEmpty
-- Only when two 'Empty' Specs are cons-ed will we get Empty
type family Calc a b
type instance Calc Empty Empty = Empty
type instance Calc Empty NonEmpty = NonEmpty
type instance Calc NonEmpty Empty = NonEmpty
type instance Calc NonEmpty NonEmpty = NonEmpty
-- Example of usage
-- We need to specify the type here (GADT..) and not to forget to add 'NonEmpty'
foo :: Spec (Int :*: Bool :*: Char) NonEmpty -> Int
foo (Some 5 :*: Some _ :*: Some _) = 1
foo (Some _ :*: Some b :*: Some 'c') = if b then 2 else 22
foo (Some 4 :*: None :*: None) = 3
foo (None :*: Some _ :*: None) = 4
foo (None :*: None :*: Some 'a') = 5
foo (Some _ :*: Some _ :*: Some _) = 42
-- Some test cases:
t1 = foo (Some 5 :*: Some True :*: Some 'a') -- ==> 1
t2 = foo (Some 8 :*: Some False :*: Some 'c') -- ==> 22
t3 = foo (Some 4 :*: None :*: None) -- ==> 3
t4 = foo (None :*: Some True :*: None) -- ==> 4
t5 = foo (None :*: Some False :*: None) -- ==> 4
t6 = foo (Some 1 :*: Some True :*: Some 'e') -- ==> 42
-- t7 = foo (None :*: None :*: None) -- Will not compile due to Empty/NonEmpty mismatch (at least one item should be not 'None')
PS Также: http://homepages.cwi.nl/~ralf/HList/ «Строго типизированные гетерогенные коллекции»
ОБНОВЛЕНИЕ : следуя комментариям автора: если мы опускаем требование статической проверки в случае «всего ничего» и впоследствии избавляемся от GADT (которые действительно требуют явной спецификации типа при каждом использовании)мы можем использовать стандартный ADT плюс несколько простых вычислений на уровне типов, чтобы создать случай «всего ничего» для динамической проверки:
{-# LANGUAGE TypeOperators, FlexibleInstances #-}
infixr 7 :*:
data a :*: b = a :*: b
-- type-level manipulations against our "custom-made tuple"
-- for now it only generates a tuple with all members set to Nothing, but can be extended
class Nothingable a where
nothing :: a
instance Nothingable (Maybe a) where
nothing = Nothing
instance (Nothingable b) => Nothingable (Maybe a :*: b) where
nothing = Nothing :*: nothing
-- the same tests
foo (Just 5 :*: Just True :*: Just 'a') = 1
foo (Just _ :*: Just b :*: Just 'c') = if b then 2 else 22
foo (Just 4 :*: Nothing :*: Nothing) = 3
foo (Nothing :*: Just _ :*: Nothing) = 4
foo (Nothing :*: Nothing :*: Just 'a') = 5
foo (Just _ :*: Just _ :*: Just _) = 42
-- test for "all Nothing"
foo nothing = error "Need at least one non 'Nothing' case"
-- works for let and case bindings
boo t =
let (Just a :*: b) = t
in case b of
(Just _ :*: Nothing :*: Just c) -> c
nothing -> 0
t1 = foo (Just 5 :*: Just True :*: Just 'a') -- ==> 1
t2 = foo (Just 8 :*: Just False :*: Just 'c') -- ==> 22
t3 = foo (Just 4 :*: Nothing :*: Nothing) -- ==> 3
t4 = foo (Nothing :*: Just True :*: Nothing) -- ==> 4
t5 = foo (Nothing :*: Just False :*: Nothing) -- ==> 4
t6 = foo (Just 1 :*: Just True :*: Just 'e') -- ==> 42
t7 = foo (Nothing :*: Nothing :*: Nothing) -- ==> error
t8 = boo (Just undefined :*: Just True :*: Nothing :*: Just 5) -- ==> 5
t9 = boo (Just undefined :*: Just True :*: Nothing :*: Nothing) -- ==> 0
2-Й ОБНОВЛЕНИЕ: Пожалуйста, не обращайте внимания на мое предыдущее «Обновление»: этонеправильно.Конечно, вы не можете сравнить с функцией nothing
- здесь разрешен только конструктор данных или переменная, поэтому nothing
считается переменной (как в вашем примере: someFun nothing = nothing
isэквивалентно someFun a = a
).Тем не менее, он может быть полезен как генератор кортежей «все ничего», и, если мы добавим функцию «test» isNothing
в наш класс:
class Nothingable a where
nothing :: a
isNothing :: a -> Bool
instance Nothingable (Maybe a) where
nothing = Nothing
isNothing Nothing = True
isNothing _ = False
instance (Nothingable b) => Nothingable (Maybe a :*: b) where
nothing = Nothing :*: nothing
isNothing (Nothing :*: a) = isNothing a
isNothing _ = False
, то мы сможем использовать защиту Haskel98:
koo (Just 5 :*: Just "42" :*: Just True) = (Just True :*: Just 5.0 :*: Nothing)
koo ns | isNothing ns = nothing -- 'nothing' here generates a tuple of three members all set to Nothing
или причудливые шаблоны вида (с расширением GHC "ViewPatterns"):
koo (Just 5 :*: Just "42" :*: Just True) = (Just True :*: Just 5.0 :*: Nothing)
koo (Just 5 :*: (isNothing -> True)) = (Just True :*: Nothing :*: nothing)
и:
boo t =
let (Just a :*: b) = t
in case b of
(Just _ :*: Nothing :*: Just c) -> c
(isNothing -> True) -> 0
_ -> error "failed"
Позор мне за предыдущие Update
- это работало просто потому, что я поместил nothing
match в качестве последнего случая в определениях функций (он совпадал с любым аргументом, не принятым в предыдущих случаях - привязывая его к этой вводящей в заблуждение переменной nothing
),Извините за это!