Remove Covariance section from the experimental manual (#18688)
* Remove `Covariance` section * Add blank lines after `.. code-block::` * Fix CI?
This commit is contained in:
parent
c07d8da7b9
commit
0ddd9519c0
1 changed files with 63 additions and 101 deletions
|
|
@ -70,6 +70,7 @@ type `void` are treated as non-existent, `void` as a return type means that
|
||||||
the procedure does not return a value:
|
the procedure does not return a value:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
proc nothing(x, y: void): void =
|
proc nothing(x, y: void): void =
|
||||||
echo "ha"
|
echo "ha"
|
||||||
|
|
||||||
|
|
@ -78,6 +79,7 @@ the procedure does not return a value:
|
||||||
The `void` type is particularly useful for generic code:
|
The `void` type is particularly useful for generic code:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
proc callProc[T](p: proc (x: T), x: T) =
|
proc callProc[T](p: proc (x: T), x: T) =
|
||||||
when T is void:
|
when T is void:
|
||||||
p()
|
p()
|
||||||
|
|
@ -93,6 +95,7 @@ The `void` type is particularly useful for generic code:
|
||||||
However, a `void` type cannot be inferred in generic code:
|
However, a `void` type cannot be inferred in generic code:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
callProc(emptyProc)
|
callProc(emptyProc)
|
||||||
# Error: type mismatch: got (proc ())
|
# Error: type mismatch: got (proc ())
|
||||||
# but expected one of:
|
# but expected one of:
|
||||||
|
|
@ -102,106 +105,6 @@ The `void` type is only valid for parameters and return types; other symbols
|
||||||
cannot have the type `void`.
|
cannot have the type `void`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Covariance
|
|
||||||
==========
|
|
||||||
|
|
||||||
Covariance in Nim can be introduced only through pointer-like types such
|
|
||||||
as `ptr` and `ref`. Sequence, Array and OpenArray types, instantiated
|
|
||||||
with pointer-like types will be considered covariant if and only if they
|
|
||||||
are also immutable. The introduction of a `var` modifier or additional
|
|
||||||
`ptr` or `ref` indirections would result in invariant treatment of
|
|
||||||
these types.
|
|
||||||
|
|
||||||
`proc` types are currently always invariant, but future versions of Nim
|
|
||||||
may relax this rule.
|
|
||||||
|
|
||||||
User-defined generic types may also be covariant with respect to some of
|
|
||||||
their parameters. By default, all generic params are considered invariant,
|
|
||||||
but you may choose the apply the prefix modifier `in` to a parameter to
|
|
||||||
make it contravariant or `out` to make it covariant:
|
|
||||||
|
|
||||||
.. code-block:: nim
|
|
||||||
type
|
|
||||||
AnnotatedPtr[out T] =
|
|
||||||
metadata: MyTypeInfo
|
|
||||||
p: ref T
|
|
||||||
|
|
||||||
RingBuffer[out T] =
|
|
||||||
startPos: int
|
|
||||||
data: seq[T]
|
|
||||||
|
|
||||||
Action {.importcpp: "std::function<void ('0)>".} [in T] = object
|
|
||||||
|
|
||||||
When the designated generic parameter is used to instantiate a pointer-like
|
|
||||||
type as in the case of `AnnotatedPtr` above, the resulting generic type will
|
|
||||||
also have pointer-like covariance:
|
|
||||||
|
|
||||||
.. code-block:: nim
|
|
||||||
type
|
|
||||||
GuiWidget = object of RootObj
|
|
||||||
Button = object of GuiWidget
|
|
||||||
ComboBox = object of GuiWidget
|
|
||||||
|
|
||||||
var
|
|
||||||
widgetPtr: AnnotatedPtr[GuiWidget]
|
|
||||||
buttonPtr: AnnotatedPtr[Button]
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
proc drawWidget[T](x: AnnotatedPtr[GuiWidget]) = ...
|
|
||||||
|
|
||||||
# you can call procs expecting base types by supplying a derived type
|
|
||||||
drawWidget(buttonPtr)
|
|
||||||
|
|
||||||
# and you can convert more-specific pointer types to more general ones
|
|
||||||
widgetPtr = buttonPtr
|
|
||||||
|
|
||||||
Just like with regular pointers, covariance will be enabled only for immutable
|
|
||||||
values:
|
|
||||||
|
|
||||||
.. code-block:: nim
|
|
||||||
proc makeComboBox[T](x: var AnnotatedPtr[GuiWidget]) =
|
|
||||||
x.p = new(ComboBox)
|
|
||||||
|
|
||||||
makeComboBox(buttonPtr) # Error, AnnotatedPtr[Button] cannot be modified
|
|
||||||
# to point to a ComboBox
|
|
||||||
|
|
||||||
On the other hand, in the `RingBuffer` example above, the designated generic
|
|
||||||
param is used to instantiate the non-pointer `seq` type, which means that
|
|
||||||
the resulting generic type will have covariance that mimics an array or
|
|
||||||
sequence (i.e. it will be covariant only when instantiated with `ptr` and
|
|
||||||
`ref` types):
|
|
||||||
|
|
||||||
.. code-block:: nim
|
|
||||||
|
|
||||||
type
|
|
||||||
Base = object of RootObj
|
|
||||||
Derived = object of Base
|
|
||||||
|
|
||||||
proc consumeBaseValues(b: RingBuffer[Base]) = ...
|
|
||||||
|
|
||||||
var derivedValues: RingBuffer[Derived]
|
|
||||||
|
|
||||||
consumeBaseValues(derivedValues) # Error, Base and Derived values may differ
|
|
||||||
# in size
|
|
||||||
|
|
||||||
proc consumeBasePointers(b: RingBuffer[ptr Base]) = ...
|
|
||||||
|
|
||||||
var derivedPointers: RingBuffer[ptr Derived]
|
|
||||||
|
|
||||||
consumeBaseValues(derivedPointers) # This is legal
|
|
||||||
|
|
||||||
Please note that Nim will treat the user-defined pointer-like types as
|
|
||||||
proper alternatives to the built-in pointer types. That is, types such
|
|
||||||
as `seq[AnnotatedPtr[T]]` or `RingBuffer[AnnotatedPtr[T]]` will also be
|
|
||||||
considered covariant and you can create new pointer-like types by instantiating
|
|
||||||
other user-defined pointer-like types.
|
|
||||||
|
|
||||||
The contravariant parameters introduced with the `in` modifier are currently
|
|
||||||
useful only when interfacing with imported types having such semantics.
|
|
||||||
|
|
||||||
|
|
||||||
Automatic dereferencing
|
Automatic dereferencing
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
|
@ -209,6 +112,7 @@ Automatic dereferencing is performed for the first argument of a routine call.
|
||||||
This feature has to be enabled via `{.experimental: "implicitDeref".}`:
|
This feature has to be enabled via `{.experimental: "implicitDeref".}`:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
{.experimental: "implicitDeref".}
|
{.experimental: "implicitDeref".}
|
||||||
|
|
||||||
proc depth(x: NodeObj): int = ...
|
proc depth(x: NodeObj): int = ...
|
||||||
|
|
@ -219,6 +123,7 @@ This feature has to be enabled via `{.experimental: "implicitDeref".}`:
|
||||||
echo n.depth
|
echo n.depth
|
||||||
# no need to write n[].depth either
|
# no need to write n[].depth either
|
||||||
|
|
||||||
|
|
||||||
Code reordering
|
Code reordering
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
|
@ -281,6 +186,7 @@ statement) can have their entire initialization statement reordered. Be wary of
|
||||||
what code is executed at the top level:
|
what code is executed at the top level:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
{.experimental: "codeReordering".}
|
{.experimental: "codeReordering".}
|
||||||
|
|
||||||
proc a() =
|
proc a() =
|
||||||
|
|
@ -300,6 +206,7 @@ what code is executed at the top level:
|
||||||
code is the same as it would be with code reordering disabled.
|
code is the same as it would be with code reordering disabled.
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
{.experimental: "codeReordering".}
|
{.experimental: "codeReordering".}
|
||||||
|
|
||||||
proc x() =
|
proc x() =
|
||||||
|
|
@ -313,6 +220,7 @@ It is important to note that reordering *only* works for symbols at top level
|
||||||
scope. Therefore, the following will *fail to compile:*
|
scope. Therefore, the following will *fail to compile:*
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
{.experimental: "codeReordering".}
|
{.experimental: "codeReordering".}
|
||||||
|
|
||||||
proc a() =
|
proc a() =
|
||||||
|
|
@ -331,6 +239,7 @@ has different names. This does not need an `experimental` switch, but is an
|
||||||
unstable feature.
|
unstable feature.
|
||||||
|
|
||||||
.. code-block:: Nim
|
.. code-block:: Nim
|
||||||
|
|
||||||
proc foo(x: int) =
|
proc foo(x: int) =
|
||||||
echo "Using x: ", x
|
echo "Using x: ", x
|
||||||
proc foo(y: int) =
|
proc foo(y: int) =
|
||||||
|
|
@ -349,6 +258,7 @@ As a special more convenient notation, proc expressions involved in procedure
|
||||||
calls can use the `do` keyword:
|
calls can use the `do` keyword:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
sort(cities) do (x,y: string) -> int:
|
sort(cities) do (x,y: string) -> int:
|
||||||
cmp(x.len, y.len)
|
cmp(x.len, y.len)
|
||||||
|
|
||||||
|
|
@ -371,6 +281,7 @@ parentheses is just a block of code. The `do` notation can be used to
|
||||||
pass multiple blocks to a macro:
|
pass multiple blocks to a macro:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
macro performWithUndo(task, undo: untyped) = ...
|
macro performWithUndo(task, undo: untyped) = ...
|
||||||
|
|
||||||
performWithUndo do:
|
performWithUndo do:
|
||||||
|
|
@ -380,7 +291,6 @@ pass multiple blocks to a macro:
|
||||||
# code to undo it
|
# code to undo it
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Special Operators
|
Special Operators
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
|
@ -404,6 +314,7 @@ the expression, where the unknown field or proc name is passed to
|
||||||
an `untyped` parameter:
|
an `untyped` parameter:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
a.b # becomes `.`(a, b)
|
a.b # becomes `.`(a, b)
|
||||||
a.b(c, d) # becomes `.`(a, b, c, d)
|
a.b(c, d) # becomes `.`(a, b, c, d)
|
||||||
|
|
||||||
|
|
@ -411,6 +322,7 @@ The matched dot operators can be symbols of any callable kind (procs,
|
||||||
templates and macros), depending on the desired effect:
|
templates and macros), depending on the desired effect:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template `.`(js: PJsonNode, field: untyped): JSON = js[astToStr(field)]
|
template `.`(js: PJsonNode, field: untyped): JSON = js[astToStr(field)]
|
||||||
|
|
||||||
var js = parseJson("{ x: 1, y: 2}")
|
var js = parseJson("{ x: 1, y: 2}")
|
||||||
|
|
@ -435,6 +347,7 @@ operator `.=`
|
||||||
This operator will be matched against assignments to missing fields.
|
This operator will be matched against assignments to missing fields.
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
a.b = c # becomes `.=`(a, b, c)
|
a.b = c # becomes `.=`(a, b, c)
|
||||||
|
|
||||||
Call operator
|
Call operator
|
||||||
|
|
@ -445,6 +358,7 @@ for existing routines. The experimental `callOperator` switch must be enabled
|
||||||
to use this operator.
|
to use this operator.
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
{.experimental: "callOperator".}
|
{.experimental: "callOperator".}
|
||||||
|
|
||||||
template `()`(a: int, b: float): untyped = $(a, b)
|
template `()`(a: int, b: float): untyped = $(a, b)
|
||||||
|
|
@ -480,6 +394,7 @@ All types for which `nil` is a valid value can be annotated with the
|
||||||
`not nil` annotation to exclude `nil` as a valid value:
|
`not nil` annotation to exclude `nil` as a valid value:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
{.experimental: "notnil".}
|
{.experimental: "notnil".}
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
@ -502,6 +417,7 @@ here.
|
||||||
|
|
||||||
.. include:: manual_experimental_strictnotnil.rst
|
.. include:: manual_experimental_strictnotnil.rst
|
||||||
|
|
||||||
|
|
||||||
Concepts
|
Concepts
|
||||||
========
|
========
|
||||||
|
|
||||||
|
|
@ -511,6 +427,7 @@ arbitrary set of requirements that the matched type must satisfy.
|
||||||
Concepts are written in the following form:
|
Concepts are written in the following form:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type
|
type
|
||||||
Comparable = concept x, y
|
Comparable = concept x, y
|
||||||
(x < y) is bool
|
(x < y) is bool
|
||||||
|
|
@ -536,6 +453,7 @@ instance. You can also apply the `type` modifier to create a named instance of
|
||||||
the type itself:
|
the type itself:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type
|
type
|
||||||
MyConcept = concept x, var v, ref r, ptr p, static s, type T
|
MyConcept = concept x, var v, ref r, ptr p, static s, type T
|
||||||
...
|
...
|
||||||
|
|
@ -545,6 +463,7 @@ and parameters are expected. This provides a more convenient way to check for
|
||||||
the presence of callable symbols with specific signatures:
|
the presence of callable symbols with specific signatures:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type
|
type
|
||||||
OutputStream = concept var s
|
OutputStream = concept var s
|
||||||
s.write(string)
|
s.write(string)
|
||||||
|
|
@ -555,6 +474,7 @@ type, following the `concept` keyword is also considered to have the
|
||||||
explicit modifier and will be matched only as a type.
|
explicit modifier and will be matched only as a type.
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type
|
type
|
||||||
# Let's imagine a user-defined casting framework with operators
|
# Let's imagine a user-defined casting framework with operators
|
||||||
# such as `val.to(string)` and `val.to(JSonValue)`. We can test
|
# such as `val.to(string)` and `val.to(JSonValue)`. We can test
|
||||||
|
|
@ -592,6 +512,7 @@ concept and, as a result, a wrong overload is selected, you can apply the
|
||||||
`explain` pragma to either the concept body or a particular call-site.
|
`explain` pragma to either the concept body or a particular call-site.
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type
|
type
|
||||||
MyConcept {.explain.} = concept ...
|
MyConcept {.explain.} = concept ...
|
||||||
|
|
||||||
|
|
@ -607,6 +528,7 @@ Generic concepts and type binding rules
|
||||||
The concept types can be parametric just like the regular generic types:
|
The concept types can be parametric just like the regular generic types:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
### matrixalgo.nim
|
### matrixalgo.nim
|
||||||
|
|
||||||
import std/typetraits
|
import std/typetraits
|
||||||
|
|
@ -680,6 +602,7 @@ Unbound static params will be inferred from expressions involving the `==`
|
||||||
operator and also when types dependent on them are being matched:
|
operator and also when types dependent on them are being matched:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type
|
type
|
||||||
MatrixReducer[M, N: static int; T] = concept x
|
MatrixReducer[M, N: static int; T] = concept x
|
||||||
x.reduce(SquareMatrix[N, T]) is array[M, int]
|
x.reduce(SquareMatrix[N, T]) is array[M, int]
|
||||||
|
|
@ -694,6 +617,7 @@ matched without permanently inferring it. This may be useful when you need
|
||||||
to match several procs accepting the same wide class of types:
|
to match several procs accepting the same wide class of types:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type
|
type
|
||||||
Enumerable[T] = concept e
|
Enumerable[T] = concept e
|
||||||
for v in e:
|
for v in e:
|
||||||
|
|
@ -716,6 +640,7 @@ types used in multiple signatures, without actually requiring any concrete
|
||||||
types, thus allowing you to encode implementation-defined types:
|
types, thus allowing you to encode implementation-defined types:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type
|
type
|
||||||
MyConcept = concept x
|
MyConcept = concept x
|
||||||
type T1 = auto
|
type T1 = auto
|
||||||
|
|
@ -776,6 +701,7 @@ accessible through the dot operator in procs where the concept was successfully
|
||||||
matched to a concrete type:
|
matched to a concrete type:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type
|
type
|
||||||
DateTime = concept t1, t2, type T
|
DateTime = concept t1, t2, type T
|
||||||
const Min = T.MinDate
|
const Min = T.MinDate
|
||||||
|
|
@ -810,6 +736,7 @@ one. As an alternative way of defining concept refinements, you can use the
|
||||||
object inheritance syntax involving the `of` keyword:
|
object inheritance syntax involving the `of` keyword:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type
|
type
|
||||||
Graph = concept g, type G of EquallyComparable, Copyable
|
Graph = concept g, type G of EquallyComparable, Copyable
|
||||||
type
|
type
|
||||||
|
|
@ -853,6 +780,7 @@ object inheritance syntax involving the `of` keyword:
|
||||||
the concept body:
|
the concept body:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type
|
type
|
||||||
Stringable = concept x
|
Stringable = concept x
|
||||||
$x is string
|
$x is string
|
||||||
|
|
@ -904,6 +832,7 @@ object inheritance syntax involving the `of` keyword:
|
||||||
types to the corresponding VTable type.
|
types to the corresponding VTable type.
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type
|
type
|
||||||
IntEnumerable = vtref Enumerable[int]
|
IntEnumerable = vtref Enumerable[int]
|
||||||
|
|
||||||
|
|
@ -970,6 +899,7 @@ language may weaken this restriction.)
|
||||||
The signature has to be:
|
The signature has to be:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
proc `=deepCopy`(x: T): T
|
proc `=deepCopy`(x: T): T
|
||||||
|
|
||||||
This mechanism will be used by most data structures that support shared memory
|
This mechanism will be used by most data structures that support shared memory
|
||||||
|
|
@ -1037,6 +967,7 @@ phase of the compiler: This means they provide an easy way to enhance the
|
||||||
compilation pipeline with user defined optimizations:
|
compilation pipeline with user defined optimizations:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template optMul{`*`(a, 2)}(a: int): int = a+a
|
template optMul{`*`(a, 2)}(a: int): int = a+a
|
||||||
|
|
||||||
let x = 3
|
let x = 3
|
||||||
|
|
@ -1059,6 +990,7 @@ Unfortunately optimizations are hard to get right and even the tiny example
|
||||||
is **wrong**:
|
is **wrong**:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template optMul{`*`(a, 2)}(a: int): int = a+a
|
template optMul{`*`(a, 2)}(a: int): int = a+a
|
||||||
|
|
||||||
proc f(): int =
|
proc f(): int =
|
||||||
|
|
@ -1071,6 +1003,7 @@ We cannot duplicate 'a' if it denotes an expression that has a side effect!
|
||||||
Fortunately Nim supports side effect analysis:
|
Fortunately Nim supports side effect analysis:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template optMul{`*`(a, 2)}(a: int{noSideEffect}): int = a+a
|
template optMul{`*`(a, 2)}(a: int{noSideEffect}): int = a+a
|
||||||
|
|
||||||
proc f(): int =
|
proc f(): int =
|
||||||
|
|
@ -1088,11 +1021,13 @@ cannot really do that however as the following code only swaps arguments
|
||||||
blindly:
|
blindly:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template mulIsCommutative{`*`(a, b)}(a, b: int): int = b*a
|
template mulIsCommutative{`*`(a, b)}(a, b: int): int = b*a
|
||||||
|
|
||||||
What optimizers really need to do is a *canonicalization*:
|
What optimizers really need to do is a *canonicalization*:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template canonMul{`*`(a, b)}(a: int{lit}, b: int): int = b*a
|
template canonMul{`*`(a, b)}(a: int{lit}, b: int): int = b*a
|
||||||
|
|
||||||
The `int{lit}` parameter pattern matches against an expression of
|
The `int{lit}` parameter pattern matches against an expression of
|
||||||
|
|
@ -1152,6 +1087,7 @@ but also to every other bound parameter; syntactically they need to occur after
|
||||||
the ordinary AST predicates:
|
the ordinary AST predicates:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template ex{a = b + c}(a: int{noalias}, b, c: int) =
|
template ex{a = b + c}(a: int{noalias}, b, c: int) =
|
||||||
# this transformation is only valid if 'b' and 'c' do not alias 'a':
|
# this transformation is only valid if 'b' and 'c' do not alias 'a':
|
||||||
a = b
|
a = b
|
||||||
|
|
@ -1160,6 +1096,7 @@ the ordinary AST predicates:
|
||||||
Another example:
|
Another example:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
proc somefunc(s: string) = assert s == "variable"
|
proc somefunc(s: string) = assert s == "variable"
|
||||||
proc somefunc(s: string{nkStrLit}) = assert s == "literal"
|
proc somefunc(s: string{nkStrLit}) = assert s == "literal"
|
||||||
proc somefunc(s: string{nkRStrLit}) = assert s == r"raw"
|
proc somefunc(s: string{nkRStrLit}) = assert s == r"raw"
|
||||||
|
|
@ -1189,6 +1126,7 @@ The `|` operator
|
||||||
The `|` operator if used as infix operator creates an ordered choice:
|
The `|` operator if used as infix operator creates an ordered choice:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template t{0|1}(): untyped = 3
|
template t{0|1}(): untyped = 3
|
||||||
let a = 1
|
let a = 1
|
||||||
# outputs 3:
|
# outputs 3:
|
||||||
|
|
@ -1198,6 +1136,7 @@ The matching is performed after the compiler performed some optimizations like
|
||||||
constant folding, so the following does not work:
|
constant folding, so the following does not work:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template t{0|1}(): untyped = 3
|
template t{0|1}(): untyped = 3
|
||||||
# outputs 1:
|
# outputs 1:
|
||||||
echo 1
|
echo 1
|
||||||
|
|
@ -1215,6 +1154,7 @@ A pattern expression can be bound to a pattern parameter via the `expr{param}`
|
||||||
notation:
|
notation:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template t{(0|1|2){x}}(x: untyped): untyped = x+1
|
template t{(0|1|2){x}}(x: untyped): untyped = x+1
|
||||||
let a = 1
|
let a = 1
|
||||||
# outputs 2:
|
# outputs 2:
|
||||||
|
|
@ -1227,6 +1167,7 @@ The `~` operator
|
||||||
The `~` operator is the **not** operator in patterns:
|
The `~` operator is the **not** operator in patterns:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template t{x = (~x){y} and (~x){z}}(x, y, z: bool) =
|
template t{x = (~x){y} and (~x){z}}(x, y, z: bool) =
|
||||||
x = y
|
x = y
|
||||||
if x: x = z
|
if x: x = z
|
||||||
|
|
@ -1246,6 +1187,7 @@ The `*` operator can *flatten* a nested binary expression like `a & b & c`
|
||||||
to `&(a, b, c)`:
|
to `&(a, b, c)`:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
var
|
var
|
||||||
calls = 0
|
calls = 0
|
||||||
|
|
||||||
|
|
@ -1270,6 +1212,7 @@ which is flattened into a call expression; thus the invocation of `optConc`
|
||||||
produces:
|
produces:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
`&&`("my", space & "awe", "some ", "concat")
|
`&&`("my", space & "awe", "some ", "concat")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1280,6 +1223,7 @@ The `**` is much like the `*` operator, except that it gathers not only
|
||||||
all the arguments, but also the matched operators in reverse polish notation:
|
all the arguments, but also the matched operators in reverse polish notation:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
import std/macros
|
import std/macros
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
@ -1324,6 +1268,7 @@ parameter is of the type `varargs` it is treated specially and it can match
|
||||||
0 or more arguments in the AST to be matched against:
|
0 or more arguments in the AST to be matched against:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template optWrite{
|
template optWrite{
|
||||||
write(f, x)
|
write(f, x)
|
||||||
((write|writeLine){w})(f, y)
|
((write|writeLine){w})(f, y)
|
||||||
|
|
@ -1339,6 +1284,7 @@ The following example shows how some simple partial evaluation can be
|
||||||
implemented with term rewriting:
|
implemented with term rewriting:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
proc p(x, y: int; cond: bool): int =
|
proc p(x, y: int; cond: bool): int =
|
||||||
result = if cond: x + y else: x - y
|
result = if cond: x + y else: x - y
|
||||||
|
|
||||||
|
|
@ -1352,6 +1298,7 @@ Example: Hoisting
|
||||||
The following example shows how some form of hoisting can be implemented:
|
The following example shows how some form of hoisting can be implemented:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
import std/pegs
|
import std/pegs
|
||||||
|
|
||||||
template optPeg{peg(pattern)}(pattern: string{lit}): Peg =
|
template optPeg{peg(pattern)}(pattern: string{lit}): Peg =
|
||||||
|
|
@ -1375,6 +1322,7 @@ Parameter constraints can also be used for ordinary routine parameters; these
|
||||||
constraints affect ordinary overloading resolution then:
|
constraints affect ordinary overloading resolution then:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
proc optLit(a: string{lit|`const`}) =
|
proc optLit(a: string{lit|`const`}) =
|
||||||
echo "string literal"
|
echo "string literal"
|
||||||
proc optLit(a: string) =
|
proc optLit(a: string) =
|
||||||
|
|
@ -1426,6 +1374,7 @@ Spawn statement
|
||||||
`spawn`:idx: can be used to pass a task to the thread pool:
|
`spawn`:idx: can be used to pass a task to the thread pool:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
import std/threadpool
|
import std/threadpool
|
||||||
|
|
||||||
proc processLine(line: string) =
|
proc processLine(line: string) =
|
||||||
|
|
@ -1458,6 +1407,7 @@ with the `^` operator is **blocking**. However, one can use `blockUntilAny` to
|
||||||
wait on multiple flow variables at the same time:
|
wait on multiple flow variables at the same time:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
import std/threadpool, ...
|
import std/threadpool, ...
|
||||||
|
|
||||||
# wait until 2 out of 3 servers received the update:
|
# wait until 2 out of 3 servers received the update:
|
||||||
|
|
@ -1552,6 +1502,7 @@ Protecting global variables
|
||||||
Object fields and global variables can be annotated via a `guard` pragma:
|
Object fields and global variables can be annotated via a `guard` pragma:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
var glock: TLock
|
var glock: TLock
|
||||||
var gdata {.guard: glock.}: int
|
var gdata {.guard: glock.}: int
|
||||||
|
|
||||||
|
|
@ -1559,6 +1510,7 @@ The compiler then ensures that every access of `gdata` is within a `locks`
|
||||||
section:
|
section:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
proc invalid =
|
proc invalid =
|
||||||
# invalid: unguarded access:
|
# invalid: unguarded access:
|
||||||
echo gdata
|
echo gdata
|
||||||
|
|
@ -1577,6 +1529,7 @@ semantics and should not be used directly! It should only be used in templates
|
||||||
that also implement some form of locking at runtime:
|
that also implement some form of locking at runtime:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template lock(a: TLock; body: untyped) =
|
template lock(a: TLock; body: untyped) =
|
||||||
pthread_mutex_lock(a)
|
pthread_mutex_lock(a)
|
||||||
{.locks: [a].}:
|
{.locks: [a].}:
|
||||||
|
|
@ -1590,6 +1543,7 @@ The guard does not need to be of any particular type. It is flexible enough to
|
||||||
model low level lockfree mechanisms:
|
model low level lockfree mechanisms:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
var dummyLock {.compileTime.}: int
|
var dummyLock {.compileTime.}: int
|
||||||
var atomicCounter {.guard: dummyLock.}: int
|
var atomicCounter {.guard: dummyLock.}: int
|
||||||
|
|
||||||
|
|
@ -1617,6 +1571,7 @@ Since objects can reside on the heap or on the stack this greatly enhances the
|
||||||
expressivity of the language:
|
expressivity of the language:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type
|
type
|
||||||
ProtectedCounter = object
|
ProtectedCounter = object
|
||||||
v {.guard: L.}: int
|
v {.guard: L.}: int
|
||||||
|
|
@ -1631,6 +1586,7 @@ The access to field `x.v` is allowed since its guard `x.L` is active.
|
||||||
After template expansion, this amounts to:
|
After template expansion, this amounts to:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
proc incCounters(counters: var openArray[ProtectedCounter]) =
|
proc incCounters(counters: var openArray[ProtectedCounter]) =
|
||||||
for i in 0..counters.high:
|
for i in 0..counters.high:
|
||||||
pthread_mutex_lock(counters[i].L)
|
pthread_mutex_lock(counters[i].L)
|
||||||
|
|
@ -1651,6 +1607,7 @@ Two paths are considered equivalent if they are syntactically the same.
|
||||||
This means the following compiles (for now) even though it really should not:
|
This means the following compiles (for now) even though it really should not:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
{.locks: [a[i].L].}:
|
{.locks: [a[i].L].}:
|
||||||
inc i
|
inc i
|
||||||
access a[i].v
|
access a[i].v
|
||||||
|
|
@ -1671,6 +1628,7 @@ of the same level can only be acquired *at the same time* within a
|
||||||
single `locks` section:
|
single `locks` section:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
var a, b: TLock[2]
|
var a, b: TLock[2]
|
||||||
var x: TLock[1]
|
var x: TLock[1]
|
||||||
# invalid locking order: TLock[1] cannot be acquired before TLock[2]:
|
# invalid locking order: TLock[1] cannot be acquired before TLock[2]:
|
||||||
|
|
@ -1697,6 +1655,7 @@ the runtime check is required to ensure a global ordering for two locks `a`
|
||||||
and `b` of the same lock level:
|
and `b` of the same lock level:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template multilock(a, b: ptr TLock; body: untyped) =
|
template multilock(a, b: ptr TLock; body: untyped) =
|
||||||
if cast[ByteAddress](a) < cast[ByteAddress](b):
|
if cast[ByteAddress](a) < cast[ByteAddress](b):
|
||||||
pthread_mutex_lock(a)
|
pthread_mutex_lock(a)
|
||||||
|
|
@ -1717,6 +1676,7 @@ level. This then means that the routine may acquire locks of up to this level.
|
||||||
This is essential so that procs can be called within a `locks` section:
|
This is essential so that procs can be called within a `locks` section:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
proc p() {.locks: 3.} = discard
|
proc p() {.locks: 3.} = discard
|
||||||
|
|
||||||
var a: TLock[4]
|
var a: TLock[4]
|
||||||
|
|
@ -1739,6 +1699,7 @@ cannot be inferred statically, leading to compiler warnings. By using
|
||||||
having unknown lock level as well:
|
having unknown lock level as well:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
type SomeBase* = ref object of RootObj
|
type SomeBase* = ref object of RootObj
|
||||||
type SomeDerived* = ref object of SomeBase
|
type SomeDerived* = ref object of SomeBase
|
||||||
memberProc*: proc ()
|
memberProc*: proc ()
|
||||||
|
|
@ -1761,6 +1722,7 @@ e.g. when rewriting term to same term plus extra content.
|
||||||
e.g. with given example `echo("ab")` will be rewritten just once:
|
e.g. with given example `echo("ab")` will be rewritten just once:
|
||||||
|
|
||||||
.. code-block:: nim
|
.. code-block:: nim
|
||||||
|
|
||||||
template pwnEcho{echo(x)}(x: untyped) =
|
template pwnEcho{echo(x)}(x: untyped) =
|
||||||
{.noRewrite.}: echo("pwned!")
|
{.noRewrite.}: echo("pwned!")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue