Reputation: 769
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
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
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