はい、すべてのサブケースの右辺はすべて、まったく同じ型でなければなりません。この型は、式全体の型と同じでなければなりませんcase
。これは機能です。実行時に型エラーが発生しないことをコンパイル時に言語が保証できるようにする必要があります。
あなたの質問に対するコメントのいくつかは、最も簡単な解決策は合計(別名バリアント)タイプを使用することであると述べています。
data ParserMsg = DebugMsg String | UpdateMsg [String]
この結果、一連の代替結果が事前に定義されます。これは、利点 (未処理のサブケースがないことをコードで確認できる) の場合もあれば、欠点 (サブケースの数には限りがあり、コンパイル時に決定される) の場合もあります。
場合によっては、より高度な解決策 (必要ないかもしれませんが、簡単に説明します) は、関数を data として使用するようにコードをリファクタリングすることです。アイデアは、フィールドとして関数 (またはモナド アクション) を持つデータ型を作成し、次に異なる動作 = 異なる関数をレコード フィールドとして作成するというものです。
この 2 つのスタイルをこの例と比較してください。まず、さまざまなケースを合計として指定します (これは 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)
この最初の例では、 aは aまたは a の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
a の動作は、実行時に提供されるその要素関数によって決定されるため、サブケースのコンパイル時の固定セットはありません。したがって、これらの定義に触れることなく、次を追加できます。
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)
私はこの 2 番目のスタイルを「行動型」と呼んでいますが、それはまさに私です。
GHC の型クラスはこれと同様に実装されていることに注意してください — 隠された "辞書" 引数が渡されます. この辞書はクラス メソッドの実装をメンバーとするレコードです:
data ShowDictionary a { primitiveShow :: a -> String }
stringShowDictionary :: ShowDictionary String
stringShowDictionary = ShowDictionary { primitiveShow = ... }
-- show "whatever"
-- ---> primitiveShow stringShowDictionary "whatever"