Parsing 分析Haskell中变量类型的元素

Parsing 分析Haskell中变量类型的元素,parsing,haskell,types,Parsing,Haskell,Types,我终于设法操纵了我的Haskell程序,这样,在最后,如果我写 let foo = bar :: A let foo = bar :: B 然后我有一个行为,如果我写 let foo = bar :: A let foo = bar :: B 然后我得到了另一个想要的行为 现在我希望我的程序能够在运行时解析这个参数,但我真的不知道如何继续。有什么建议吗 编辑:我想解析某种(文本)配置文件,我可以自由地为其编写规范/格式。 一个可能的示例是,根据配置文件中提供的进一步上下

我终于设法操纵了我的Haskell程序,这样,在最后,如果我写

  let foo = bar :: A
  let foo = bar :: B
然后我有一个行为,如果我写

  let foo = bar :: A
  let foo = bar :: B
然后我得到了另一个想要的行为

现在我希望我的程序能够在运行时解析这个参数,但我真的不知道如何继续。有什么建议吗


编辑:我想解析某种(文本)配置文件,我可以自由地为其编写规范/格式。
一个可能的示例是,根据配置文件中提供的进一步上下文,将整数读取为Int或Double,这与配置文件中的以下内容类似

barType: Int
barValue: 2
给我bar=2::Int,和

barType: Double
barValue: 2
给我酒吧=2::双倍。在这里,我可以接受任何具有Num实例的类型。
在我的例子中,我有一个带有一些方法的类型类,我想用该类型类的实例解析任何东西;根据具体类型的不同,这些方法可以做一些显著不同的事情。我不知道该如何为它编写一个Read实例


谢谢。

如果要根据实例化的类型查找不同的行为,则需要一个类型类。即使是
Read
type类的实例都是值得研究的好例子

例如

你没有说你想做什么样的分析——文本?二进制?--但这可能和重用现有的解析库以及为您的类型编写实例一样简单


或者,您可以使用解析器组合器在不同的选项之间进行选择。

按照我的理解,配置文件指定了解析器如何进一步操作。一个非常简单的例子是CSV,其中第一行决定了所有后续行中的字段数。假设所有字段都是字符串字段,我的变体是将第一行解析为解析以下行的解析器:

csvHeader :: Parser (Parser [String])
csvHeader >>= many
csvHeader
的结果是将应用于所有剩余行的解析器:

csvHeader :: Parser (Parser [String])
csvHeader >>= many
现在在您的案例中,实际结果也可以是多种类型之一。做到这一点的最简单方法是进行ADT,并提供可能的结果:

data CsvField = Coordinate Float Float | Person Name
更先进、更安全、更灵活的解决方案不是从数据的角度来考虑,而是从您可以对数据执行的操作的角度来考虑。假设您正在分析一个包含数字(整数或浮点数)的配置文件。在这两种情况下,您都希望将数字打印到屏幕上。因此,不返回实际数字,而是返回操作:

config :: Parser (IO ())
最后,如果希望在保留类型变量的同时保留有关字段中所包含内容的一些信息,则需要成为存在变量:

data Field = forall a. Field a (a -> a)
现在,您可以解析任意内容,只要返回一个值以及要应用于该值的函数:

config :: Parser Field

如果您正在这样做,我假定您有一些代码,其内容取决于foo的类型,但其返回类型不取决于foo的类型。假设您的完整代码示例是

let foo = bar :: Int
in print (foo + 1)
运行时参数传递通常由函数完成,因此首先将代码重写为函数。类型签名中的
(Show a,Num a)=>
显示某些类型类方法作为隐藏参数传递给函数

printBar :: (Show a, Num a) => a -> IO ()
printBar x = print ((bar `asTypeOf` x) + 1)
例如,
printBar(undefined::Double)
将在其主体中使用type
Double
。编译器发现该参数的类型为
Double
,并为
Double
传入类型类方法

现在,让我们编写一个函数来选择
printBar
的实际类型

invokeBar :: Bool -> (forall a. (Show a, Num a) => a -> b) -> b
invokeBar True  f = f (undefined :: Int)
invokeBar False f = f (undefined :: Double)
现在,您可以调用
invokeBar-True-printBar
invokeBar-False-printBar
来选择是使用
Int
还是
Double


无法从文件中读取“未知类型”,因为没有包含所有可能类型的文件格式。但是,您可以创建一个解析器来识别您将要使用的类型的名称,并对每个名称使用上述方法。

使用示例输入:

barType: Int
barValue: 2
由于
barType
告诉您如何解释
barValue
,因此您需要同时解析这两行,或者在解析时维护状态(使用
state
StateT
)。无论哪种情况,基本上都需要在类型上进行模式匹配。例如,如果我使用
Data.Binary.Get
进行解析,我会得到如下结果:

data Bar = IntBar Int | DoubleBar Double

parseBar :: String -> Get Bar
parseBar t = case t of 
  "Int" -> liftM IntBar $ get
  "Double" -> liftM DoubleBar $ get

重点是,无论您如何分割它,您都需要提前知道您将使用哪些类型,因为您的解析器需要考虑所有类型。这也意味着,一个表示所有可能的类似于
条的东西的类型类可能不是一个好办法;您可能需要一个简单的
条形图
ADT。

您的意思是要根据运行时输入在
a
B
类型之间进行选择吗?是的,这完全正确。例如,我不能将其解析为A或B,因为我无法编写该类型所需的必要实例;此外,兴趣有两种以上的类型,我不想列举所有的可能性,相反,我想用正确的类型来接受一切。不幸的是,我没有足够的能力从你的答案中正确理解我要做的事情。我在问题中添加了一些细节,希望能阐明我的意图。在这里的示例中,我认为
CsvField
ADT是迄今为止最好的方法。任何假设您正在对原始输入执行某些特定操作(例如打印到屏幕)的替代方法都比ADT方法灵活,ADT方法自然地将数据与适用于它的操作分离;这就是程序其余部分的工作方式。然而,你是对的,它确实如此