[英]How to have a type with indexable but optional elements requiring one to exist in Haskell
我需要一個具有一定數量可索引的“插槽”的類型結構(以便我們可以分別且始終如一地對插槽1或2或3中的項目做出反應)
(也許a,也許b,也許c ...)笨拙,很難與框架做很多事情,並且允許(Nothing,Nothing ...)的表示形式,這對於我正在做的事情是不允許的。
這有效:data或ab = OrBoth ab | 或左| 或右b
並具有完全正確的語義,但是進行模式匹配是一團糟。
OrBoth tas1 (OrRight (OrRight new)) ->
OrBoth tas1 (OrRight (OrBoth _ new)) ->
OrBoth tas1 (OrBoth _ (OrRight new)) ->
OrBoth tas1 (OrBoth _ (OrBoth _ new)) ->
關於如何有效且可讀地完成此操作的其他想法?
Ed'ka的答案很好,對此我還有一個疑問:
是否可以創建合適大小的“元組”?
step :: (Nothingable a, Nothingable b) => SignalFunction a b -> a -> (SignalFunction a b, b)
step sf nothing = (sf, nothing) -- second nothing here is error
step sf a = transition sf a
src/Processors.hs:59:23:
Couldn't match expected type `b' against inferred type `a'
`b' is a rigid type variable bound by
the type signature for `step' at src/Processors.hs:58:36
`a' is a rigid type variable bound by
the type signature for `step' at src/Processors.hs:58:21
In the expression: nothing
就個人而言,我會使用Either (a,b) (Either ab)
。 但是,這對於模式匹配而言並沒有任何優勢。
您真正想要使用的是投影功能和圖案防護罩。
getLeft :: Or a b -> Maybe a
foo x
| Just a <- getLeft x, Just b <- getRight x = ...
| Just a <- getLeft x = ...
編輯:我剛剛意識到另一種方法-在您的類型上編寫消除符/變形。
import Data.Monoid
data Or a b = OrBoth a b | OrLeft a | OrRight b
orElim :: (t -> t2) -> (t1 -> t2) -> (t -> t1 -> t2) -> Or t t1 -> t2
orElim onLeft onRight onBoth x =
case x of
OrLeft a -> onLeft a
OrRight b -> onRight b
OrBoth a b -> onBoth a b
morElim :: (Monoid a) => (t -> a) -> (t1 -> a) -> Or t t1 -> a
morElim onLeft onRight x =
case x of
OrLeft a -> onLeft a
OrRight b -> onRight b
OrBoth a b -> onLeft a `mappend` onRight b
GADT-s對於這類事情可能很方便。 不確定這有多實用,但是您可以進行模式匹配,並且不允許您傳遞“空”(全為“無”)大小寫。 作為“異構集合”, 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
第二次更新:請忽略我以前的“更新”:這是錯誤的。 當然,您不能與nothing
函數 nothing
匹配-這里只允許使用數據構造函數或變量,因此, nothing
都不能視為變量 (例如在您的示例中: someFun nothing = nothing
someFun a = a
等於someFun a = a
)。 它仍然可以用作“ all Nothing”元組生成器,並且,如果我們在類中添加“ 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
或精美的視圖模式(帶有“ ViewPatterns” GHC擴展名):
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
比賽在函數定義最后的情況下(這是不匹配由以前的情況下拿起任何參數-它綁定到誤導nothing
變量)。 不好意思!
為什么你的元素OrBoth
型的Or
? 如果您知道第二個字段將是另一個字段, Or
將結構OrBoth x (Or ab)
展平,則變為OrBothA xa | OrBothB xb
OrBothA xa | OrBothB xb
。
我也打算建議viewpatterns ,但是我發現sclv可以解決這個問題!
僅當您希望Or ab
數據結構能夠包含數量有限且可管理的數據類型時,這才可以用作解決方案。
在這種情況下,您可能會做類似
data OrValue = OrInt Int | OrChar Char | OrString String | OrBool Bool
然后您可以簡單地處理[OrValue]
而不用[OrValue]
一堆數據構造函數。
請注意,這確實允許輸入完全為空(即空列表)的可能性,但檢查起來相當容易。
有關在實際應用中如何使用此技術的示例,您可以看一下Text.JSON庫(可以通過cabal install json
從cabal中獲得)-特別是其JSValue
數據類型。
玩了一下,這就是我的想法。
data Or a b = Or { extractLeft :: Maybe a, extractRight :: Maybe b }
deriving (Show, Eq)
除了記錄語法中的extractLeft和extractRight之外,還有一些方法...
-- only works for "Or" with same type in left and right!
orExtract :: Or a a -> Maybe a
orExtract (Or (Just x) Nothing) = Just x
orExtract (Or Nothing (Just y)) = Just y
orExtract _ = Nothing
orLeft :: a -> Or a b
orLeft x = Or (Just x) Nothing
lEmpty (Or Nothing _) = True
lEmpty _ = False
orRight :: b -> Or a b
orRight x = Or Nothing (Just x)
rEmpty (Or _ Nothing) = True
rEmpty _ = False
rEmpty' x = case extractRight x of Nothing -> True
_ -> False
orBoth :: a -> b -> Or a b
orBoth x y = Or (Just x) (Just y)
一些使用這種類型構建的數據(使用智能構造函數)
foo :: Or Int Char
foo = orBoth 1 's'
bar :: Or Int a
bar = orLeft 3
baz :: Or a Char
baz = orRight 'f'
一種使用防護的方法(不是模式匹配...)
orMethod orObj
| lEmpty orObj = undefined
| rEmpty orObj = undefined
| lEmpty lObj = undefined
| rEmpty rObj = undefined
where (Just lObj) = extractLeft orObj
(Just rObj) = extractRight orObj
不過,我不確定使用這種方法是否是個好主意。我更同意drvitek的方法。 您將使用有限數量的類型,因此您的數據結構可以被概括為相同的“類型”,此時您可以使用列表操作和模式。
我警告不要直接使用Or類型,我認為它過於籠統而難以編寫。
基本上,Or是一個Sum-and-product數據類型-sum是Haskell中的Either,而積是(,)。 使用sum和product-再加上null構造函數()-您可以對Haskell的代數類型建模。 這是某些泛型庫的模型,也是某些序列化格式的模型,例如ASDL(“抽象語法描述語言”)。 總和和產品視圖確實是一般性的...
但是,由於它是如此普遍,使用起來很麻煩-您確實希望盡快退出“求和與求和”視圖,並使用更直接的語法來匹配要使用的數據。 Or的另一個缺點是,當您嵌套時,它將生成非常長的類型簽名。 編寫用類型語言遍歷嵌套或結構的函數也將很困難。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.