案例表达式中的不同类型导致Haskell

ikk*_*eps 7 haskell types case

我正在尝试在Haskell中实现某种消息解析器,所以我决定使用类型的消息类型,而不是构造函数:

data DebugMsg  = DebugMsg String
data UpdateMsg = UpdateMsg [String]
Run Code Online (Sandbox Code Playgroud)

.. 等等.我相信它对我来说更有用,因为我可以Msg为包含与此消息相关的所有信息/解析器/操作的消息定义类型类.但我这里有问题.当我尝试使用case以下方法编写解析函数时:

parseMsg :: (Msg a) => Int -> Get a
parseMsg code = 
    case code of
        1 -> (parse :: Get DebugMsg)
        2 -> (parse :: Get UpdateMsg)
Run Code Online (Sandbox Code Playgroud)

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

Tik*_*vis 9

可以用存在类型完成这样的事情,但是它不会按照你想要的方式工作,所以你真的不应该这样做.

正如您在示例中所做的那样,使用正常的多态性,根本不起作用.你的类型说的是该函数对所有人都 有效a- 也就是说,调用者可以选择接收哪种消息.但是,您必须根据数字代码选择消息,因此这显然不会.

澄清一下:默认情况下,所有标准Haskell类型变量都是普遍量化的.您可以将您的类型签名读作?a. Msg a => Int -> Get a.这就是说,无论参数是什么,函数都是为每个值定义的a.这意味着它必须能够返回a调用者想要的任何特定内容,无论它得到什么参数.

你真正想要的是什么?a. Msg a => Int -> Get a.这就是为什么我说你可以用存在类型做到这一点.但是,这在Haskell中相对复杂(你不能完全写出这样的类型签名)并且实际上不能正确解决你的问题; 这是对未来要记住的事情.

从根本上说,在Haskell中使用这样的类和类型并不是非常惯用,因为那不是类的意思.坚持使用正常的代数数据类型会更好.

我会有这样一个类型:

data Message = DebugMsg String
             | UpdateMsg [String]
Run Code Online (Sandbox Code Playgroud)

因此,不是parse每个类型都有一个函数,只需在parseMsg函数中进行适当的解析:

parseMsg :: Int -> String -> Message
parseMsg n msg = case n of
  1 -> DebugMsg msg
  2 -> UpdateMsg [msg]
Run Code Online (Sandbox Code Playgroud)

(显然填写你实际拥有的任何逻辑.)

从本质上讲,这是正常代数数据类型的经典用法.没有理由为不同类型的消息设置不同的类型,如果它们具有相同的类型,则生活会更容易.

看起来你正试图模仿其他语言的子类型.根据经验,您可以使用代数数据类型代替其他语言中子类型的大多数用法.这肯定是其中一个案例.


Lui*_*las 8

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

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

data ParserMsg = DebugMsg String | UpdateMsg [String]
Run Code Online (Sandbox Code Playgroud)

其结果是提前定义了一组替代结果.这有时是一个好处(你的代码可以确定没有未处理的子句),有时是一个缺点(有一些有限数量的子句,它们是在编译时确定的).

在某些情况下,您可能不需要更高级的解决方案,但我只是将其抛入其中 - 重构代码以将函数用作数据.我们的想法是创建一个数据类型,其中包含函数(或monadic actions)作为其字段,然后不同的行为=不同的函数作为记录字段.

将这两个样式与此示例进行比较.首先,将不同的情况指定为总和(这使用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)
Run Code Online (Sandbox Code Playgroud)

在第一个例子中,a Frame只能是a SimpleFrame或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)
Run Code Online (Sandbox Code Playgroud)

这里的Frame类型将getIndexpointIndex操作作为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)
Run Code Online (Sandbox Code Playgroud)

我把这第二种风格称为"行为类型",但这真的只是我.

请注意,GHC中的类型类与此类似地实现 - 传递了隐藏的"字典"参数,并且此字典是一个记录,其成员是类方法的实现:

data ShowDictionary a { primitiveShow :: a -> String }

stringShowDictionary :: ShowDictionary String
stringShowDictionary = ShowDictionary { primitiveShow = ... }

-- show "whatever"
-- ---> primitiveShow stringShowDictionary "whatever"
Run Code Online (Sandbox Code Playgroud)