0

我一直在玩Haskell类型类,我面临一个问题,我希望有人能帮我解决。考虑到我来自Swift背景并试图将一些面向协议的知识移植到Haskell代码中。Type Classe实例列表

起初我宣布了一堆JSON解析器其中有相同的结构,只是不同的实现:

data Candle = Candle { 
    mts :: Integer, 
    open :: Double, 
    close :: Double 
} 

data Bar = Bar { 
    mts :: Integer, 
    min :: Double, 
    max :: Double 
} 

然后,我决定创建一个“类”,将定义其基本操作:

class GenericData a where 
    dataName :: a -> String 
    dataIdentifier :: a -> Double 
    dataParsing :: a -> String -> Maybe a 
    dataEmptyInstance :: a 


instance GenericData Candle where 
    dataName _ = "Candle" 
    dataIdentifier = fromInteger . mts 
    dataParsing _ = candleParsing 
    dataEmptyInstance = emptyCandle 

instance GenericData Bar where 
    dataName _ = "Bar" 
    dataIdentifier = fromInteger . mts 
    dataParsing _ = barParsing 
    dataEmptyInstance = emptyBar 

我的第一个代码的气味是需要包括“一”,当它不需要(dataNamedataParsing),但随后我继续。

analyzeArguments :: GenericData a => [] -> [String] -> Maybe (a, [String]) 
analyzeArguments [] _    = Nothing 
analyzeArguments _ []    = Nothing 
analyzeArguments name data 
    | name == "Candles" = Just (head possibleCandidates, data) 
    | name == "Bar" = Just (last possibleRecordCandidates, data) 
    | otherwise = Nothing 

possibleCandidates :: GenericData a => [a] 
possibleCandidates = [emptyCandle, emptyBar] 

现在,当我要选择,如果这两种情况下,应选择进行解析,我总是得到以下错误

• Couldn't match expected type ‘a’ with actual type ‘Candle’ 
    ‘a’ is a rigid type variable bound by 
    the type signature for: 
     possibleCandidates :: forall a. GenericData a => [a] 
    at src/GenericRecords.hs:42:29 

我的目标是创建的GenericData因为其他实例的列表功能取决于被选择执行正确的dataParser。我知道这与类型检查程序* -> Constraint有关,但仍未找到解决此冲突的方法。我已经使用了几个GHC语言扩展,但没有一个解决了这个问题。

+2

我认为这里的代码味道可能是你的类型类。每当我看到类型类的方法都以'a'作为第一个参数时,我就会怀疑有人试图将面向对象类放在Haskell类型类中。这可能听起来像你想把'Bar'和'Candle'构造函数放在同一个ADT下。 – Alec

+3

我认为这会很快下降到一个已知的[反模式](https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/)。 – chi

+2

两个评论什么是错误的(没有提出解决方案)。 1.列表是同类的(所有元素都具有相同的类型),因此'[emptyCandle,emptyBar]'是正确的。 2.'Foo a => [a]'类型给用户这个值控制选择'Foo'的哪个实例;实现者必须准备产生一个* any类型的值的列表,这是'Foo'的一个实例,而你似乎认为这种类型的实现者可以选择*他们最喜欢的*类型,这是一个实例'Foo'。 –

回答

2

你有一个类型签名:

possibleCandidates :: GenericData a => [a] 

,你可能一点意味着你可以把任何东西在该列表中,只要它是GenericData。但这不是Haskell的类型系统实际工作的方式。值possibleCandidates可以是具有GenericData类的任何类型的列表,但列表中的每个元素必须是相同的类型。

GHC错误信息告诉你什么(以它自己的特殊方式)是列表的第一个元素是Candle,所以它认为列表的其余部分也应该是Candle类型,但第二个元素是实际上是一个Bar

现在有办法在Haskell中制作异构列表(和其他集合),但它几乎从来都不是正确的做法。这个问题

一个典型的解决方法就是合并一切分解成一个sum data type

data GenericData = GenericCandle Candle | GenericBar Bar 

你甚至可以放弃间接的步骤,而直接把蜡烛和酒吧数据直接导入的数据结构。

现在不是发类,你只需要一个数据类型和你的类功能趋于正常功能:

dataName :: GenericData -> String 
dataIdentifier :: GenericData -> Double 
dataParsing :: GenericData -> String -> Maybe a 
dataEmptyInstance :: String -> GenericData 

还有一些其他更复杂的方式来完成这项工作,但如果和数据类型适合该法案,用它。 Haskell中的解析器通常具有大数据类型(通常也是递归的)作为结果。以Aeson的标准JSON库为例,请看Value类型。

+1

我最终做了一些类似于您的解决方案的基于此博客文章: http://www.haskellforall.com/2012/05/scrap-your-type-classes.html – Invoke