【问题标题】:How to read the syntax `Typ{..}` in haskell? [duplicate]如何阅读haskell中的语法`Typ{..}`? [复制]
【发布时间】:2018-05-10 08:38:39
【问题描述】:

在阅读库代码 here 时,我注意到一个看起来很奇怪的语法,我无法理解:

momenta
    :: (KnownNat m, KnownNat n)
    => System m n
    -> Config n
    -> R n
momenta Sys{..} Cfg{..} = tr j #> diag _sysInertia #> j #> cfgVelocities
    --  ^^^^^^^^^^^^^^^ the syntax in question
  where
    j = _sysJacobian cfgPositions

System的相关定义包括一条记录{ _sysJacobian :: R n -> L m n },而{ cfgVelocities :: R n }Config的记录声明的一部分所以我相信我知道代码是做什么的,我认为代码可读性很强,道具给作者。

问题是:这个语法叫什么,我该如何使用它?

【问题讨论】:

  • 如果有人能想出更好的标题,请随时编辑
  • 这是RecordWildcards 扩展名,如stackoverflow.com/a/38052886/14955 所示?
  • @Thilo 确实如此。有点不幸的是,使用所有非 acii 字符的搜索引擎很难找到语法,因为已经存在这样一个广受欢迎的答案。
  • RecordWildCards 是一个相当有问题的扩展,原因与非限定通配符导入的原因相同——它使代码更容易编写,但以后更难阅读,因为你无法分辨标识符的来源;另外,记录/模块可能会添加一个字段/导出,其名称与您使用记录/模块的范围内的其他标识符冲突。一个更好的解决方案 imo 是 NamedFieldPuns,您可以将 Record { foo, bar, baz } 写成 Record { foo = foo, bar = bar, baz = baz } 的缩写(在模式或表达式中)——仍然相当简洁,但更清晰、更明确。

标签: haskell syntax


【解决方案1】:

简而言之:它是GHC 的扩展,称为RecordWildCards

在 Haskell 中,您可以使用 record syntax 来定义数据类型。例如:

data Foo = Bar { foo :: Int, bar :: String } | Qux { foo :: Int, qux :: Int }

然后我们可以在数据构造函数上进行模式匹配,并匹配零个或多个参数,例如:

someFunction :: Int -> Foo -> Foo
someFunction dd (Bar {foo=x}) = dd + x
someFunction dd (Qux {foo=x, qux=y}) = dd + x + y

但我们可能需要访问大量(甚至全部)参数。比如:

someOtherFunction :: Foo -> Int
someOtherFunction (Bar {foo=foo, bar=bar}) = foo
someOtherFunction (Qux {foo=foo, qux=qux}) = foo + qux

如果参数的数量相当大,那么这变得很麻烦。有分机RecordWildCards

{-# LANGUAGE RecordWildCards #-}

如果我们在记录模式匹配时写入{..},这将隐式写入每个参数foofoo=foo

所以我们可以这样写:

someOtherFunction :: Foo -> Int
someOtherFunction (Bar {..}) = foo
someOtherFunction (Qux {..}) = foo + qux

所以这里编译器将所有参数与同名变量进行隐式模式匹配,这样我们就可以在没有显式模式匹配的情况下访问这些参数,也不需要使用 getter。

因此,我们可以节省大量必须手动编写的大型代码块。然而,缺点是参数不再明确,因此代码更难理解。我们看到参数的使用实际上存在 getter 对应物,因此它可能会引起一些混乱。

就像@leftroundabout 所说,lenses 可能也可以做到这一点,它可以防止引入基本上影响 getter 的变量等。

您还可以将RecordWildCards与参数上的模式匹配合并,例如:

someOtherFunction :: Foo -> Int
someOtherFunction (Bar {bar=[], ..}) = foo
someOtherFunction (Bar {..}) = foo + 42
someOtherFunction (Qux {..}) = foo + qux

因此,如果带有Bar 数据构造函数的Foo 实例的bar 参数是空字符串,我们返回foo 值,否则我们将42 添加到它。

【讨论】:

  • 我认为值得一提的是,RecordWildcards 是一种更临时且可以说是劣质的解决问题的方法,而镜头可以很好地解决。
  • 在 OP 代码 j = _sysJacobian cfgPositions 中,如果 cfgPositions(未显示)是 System m n 类型会发生什么?它会引用cfgPositions 还是momenta 的第一个参数,或者它会是一个错误? ... erhm,我猜它被遮住了,所以来自第一个参数,cfgPositions :: R n?确实很令人困惑。
  • @WillNess:由于变量遮蔽了getter,它会引发错误,因为此时_sysJacobian不是getter,它被Sys对象的属性所遮蔽。
  • 你的意思是,如果cfgPositions :: System m n,它引发错误。所以它可能 cfgPositions :: R n。好的,谢谢。
  • @leftaroundabout 我不同意(或者至少不理解批评)。我发现 RecordWildcards 在你可以选择的情况下远远优于镜头。
【解决方案2】:

这是RecordWildCards 语法扩展。来自docs

对于具有许多字段的记录,在记录模式中单独写出每个字段可能会令人厌烦......记录通配符语法允许在记录模式中使用“..”,其中每个省略的字段 f 都被模式替换f = f ... 扩展是纯粹的语法,因此记录通配符表达式指的是最近的封闭变量,其拼写与省略的字段名称相同。

基本上,它将记录的字段纳入范围。

在编写编码器/解码器时特别有用(例如Aeson),但为了代码的清晰性,应谨慎使用。

【讨论】:

  • 我要补充一点,RecordWildCards 如果在定义类型的同一包中使用它,则更具可读性,但是一旦在另一个包中使用它,可能会非常不清楚这些功能/记录访问者来自。
猜你喜欢
  • 1970-01-01
  • 2017-04-20
  • 1970-01-01
  • 2013-01-06
  • 2017-11-18
  • 1970-01-01
  • 1970-01-01
  • 2018-01-29
  • 1970-01-01
相关资源
最近更新 更多