Что происходит, когда мы вызываем putPixel для координат, выходящих за границы экрана? Это зависит от реализации нашей библиотеки пикселей, но возможны три варианта:
In order to do “nothing” early returns can be a handy mechanism:
const
ScreenWidth = 1024 # 1
ScreenHeight = 768
proc safePutPixel(x, y: int; col: Color) =
if x < 0 or x >= ScreenWidth or
y < 0 or y >= ScreenHeight:
return # 2
putPixel(x, y, col) # 3
A program fragment like if not inBounds(...): return is common in graphics programming and so one can desire to move it into a helper proc.
Unfortunately, a return statement leaves the current proc and so code like the following has not the desired effect:
proc boundsCheck(x, y: int) =
if x < 0 or x >= ScreenWidth or
y < 0 or y >= ScreenHeight:
return # 1
proc safePutPixel(x, y: int; col: Color) =
boundsCheck(x, y)
putPixel(x, y, col)
Nim offers a construct which has the “inlining” semantics that we need: A template is syntactically much like a proc, but an invocation to a template means to expand the template's body at the call site:
template boundsCheck(a, b: int) = # 1
if a < 0 or a >= ScreenWidth or
b < 0 or b >= ScreenHeight:
return # 2
proc safePutPixel(x, y: int; col: Color) =
boundsCheck(x, y) # 3
putPixel(x, y, col)
int is declared.Even though boundsCheck(x, y) looks like a call, it’s not called, instead boundsCheck's body is inserted directly into safePutPixel. This insertion also does parameter substitutions; in our example the template parameter a is replaced by the procs parameter x and likewise is b replaced by y.
A template is a simple form of a macro, it is most commonly used for control flow abstractions and one can pass multiple statements to a template easily:
template wrap(body: untyped) = # 1
drawText 0, 10, "Before Body", 8, Yellow # 2
body # 3
wrap: # 4
for i in 1..3:
let textToDraw = "Welcome to Nim for the " & $i & "th time!"
drawText 10, i*10, textToDraw, 8, Yellow
drawText call runsEven though templates are based on a conceptually quite simple substitution mechanism that is completely performed at compile-time, their power is surprising. With some experience they enable a programming style that lets us abstract away many details leading to shorter programs without negatively impacting the readability.
As an example we introduce a withColor environment. Inside this environment putPixel and drawText should use a specified color implicitly so that we don’t have to repeat the color argument again and again. We declare variants of putPixel and drawText as templates that use an undeclared colorContext variable:
template putPixel(x, y: int) = putPixel(x, y, colorContext) # 1
template drawText(x, y: int; s: string) = drawText(x, y, s, colorContext) # 2
putPixel template which does not take a color delegates its work to the existing putPixel proc using the still undeclared colorContext color.drawText.Even though putPixel and drawText are already in our scope, it is valid to use the same names again for different (but in this case related) operations. The disambiguate the invocations. In our case the disambiguation is simple: A call putPixel(x, y, color) resolves to pixels.putPixel(x, y, color), whereas a call without a color parameter resolves to the newly introduced template of this name. The same applies for drawText.
Templates can easily refer to undeclared entities because only a template
expansion implies that the result is checked for semantics.
The colorContext variable is declared inside the withColor environment. It is marked with inject so that it is visible inside the body:
template withColor(col: Color; body: untyped) = # 1
let colorContext {.inject.} = col # 2
body
withColor Blue: # 3
putPixel 3, 4 # 4
drawText 10, 10, "abc", 12
withColor is a template that takes both a color and a body of code.colorContext is injected into body. Without the .inject annotation, putPixel and drawText would not be able to see the colorContext variable.putPixel ... drawText ... to body via the colon syntax.putPixel and drawText templates are invoked.After all templates are expanded the complete example looks like:
let colorContext = Blue
putPixel(3, 4, colorContext)
drawText(10, 10, "abc", colorContext)