wu-lee
wu-lee

Reputation: 769

How do I create an variable alias in Nim?

I'm new to Nim, so this might be an obtuse question, but how does one create a short-hand alias variable for the purpose of simplifying code?

For instance:

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)

  # ...

Instead of that last part, I'd like to use a shorter alias to access the ball position:

  # 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)

However, if I use a var in place of ???, then I seem to create a copy in pos, which then means the original isn't updated. A ref isn't allowed, and let won't let me mutate it.

This seems a natural thing to want to do, so I'd be surprised if Nim doesn't let you do it, I just can't see anything in the manuals or tutorials.

[later] Well, apart from "abusing" ptr to achieve this, but I had thought that use of ptr is discouraged except for C API interoperability.

What I'm hoping for is something like Lisp/Haskell's let* construct...

Upvotes: 2

Views: 977

Answers (2)

PMunch
PMunch

Reputation: 1719

Another solution, maybe more Nim-like, would be to use a template. Templates in Nim are just a simple substitution at the AST level. So if you create a couple templates like this:

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

You can now replace your code with:

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)

This will get converted to your original code on compile-time and not carry any overhead. It will also maintain the same type-safety of the original code.

Of course if this is something you find yourself doing often you can create a template to create the templates:

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

This can then be used like this in your code:

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)

As you can see that solution is only really useful if you use it multiple times. Also note that since the alias template is expanded within the for loop the created templates will be scoped in there as well and can therefore share a name just fine.

Of course what might be more normal in a game setting is to use a more object oriented approach (one of the few cases where OO really makes sense IMHO, but that's another discussion). If you crate a procedure for the ball type you can annotate it with the {.this: self.} pragma to save on some typing:

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()

Upvotes: 6

Grzegorz Adam Hankiewicz
Grzegorz Adam Hankiewicz

Reputation: 7661

There are rules to creating a reference so you would likely need to use an unsafe pointer into the memory held by the Game variable like this:

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)

Note that the let only applies to the local ball alias, you can still mutate whatever it is pointing at. Another way to reduce typing might be to write a wrapper around filledCircleRGBA which accepts a Game and the index to the Ball you want to render:

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)

Depending on your performance needs you could inline that wrapper proc or turn it into a template guaranteeing no proc call overhead.

Upvotes: 2

Related Questions