簡體   English   中英

如何在Haskell中使用帶有可索引但可選元素的類型,該元素需要一個可索引元素

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM