[英]Different types in case expression result in Haskell
我正在嘗試在Haskell中實現某種消息解析器,所以我決定使用類型的消息類型,而不是構造函數:
data DebugMsg = DebugMsg String
data UpdateMsg = UpdateMsg [String]
.. 等等。 我相信它對我來說更有用,因為我可以為消息定義類型,比如Msg
,其中包含與此消息相關的所有信息/解析器/操作。 但我這里有問題。 當我嘗試使用case
編寫解析函數時:
parseMsg :: (Msg a) => Int -> Get a
parseMsg code =
case code of
1 -> (parse :: Get DebugMsg)
2 -> (parse :: Get UpdateMsg)
案例結果的類型在所有分支中應該相同 。 有什么解決方案嗎? 甚至可能只指定函數結果的類型類並期望它是完全多態的?
你可以用存在類型來完成這樣的事情,但是它不會按照你想要的方式工作,所以你真的不應該這樣做。
正如您在示例中所做的那樣,使用正常的多態性,根本不起作用。 你是什么類型說的是,該功能適用於所有 a
凹口-是,來電者就可以選擇什么樣的消息的接收。 但是,您必須根據數字代碼選擇消息,因此這顯然不會。
澄清一下:默認情況下,所有標准Haskell類型變量都是普遍量化的。 您可以將類型簽名讀作∀a. Msg a => Int -> Get a
∀a. Msg a => Int -> Get a
。 它說的是,該函數定義的每個值a
,無論什么樣的說法可能。 這意味着它必須能夠返回調用者想要的任何特定a
,無論它獲得什么參數。
你真正想要的是像∃a. Msg a => Int -> Get a
∃a. Msg a => Int -> Get a
。 這就是為什么我說你可以用存在類型做到這一點。 但是,這在Haskell中相對復雜(你不能完全寫出這樣的類型簽名)並且實際上不能正確解決你的問題; 這是對未來要記住的事情。
從根本上說,在Haskell中使用這樣的類和類型並不是非常慣用,因為那不是類的意思。 堅持使用正常的代數數據類型會更好。
我會有這樣一個類型:
data Message = DebugMsg String
| UpdateMsg [String]
因此,不是每個類型都有一個parse
函數,只需在parseMsg
函數中進行適當的解析:
parseMsg :: Int -> String -> Message
parseMsg n msg = case n of
1 -> DebugMsg msg
2 -> UpdateMsg [msg]
(顯然填寫你實際擁有的任何邏輯。)
從本質上講,這是正常代數數據類型的經典用法。 沒有理由為不同類型的消息設置不同的類型,如果它們具有相同的類型,則生活會更容易。
看起來你正試圖模仿其他語言的子類型。 根據經驗,您可以使用代數數據類型代替其他語言中子類型的大多數用法。 這肯定是其中一個案例。
是的,所有子類的所有右側必須具有完全相同的類型; 並且此類型必須與整個case
表達式的類型相同。 這是一個特點 ; 語言必須能夠在編譯時保證運行時不會出現任何類型錯誤。
關於你的問題的一些評論提到最簡單的解決方案是使用sum(aka variant)類型:
data ParserMsg = DebugMsg String | UpdateMsg [String]
其結果是提前定義了一組替代結果。 這有時是一個好處(你的代碼可以確定沒有未處理的子句),有時是一個缺點(有一些有限數量的子句,它們是在編譯時確定的)。
在某些情況下,您可能不需要更高級的解決方案,但我只是將其拋入其中 - 重構代碼以將函數用作數據 。 這個想法是你創建一個數據類型,其中包含函數(或monadic動作)作為其字段,然后不同的行為=不同的函數作為記錄字段。
將這兩個樣式與此示例進行比較。 首先,將不同的情況指定為總和(這使用GADT,但應該足夠簡單,以便理解):
{-# LANGUAGE GADTs #-}
import Data.Vector (Vector, (!))
import qualified Data.Vector as V
type Size = Int
type Index = Int
-- | A 'Frame' translates between a set of values and consecutive array
-- indexes. (Note: this simplified implementation doesn't handle duplicate
-- values.)
data Frame p where
-- | A 'SimpleFrame' is backed by just a 'Vector'
SimpleFrame :: Vector p -> Frame p
-- | A 'ProductFrame' is a pair of 'Frame's.
ProductFrame :: Frame p -> Frame q -> Frame (p, q)
getSize :: Frame p -> Size
getSize (SimpleFrame v) = V.length v
getSize (ProductFrame f g) = getSize f * getSize g
getIndex :: Frame p -> Index -> p
getIndex (SimpleFrame v) i = v!i
getIndex (ProductFrame f g) ij =
let (i, j) = splitIndex (getSize f, getSize g) ij
in (getIndex f i, getIndex g j)
pointIndex :: Eq p => Frame p -> p -> Maybe Index
pointIndex (SimpleFrame v) p = V.elemIndex v p
pointIndex (ProductFrame f g) (p, q) =
joinIndexes (getSize f, getSize g) (pointIndex f p) (pointIndex g q)
joinIndexes :: (Size, Size) -> Index -> Index -> Index
joinIndexes (_, rsize) i j = i * rsize + j
splitIndex :: (Size, Size) -> Index -> (Index, Index)
splitIndex (_, rsize) ij = (ij `div` rsize, ij `mod` rsize)
在第一個示例中, Frame
只能是SimpleFrame
或ProductFrame
,並且必須定義每個Frame
函數來處理這兩種情況。
第二,帶有函數成員的數據類型(這兩個示例共有的代碼):
data Frame p = Frame { getSize :: Size
, getIndex :: Index -> p
, pointIndex :: p -> Maybe Index }
simpleFrame :: Eq p => Vector p -> Frame p
simpleFrame v = Frame (V.length v) (v!) (V.elemIndex v)
productFrame :: Frame p -> Frame q -> Frame (p, q)
productFrame f g = Frame newSize getI pointI
where newSize = getSize f * getSize g
getI ij = let (i, j) = splitIndex (getSize f, getSize g) ij
in (getIndex f i, getIndex g j)
pointI (p, q) = joinIndexes (getSize f, getSize g)
(pointIndex f p)
(pointIndex g q)
這里, Frame
類型將getIndex
和pointIndex
操作作為Frame
本身的數據成員。 沒有固定的編譯時子集子集,因為Frame
的行為由其在運行時提供的元素函數確定。 因此,無需觸及這些定義,我們可以添加:
import Control.Applicative ((<|>))
concatFrame :: Frame p -> Frame p -> Frame p
concatFrame f g = Frame newSize getI pointI
where newSize = getSize f + getSize g
getI ij | ij < getSize f = ij
| otherwise = ij - getSize f
pointI p = getPoint f p <|> fmap (+(getSize f)) (getPoint g p)
我把這第二種風格稱為“行為類型”,但這真的只是我。
請注意,GHC中的類型類與此類似地實現 - 傳遞了隱藏的“字典”參數,並且此字典是一個記錄,其成員是類方法的實現:
data ShowDictionary a { primitiveShow :: a -> String }
stringShowDictionary :: ShowDictionary String
stringShowDictionary = ShowDictionary { primitiveShow = ... }
-- show "whatever"
-- ---> primitiveShow stringShowDictionary "whatever"
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.