是的,所有子案例的所有右侧都必须具有完全相同的类型;并且此类型必须与整个case
表达式的类型相同。这是一个特点;语言需要能够在编译时保证在运行时不会出现任何类型错误。
关于您的问题的一些评论提到最简单的解决方案是使用 sum (又名变体)类型:
data ParserMsg = DebugMsg String | UpdateMsg [String]
这样做的结果是提前定义了一组替代结果。这有时是有利的(您的代码可以确定没有未处理的子案例),有时是不利的(子案例的数量是有限的,它们是在编译时确定的)。
在某些情况下,一种更高级的解决方案(您可能不需要,但我将把它扔进去)是重构代码以将函数用作数据。这个想法是你创建一个数据类型,它有函数(或单子动作)作为它的字段,然后不同的行为 = 不同的函数作为记录字段。
将这两种样式与此示例进行比较。首先,将不同的情况指定为总和(这使用 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)
在第一个示例中, aFrame
只能是 aSimpleFrame
或 a ,并且必须定义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
自身的数据成员。没有固定的编译时子案例集,因为 a 的行为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"