【问题标题】:How do I create an variable alias in Nim?如何在 Nim 中创建变量别名?
【发布时间】:2018-03-20 23:57:48
【问题描述】:

我是 Nim 新手,所以这可能是一个迟钝的问题,但是如何创建一个速记别名变量以简化代码?

例如:

import sdl2
import sdl2.gfx

type
  Vector[T] = object
    x, y: T

  Ball = object
    pos: Vector[float]

  Game = ref object
    renderer: RendererPtr
    ball: array[10, Ball]

proc render(game: Game) =
  # ...

  # Render the balls
  for ix in low(game.ball)..high(game.ball):
    var ball : ref Ball = game.ball[ix]
    game.renderer.filledCircleRGBA(
        int16(game.renderer.ball[ix].pos.x),
        int16(game.renderer.ball[ix].pos.y),
        10, 100, 100, 100, 255)

  # ...

我想使用更短的别名来访问球的位置,而不是最后一部分:

  # Update the ball positions
  for ix in low(game.ball)..high(game.ball):
    ??? pos = game.ball[ix].pos
    game.renderer.filledCircleRGBA(
        int16(pos.x),
        int16(pos.y),
        10, 100, 100, 100, 255)

但是,如果我使用var 代替???,那么我似乎在pos 中创建了一个副本,这意味着原始文件没有更新。 ref 是不允许的,let 不会让我改变它。

这似乎是一件很自然的事情,所以如果 Nim 不让你这样做我会很惊讶,我只是在手册或教程中看不到任何内容。

[稍后]好吧,除了“滥用”ptr 来实现这一点,但我认为不鼓励使用 ptr,除了 C API 互操作性。

我希望像 Lisp/Haskell 的 let* 构造...

【问题讨论】:

    标签: nim-lang


    【解决方案1】:

    另一个可能更像 Nim 的解决方案是使用模板。 Nim 中的模板只是 AST 级别的简单替换。因此,如果您创建几个这样的模板:

    template posx(index: untyped): untyped = game.ball[index].pos.x.int16
    template posy(index: untyped): untyped = game.ball[index].pos.y.int16
    

    您现在可以将代码替换为:

    proc render(game: Game) =
      # Render the balls
      for ix in low(game.ball)..high(game.ball):
        var ball : ref Ball = game.ball[ix]
        game.renderer.filledCircleRGBA(
          posx(ix),
          posy(ix),
          10, 100, 100, 100, 255)
    

    这将在编译时转换为您的原始代码,并且不会产生任何开销。它还将保持与原始代码相同的类型安全性。

    当然,如果您发现自己经常这样做,您可以创建一个模板来创建模板:

    template alias(newName: untyped, call: untyped) =
      template newName(): untyped = call
    

    然后可以在您的代码中像这样使用它:

    proc render(game: Game) =
      # Render the balls
      for ix in low(game.ball)..high(game.ball):
        var ball : ref Ball = game.ball[ix]
        alias(posx, game.ball[ballIndex].pos.x.int16)
        alias(posy, game.ball[ballIndex].pos.y.int16)
        game.renderer.filledCircleRGBA(
          posx(ix),
          posy(ix),
          10, 100, 100, 100, 255)
    

    如您所见,该解决方案只有在您多次使用时才真正有用。另请注意,由于别名模板在 for 循环中展开,因此创建的模板也将在其中限定范围,因此可以共享一个名称。

    当然,在游戏设置中更正常的做法是使用更面向对象的方法(恕我直言,OO 真正有意义的少数情况之一,但这是另一个讨论)。如果您为球类型创建了一个程序,您可以使用{.this: self.} pragma 对其进行注释以节省一些输入:

    type
      A = object
        x: int
    
    {.this: self.}
    proc testproc(self: A) =
      echo x # Here we can acces x without doing self.x
    
    var t = A(x: 10)
    t.testproc()
    

    【讨论】:

    • 谢谢。似乎使用 template ball(): untyped = game.ball[ix] 在 ix 上的循环中工作,但你能确认这通常是安全的吗?
    • 是的,一般来说应该是安全的。将它放在循环中意味着它在循环内。该模板在循环之外的任何地方都不可见。
    • 补充一点,在模板中使用ix 的原因是它返回无类型。这意味着模板不会检查它返回的内容是否实际上是有效的类型等,它只是直接粘贴 AST。
    【解决方案2】:

    rules to creating a reference,因此您可能需要使用不安全指针指向Game 变量所持有的内存,如下所示:

    type
      Vector[T] = object
        x, y: T
    
      RendererPtr = ref object
        dummy: int
    
      Ball = object
        pos: Vector[float]
    
      Game = ref object
        renderer: RendererPtr
        ball: array[10, Ball]
    
    proc filledCircleRGBA(renderer: RendererPtr, x, y: int16,
        a, b, c, d, e: int) =
      discard
    
    proc render(game: Game) =
      # Render the balls
      for ix in low(game.ball)..high(game.ball):
        let ball: ptr Ball = addr game.ball[ix]
        game.renderer.filledCircleRGBA(
            int16(ball.pos.x), int16(ball.pos.y),
            10, 100, 100, 100, 255)
    

    请注意,let 仅适用于本地 ball 别名,您仍然可以改变它指向的任何内容。另一种减少打字的方法可能是围绕filledCircleRGBA 编写一个包装器,它接受Game 和您要呈现的Ball 的索引:

    proc filledCircleRGBA(renderer: RendererPtr, x, y: int16,
        a, b, c, d, e: int) =
      discard
    
    proc filledCircleRGBA(game: Game, ballIndex: int,
        a, b, c, d, e: int) =
      filledCircleRGBA(game.renderer,
        game.ball[ballIndex].pos.x.int16,
        game.ball[ballIndex].pos.y.int16,
        a, b, c, d, e)
    
    proc render(game: Game) =
      # Render the balls
      for ix in low(game.ball)..high(game.ball):
        game.filledCircleRGBA(ix, 10, 100, 100, 100, 255)
    

    根据您的性能需求,您可以 inline 包装器 proc 或将其转换为 template 以保证没有 proc 调用开销。

    【讨论】:

    • 谢谢。这是一个常见的 Nim 成语吗?我考虑过这个技巧,但认为它可能是对 ptr 的滥用,它似乎主要用作 C 互操作性后门。
    • 我不确定,您可以尝试在语言开发人员似乎喜欢的 Nim forumsany of the chat channels 上提问。但指针并没有消失,因为这是在线程和/或语言边界之间共享内存的一种方式。
    • IRC 上的答案似乎是“避免使用 ptr 解决方案”,但可以使用宏使其安全。
    • 我还没有看过,但这里概述了一个宏解决方案:youtube.com/watch?v=EC9zCXlvY2k
    猜你喜欢
    • 2011-08-02
    • 2021-04-27
    • 1970-01-01
    • 1970-01-01
    • 2020-04-03
    • 2018-05-01
    • 2013-05-16
    • 2013-06-12
    相关资源
    最近更新 更多