8

对于 DSL 语法树的表示,我有表示这棵树的数据类型。在这棵树中的几个地方,我得到了相当多的子元素,这些子元素是可选的和/或具有“*”多重性。所以一种数据类型可能看起来像

data SpecialDslExpression = MyExpression String [Int] Double [String] (Maybe Bool)

我正在寻找的是一种无需指定所有参数即可构造这种类型的可能性,假设我对每个参数都有一个有效的默认值。使用场景是这样的,我需要创建许多类型的实例,并给出或省略其参数的各种组合(大多数时候是两个或三个),但很少是全部。将参数分组为子类型不会让我走得更远,因为参数组合不遵循可以改善分割问题的模式。

我可以定义具有不同参数组合的函数,以使用其余的默认值来创建类型,但我最终可能会得到相当多的难以正确命名的函数,因为可能无法为createWithFirstAndThirdParameter在给定的上下文中的想法。

所以最后问题归结为:是否有可能创建这样的数据类型或对其进行抽象,从而为我提供诸如可以指定或省略的可选参数之类的东西?

4

2 回答 2

12

我会建议镜头和默认实例的组合。如果您还没有导入Control.Lens一半的模块,现在是时候开始了!镜头到底是什么东西?镜头是一个 getter 和一个 setter 组合成一个函数。而且它们非常可组合。每当您需要访问或修改数据结构的某些部分但您认为记录语法笨拙时,镜头就在那里。

所以,你需要做的第一件事——启用 TH 和 import Control.Lens

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

您需要对数据类型进行的修改是为所有字段添加名称,如下所示:

data SpecialDslExpression = MyExpression { _exprType :: String
                                         , _exprParams :: [Int]
                                         , _exprCost :: Double
                                         , _exprComment :: [String]
                                         , _exprLog :: Maybe Bool
                                         } deriving Show

字段名称开头的下划线对于下一步很重要。因为现在我们要为场生成镜头。我们可以要求 GHC 使用 Template Haskell 为我们做这件事。

$(makeLenses ''SpecialDslExpression)

然后需要做的最后一件事是构造一个“空”实例。请注意,没有人会静态检查您是否确实填写了所有必填字段,因此您最好在error这些字段中添加一个,这样您至少会遇到运行时错误。像这样的东西:

emptyExpression = MyExpression (error "Type field is required!") [] 0.0 [] Nothing

现在你准备好了!您不能使用emptyExpression,它会在运行时失败:

> emptyExpression
MyExpression {_exprType = "*** Exception: Type field is required!

但!只要您填充类型字段,您将是金色的:

> emptyExpression & exprType .~ "Test expression"

MyExpression { _exprType = "Test expression"
             , _exprParams = []
             , _exprCost = 0.0
             , _exprComment = []
             , _exprLog = Nothing
             }

如果需要,您也可以一次填写多个字段。

> emptyExpression & exprType .~ "Test expression"
|                 & exprLog .~ Just False
|                 & exprComment .~ ["Test comment"]

MyExpression { _exprType = "Test expression"
             , _exprParams = []
             , _exprCost = 0.0
             , _exprComment = ["Test comment"]
             , _exprLog = Just False
             }

您还可以使用镜头将功能应用于字段,或查看字段的字段内部,或修改任何其他现有表达式等。我绝对建议看看你能做什么!

于 2013-09-05T04:53:59.867 回答
11

好吧,我实际上会扩展我的评论。首先,将您的数据类型定义为记录(并输入一些类型同义词)。

data Example = E {
    one   :: Int,
    two   :: String,
    three :: Bool,
    four  :: Double
}

接下来创建一个默认实例

defaultExample = Example 1 "foo" False 1.4

然后当用户想要调整默认字段以制作自己的数据时,他们可以这样做:

 myData = defaultExample{four=2.8}

最后,当他们只想模式匹配一​​项时,他们可以使用

  foo MyData{four=a} = a
于 2013-09-05T04:31:36.553 回答