4

我终于设法安装了我的 Haskell 程序,所以最后,如果我写

  let foo = bar :: A

然后我得到一种行为,如果我写

  let foo = bar :: B

然后我得到其他想要的行为。

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


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

barType: Int
barValue: 2

给我 bar = 2 :: Int,并且

barType: Double
barValue: 2

给我 bar = 2 :: 双倍。在这种情况下,我应该能够接受任何具有 Num 实例的类型。
就我而言,我有一个带有一些方法的类型类,我想用该类型类的实例解析任何东西;根据确切的类型,这些方法可以做一些显着不同的事情。我不知道如何为此编写一个 Read 实例。

谢谢。

4

4 回答 4

3

如果您这样做,我假设您有一些代码,其内容取决于 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)使用类型。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 printBarorinvokeBar False printBar来选择是否使用Intor Double

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

于 2012-06-21T13:35:30.737 回答
2

如果您正在寻找基于被实例化的类型的不同行为,那么您需要一个类型类。学习的好例子是Data.Binary甚至Read类型类的实例。

例如

class Binary t where

    -- | Decode a value in the Get monad
    get :: Get t

你没有说你想做什么样的解析——文本?二进制?-- 但它可能就像重用现有的解析库并为您的类型编写实例一样简单。

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

于 2012-06-21T12:53:49.413 回答
2

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

csvHeader :: Parser (Parser [String])

的结果csvHeader是一个将应用于所有剩余行的解析器:

csvHeader >>= many

现在,在您的情况下,实际结果也可以是多种类型中的一种。最简单的方法是创建一个带有可能结果的 ADT:

data CsvField = Coordinate Float Float | Person Name

更先进、更安全、更灵活的解决方案不是从数据的角度考虑,而是从您可以对它们执行的操作的角度考虑。假设您正在解析一个包含数字(整数或浮点数)的配置文件。无论哪种情况,您都希望将数字打印到屏幕上。因此,不要返回实际数字,而是返回操作:

config :: Parser (IO ())

最后,如果您想在保留类型变量的同时保留有关字段中包含的任何内容的一些信息,则需要变得存在:

data Field = forall a. Field a (a -> a)

现在你可以解析任意的东西,只要你返回一个值和一个函数来应用它:

config :: Parser Field
于 2012-06-21T13:33:54.740 回答
1

使用您的示例输入:

barType: Int
barValue: 2

由于barType告诉您如何解释barValue,您需要同时解析两行,或者在解析时保持状态(使用Stateor 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

重点是,无论您如何对其进行切片,您都需要提前知道您将使用哪些类型,因为您的解析器需要考虑所有这些类型。这也意味着代表所有可能的类似Bar事物的类型类可能不是要走的路。您可能想要一个简单的BarADT。

于 2012-06-21T17:39:37.157 回答