gpt4 book ai didi

haskell - 不同类型的case表达式导致Haskell

转载 作者:行者123 更新时间:2023-12-04 10:01:07 24 4
gpt4 key购买 nike

我正在尝试在 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)

..所有分支的案例结果类型应该相同。有什么解决办法吗?甚至可能只为函数结果指定类型类并期望它是完全多态的?

最佳答案

是的,所有子案例的所有右侧都必须具有完全相同的类型;并且这个类型必须和整个case的类型相同表达。这是一个特点;语言需要能够在编译时保证在运行时不会出现任何类型错误。

关于您的问题的一些评论提到,最简单的解决方案是使用 sum(a.k.a. variant)类型:

data ParserMsg = DebugMsg String | UpdateMsg [String]

这样做的结果是提前定义了一组替代结果。这有时是有利的(您的代码可以确定没有未处理的子案例),有时是不利的(子案例的数量是有限的,它们是在编译时确定的)。

在某些情况下,一种更高级的解决方案(您可能不需要,但我将把它扔进去)是重构代码以将函数用作数据。这个想法是你创建一个数据类型,它有函数(或单子(monad) Action )作为它的字段,然后不同的行为 = 不同的函数作为记录字段。

将这两种样式与此示例进行比较。首先,将不同的情况指定为总和(这使用 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"

关于haskell - 不同类型的case表达式导致Haskell,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14617815/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com