【问题标题】:Can I include formulas in a struct somehow?我可以以某种方式在结构中包含公式吗?
【发布时间】:2020-02-22 22:26:58
【问题描述】:

我正在尝试创建一个结构,该结构使用公式在输入其他两个值时自动在其中一个结构字段中创建数据。

例如,我想创建一个带有LengthWidth 输入值的二维矩形房间。然后我想在结构中包含公式Area = Length * Width

试过了,只是语法错误:

语法错误:意外 =,需要分号或换行符或 }

// CURRENT CODE
type room struct {
 L int
 W int
 A int
}
// WOULD LIKE IT TO BE
type room struct {
 L int
 W int
 A int = room.L*room.H
}

【问题讨论】:

标签: go


【解决方案1】:

如果您考虑一下您的目标,您会发现基本上您“不添加不必要的代码”的愿望实际上是关于不编写任何代码手工, em> 而不是执行任何代码:当然,如果类型定义

type room struct {
 L int
 W int
 A int = room.L*room.H
}

在 Go 中可能是可能的,这意味着 Go 编译器会比任何这样的代码做出安排

var r room
r.L = 42

以隐式变异r.A的方式编译。

换句话说,编译器必须确保对程序中room 类型的任何变量的LW 字段的任何修改也会执行计算并更新每个变量的A 字段这样的变量。

这带来了几个问题:

  • 如果您的公式比较复杂,比如A int = room.L/room.W,该怎么办?

    首先,给定 int 类型的零值的随意 Go 规则, 一个无辜的声明var r room 将立即使程序崩溃,因为编译器插入的代码执行整数除以零以强制讨论不变量。

    其次,即使我们发明了一个有问题的规则,即不根据声明(在 Go 中也是初始化)计算公式,问题仍然存在:在以下情况下会发生什么?

    var r room
    r.L = 42
    

    如您所见,即使编译器不会让程序在第一行崩溃,它也必须在第二行进行安排。

    当然,我们可以添加另一个有问题的规则来回避这个问题:要么以某种方式将每个字段“标记”为“显式设置”,要么要求用户为此类“武装”的类型提供一个显式的“构造函数”,并使用“公式” . 这两种解决方案都有其自身的缺陷:跟踪写入字段访问会导致性能成本(一些字段现在有一个隐藏标志,占用空间,并且每次访问此类字段都会花费额外的 CPU 计数),并且具有构造函数再次强调 Go 设计的基石原则之一:尽可能少地使用魔法。

  • 公式创建隐藏写入。

    在你开始为它所擅长的任务编写“更核心”的 Go 程序之前,这可能并不明显——具有大量同时工作的 goroutine 的高度并发代码——但是当你这样做时,你不得不考虑共享状态和它的突变方式以及——因此——这些突变被同步以保持程序正确的方式。

    所以,假设我们使用互斥锁保护对WL 的访问;考虑到互斥操作是显式的(即程序员显式编码锁定/解锁操作),编译器如何确保 A 的突变也受到保护?

  • (与前一个问题有些相关的问题。) 如果“公式”做了“有趣的事情”——比如访问/改变外部状态怎么办?

    这可能是任何事情,从访问全局变量到查询数据库,再到使用文件系统,再到通过 IPC 或通过网络协议进行交换。

    这一切都可能看起来很天真,比如A int = room.L * room.W * getCoefficient(),所有漂亮的细节都隐藏在getCoefficient()调用中。

    当然,我们可以再次通过对编译器施加任意限制来解决这个问题,只允许显式访问相同封闭类型的字段,并且只允许它们参与简单的没有函数调用或它们的一些“白名单”子集的表达式,例如math.Abs 或其他。 这显然会降低该功能的实用性,同时极大地复杂化语言。

  • 如果“公式”具有非线性复杂性怎么办?

    假设,对于W的值,公式是O(N³)

    然后将 W 的值设置为 0 几乎会立即处理,但将其设置为 10000 会显着降低程序速度,并且这两种结果都会导致看似不太不同的语句:r.W = 0 vs r.W = 10000.

    这再次违背了尽可能少魔法的原则。

  • 为什么我们只允许在结构类型上而不是在任意变量上做这样的事情——假设它们都在同一个词法范围内?

    这看起来像是另一个任意限制。

另一个——据说——最明显的问题是当程序员像这样的时候会发生什么

var r room
r.L = 2  // r.A is now 2×0=0
r.W = 5  // r.A is now 2×5=10
r.A = 42 // The invariant r.A = r.L×r.W is now broken

?


现在您可以看到,上述所有问题都可以通过简单地编写所需的代码来解决,例如,使用以下方法:

// use "unexported" fields
type room struct {
 l int
 w int
 a int
}

func (r *room) SetL(v int) {
  r.l = v
  updateArea()
}

func (r *room) SetW(v int) {
  r.w = v
  updateArea()
}

func (r *room) GetA() int {
  return r.a
}

func (r *room) updateArea() {
  r.a = r.l * r.w
}

通过这种方法,您可能对上述所有问题都一清二楚。

请记住,程序是为人类阅读而编写的,然后才让机器执行;对于正确的软件工程来说,最重要的是尽可能多地保留代码,而不要在代码的各个部分之间产生任何魔法或复杂的隐藏依赖关系。请记住

软件工程是发生在编程中的事情 当您添加时间和其他程序员时。

© 拉斯考克斯

See more.

【讨论】:

  • 谢谢,虽然明白你的意思,但比我能理解的要详细得多。非常感谢,我是学习 Go 的新手,一般来说编程和任何输入都会有所帮助。
【解决方案2】:

如果您想将 A 保留为字段,您可以选择在构造函数中执行计算。

type room struct {
 L int
 W int
 A int
}

func newRoom(length, width, int) room {
  return room{
    L: length,
    W: width,
    A: length * width,
  }
}

【讨论】:

  • 谢谢,只是想知道是否有任何方法可以在结构中实现它,以免添加不必要的代码。
  • 你也许可以使用一些 hacky 结构标签来做到这一点,但它不是很像 Go,也不推荐。
【解决方案3】:

由于A 是不变的,这将非常适合函数,而不是字段。

type room struct {
    L int
    W int
}

func (r *room) area() int {
    return r.L * r.W
}

【讨论】:

  • 谢谢,是的,我知道这一点,只是想知道是否有可能以另一种方式做到这一点。我不明白为什么这是不可能的。
猜你喜欢
  • 2015-07-05
  • 1970-01-01
  • 2017-07-01
  • 2018-05-24
  • 2023-03-18
  • 2012-06-14
  • 1970-01-01
  • 1970-01-01
  • 2021-09-03
相关资源
最近更新 更多