簡體   English   中英

案例表達式中的不同類型導致Haskell

[英]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只能是SimpleFrameProductFrame ,並且必須定義每個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類型將getIndexpointIndex操作作為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.

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