Numerics
Int
The Int type offers arbitrary-size integer numbers. They can get as big as your computer memory allows, although some implementations choose to throw a numeric overflow error when asked to produce integers of truly staggering size:
say 10**600**600
# OUTPUT: Ā«Numeric overflowā¤Ā»
Unlike some languages, division performed using / operator when both operands are of Int type, would produce a fractional number, without any rounding performed.
say 4/5; # OUTPUT: Ā«0.8ā¤Ā»
The type produced by this division is either a Rat or a Num type. The Rat is produced if, after reduction, the fraction's denominator is smaller than 64 bits, otherwise a Num type is produced.
The div and narrow routines can be helpful if you wish to end up with an Int result, whenever possible. The div operator performs integer division, discarding the remainder, while narrow fits the number into the narrowest type it'll fit:
say 5 div 2; # OUTPUT: Ā«2ā¤Ā»
# Result `2` is narrow enough to be an Int:
say (4/2).narrow; # OUTPUT: Ā«2ā¤Ā»
say (4/2).narrow.^name; # OUTPUT: Ā«Intā¤Ā»
# But 2.5 has fractional part, so it ends up being a Rat type:
say (5/2).narrow.^name; # OUTPUT: Ā«Ratā¤Ā»
say (5/2).narrow; # OUTPUT: Ā«2.5ā¤Ā»
# Denominator is too big for a Rat, so a Num is produced:
say 1 / 10ā¹ā¹; # OUTPUT: Ā«1e-99ā¤Ā»
Raku has a FatRat type that offers arbitrary precision fractions. How come a limited-precision Num is produced instead of a FatRat type in the last example above? The reason is: performance. Most operations are fine with a little bit of precision lost and so do not require the use of a more expensive FatRat type. You'll need to instantiate one yourself if you wish to have the extra precision.
Num
The Num type offers double-precision floating-point decimal numbers, sometimes called "doubles" in other languages.
A Num literal is written with the exponent separated using the letter e
. Keep
in mind that the letter e
is required even if the exponent is zero, as
otherwise you'll get a Rat rational literal instead:
say 42e0.^name; # OUTPUT: Ā«Numā¤Ā»
say 42.0.^name; # OUTPUT: Ā«Ratā¤Ā»
Case-sensitive words Inf and NaN represent the special values infinity and
not-a-number respectively. The U+221E INFINITY (ā
) character can be used
instead of Inf:
Raku follows the IEEE 754-2008 Standard for Floating-Point Arithmetic as much as possible, with more conformance planned to be implemented in later language versions. The language guarantees the closest representable number is chosen for any given Num literal and does offer support for negative zero and denormals (also known as "subnormals").
Keep in mind that output routines like say or put do not try very hard to distinguish between how Numeric types are output and may choose to display a Num as an Int or a Rat number. For a more definitive string to output, use the raku method:
say 1e0; # OUTPUT: Ā«1ā¤Ā»
say .5e0; # OUTPUT: Ā«0.5ā¤Ā»
say 1e0.raku; # OUTPUT: Ā«1e0ā¤Ā»
say .5e0.raku; # OUTPUT: Ā«0.5e0ā¤Ā»
Complex
The Complex type numerics of the complex plane. The Complex objects consist of two Num objects representing the real and imaginary portions of the complex number.
To create a Complex, you can use the postfix i operator
on any other non-complex number, optionally setting the real part with
addition. To use the i
operator on NaN
or Inf
literals, separate it
from them with a backslash.
say 42i; # OUTPUT: Ā«0+42iā¤Ā»
say 73+42i; # OUTPUT: Ā«73+42iā¤Ā»
say 73+Inf\i; # OUTPUT: Ā«73+Inf\iā¤Ā»
Keep in mind the above syntax is just an addition expression and precedence rules apply. It also cannot be used in places that forbid expressions, such as literals in routine parameters.
# Precedence of `*` is higher than that of `+`
say 2 * 73+10i; # OUTPUT: Ā«146+10iā¤Ā»
To avoid these issues, you can choose to use the Complex literal syntax instead, which involves surrounding the real and imaginary parts with angle brackets, without any spaces:
say 2 * <73+10i>; # OUTPUT: Ā«146+20iā¤Ā»
multi how-is-it (<2+4i>) { say "that's my favorite number!" }
multi how-is-it (|) { say "meh" }
how-is-it 2+4i; # OUTPUT: Ā«that's my favorite number!ā¤Ā»
how-is-it 3+2i; # OUTPUT: Ā«mehā¤Ā»
Rational
The types that do the Rational role offer high-precision and arbitrary-precision decimal numbers. Since the higher the precision the larger the performance penalty, the Rational types come in two flavors: Rat and FatRat. The Rat is the most often-used variant that degrades into a Num in most cases, when it can no longer hold all of the requested precision. The FatRat is the arbitrary-precision variant that keeps growing to provide all of the requested precision.
Rat
The most common of Rational types. It supports rationals with denominators as large as 64 bits (after reduction of the fraction to the lowest denominator). Rat objects with larger denominators can be created directly, however, when Rats with such denominators are the result of mathematical operations, they degrade to a Num object.
The Rat literals use syntax similar to Num literals in many other languages, using the dot to indicate the number is a decimal:
say .1 + .2 == .3; # OUTPUT: Ā«Trueā¤Ā»
If you try to execute a statement similar to the above in many common
languages, you'll get False
as the answer, due to imprecision of
floating point math. To get the same result in Raku, you'd have to use
Num literals instead:
say .1e0 + .2e0 == .3e0; # OUTPUT: Ā«Falseā¤Ā»
You can also use / division operator with Int or Rat objects to produce a Rat:
say 3/4; # OUTPUT: Ā«0.75ā¤Ā»
say 3/4.2; # OUTPUT: Ā«0.714286ā¤Ā»
say 1.1/4.2; # OUTPUT: Ā«0.261905ā¤Ā»
Keep in mind the above syntax is just a division expression and precedence rules apply. It also cannot be used in places that forbid expressions, such as literals in routine parameters.
# Precedence of power operators is higher than division
say 3/2Ā²; # OUTPUT: Ā«0.75ā¤Ā»
To avoid these issues, you can choose to use the Rational literal syntax instead, which involves surrounding the numerator and denominator with angle brackets, without any spaces:
say <3/2>Ā²; # OUTPUT: Ā«2.25ā¤Ā»
multi how-is-it (<3/2>) { say "that's my favorite number!" }
multi how-is-it (|) { say "meh" }
how-is-it 3/2; # OUTPUT: Ā«that's my favorite number!ā¤Ā»
how-is-it 1/3; # OUTPUT: Ā«mehā¤Ā»
Lastly, any Unicode character with property No
that represents a fractional
number can be used as a Rat literal:
say Ā½ + ā
+ ā
+ ā
; # OUTPUT: Ā«1.625ā¤Ā»
Degradation to Num
If a mathematical operation that produces a Rat answer would produce a Rat with denominator larger than 64 bits, that operation would instead return a Num object. When constructing a Rat (i.e. when it is not a result of some mathematical expression), however, a larger denominator can be used:
my $a = 1 / (2ā¶ā“ - 1);
say $a; # OUTPUT: Ā«0.000000000000000000054ā¤Ā»
say $a.^name; # OUTPUT: Ā«Ratā¤Ā»
say $a.nude; # OUTPUT: Ā«(1 18446744073709551615)ā¤Ā»
my $b = 1 / 2ā¶ā“;
say $b; # OUTPUT: Ā«5.421010862427522e-20ā¤Ā»
say $b.^name; # OUTPUT: Ā«Numā¤Ā»
my $c = Rat.new(1, 2ā¶ā“);
say $c; # OUTPUT: Ā«0.000000000000000000054ā¤Ā»
say $c.^name; # OUTPUT: Ā«Ratā¤Ā»
say $c.nude; # OUTPUT: Ā«(1 18446744073709551616)ā¤Ā»
say $c.Num; # OUTPUT: Ā«5.421010862427522e-20ā¤Ā»
FatRat
The last Rational typeāFatRatākeeps all of the precision you ask of it, storing the numerator and denominator as two Int objects. A FatRat is more infectious than a Rat, so many math operations with a FatRat will produce another FatRat, preserving all of the available precision. Where a Rat degrades to a Num, math with a FatRat keeps chugging along:
say ((42 + Rat.new(1,2))/999999999999999999).^name; # OUTPUT: Ā«Ratā¤Ā»
say ((42 + Rat.new(1,2))/9999999999999999999).^name; # OUTPUT: Ā«Numā¤Ā»
say ((42 + FatRat.new(1,2))/999999999999999999).^name; # OUTPUT: Ā«FatRatā¤Ā»
say ((42 + FatRat.new(1,2))/99999999999999999999999).^name; # OUTPUT: Ā«FatRatā¤Ā»
There's no special operator or syntax available for construction of FatRat
objects. Simply use the FatRat.new
method,
giving numerator as first positional argument and denominator as the second.
If your program requires a significant amount of FatRat creation, you could create your own custom operator:
sub infix:<š¼> { FatRat.new: $^a, $^b }
say (1š¼3).raku; # OUTPUT: Ā«FatRat.new(1, 3)ā¤Ā»
Printing rationals
Keep in mind that output routines like say or put do not try very hard to distinguish between how Numeric types are output and may choose to display a Num as an Int or a Rat number. For a more definitive string to output, use the raku method:
say 1.0; # OUTPUT: Ā«1ā¤Ā»
say ā
; # OUTPUT: Ā«0.333333ā¤Ā»
say 1.0.raku; # OUTPUT: Ā«1.0ā¤Ā»
say ā
.raku; # OUTPUT: Ā«<1/3>ā¤Ā»
For even more information, you may choose to see the Rational object in the nude, displaying its numerator and denominator:
say ā
; # OUTPUT: Ā«0.333333ā¤Ā»
say 4/2; # OUTPUT: Ā«2ā¤Ā»
say ā
.raku; # OUTPUT: Ā«<1/3>ā¤Ā»
say <4/2>.nude; # OUTPUT: Ā«(2 1)ā¤Ā»
Division by zero
In many languages division by zero is an immediate exception. In Raku, what happens depends on what you're dividing and how you use the result.
Raku follows IEEE 754-2008 Standard for Floating-Point Arithmetic, but for historical reasons
6.c and 6.d language versions do not comply fully. Num division by zero
produces a Failure, while Complex division by zero produces NaN
components, regardless of what the numerator is.
As of 6.e language, both Num and Complex division by zero will produce an
-Inf, +Inf
, or NaN depending on whether the numerator was
negative, positive, or zero, respectively (for Complex the real and imaginary
components are Num and are considered separately).
Division of Int numerics produces a Rat object (or a Num, if after reduction the denominator is larger than 64-bits, which isn't the case when you're dividing by zero). This means such division never produces an Exception or a Failure. The result is a Zero-Denominator Rational, which can be explosive.
Zero-denominator rationals
A Zero-Denominator Rational is a numeric that does role Rational, which
among core numerics would be Rat and FatRat objects, which
has denominator of zero. The numerator of such Rationals is normalized
to -1
, 0
, or 1
depending on whether the original numerator
is negative, zero or positive, respectively.
Operations that can be performed without requiring actual division to occur are non-explosive. For example, you can separately examine numerator and denominator in the nude or perform mathematical operations without any exceptions or failures popping up.
Converting zero-denominator rationals to Num follows the
IEEE conventions, and the result is an
-Inf
, Inf
, or NaN
, depending on whether the numerator is negative,
positive, or zero, respectively. The same is true going the other way:
converting Ā±Inf
/NaN
to one of the Rational types will produce a
zero-denominator rational with an appropriate numerator:
say <1/0>.Num; # OUTPUT: Ā«Infā¤Ā»
say <-1/0>.Num; # OUTPUT: Ā«-Infā¤Ā»
say <0/0>.Num; # OUTPUT: Ā«NaNā¤Ā»
say Inf.Rat.nude; # OUTPUT: Ā«(1 0)ā¤Ā»
All other operations that require non-IEEE division of the numerator and denominator to occur will result in X::Numeric::DivideByZero exception to be thrown. The most common of such operations would likely be trying to print or stringify a zero-denominator rational:
say 0/0;
# OUTPUT:
# Attempt to divide by zero using div
# in block <unit> at -e line 1
Allomorphs
Allomorphs are subclasses of two types that can behave as either of them. For example, the allomorph IntStr is the subclass of Int and Str types and will be accepted by any type constraint that requires an Int or Str object.
Allomorphs can be created using angle brackets|/language/quoting#Word_quoting:_<_>, either used
standalone or as part of a hash key lookup; directly
using method .new
and are also provided by some constructs such as
parameters of sub MAIN.
say <42>.^name; # OUTPUT: Ā«IntStrā¤Ā»
say <42e0>.^name; # OUTPUT: Ā«NumStrā¤Ā»
say < 42+42i>.^name; # OUTPUT: Ā«ComplexStrā¤Ā»
say < 1/2>.^name; # OUTPUT: Ā«RatStrā¤Ā»
say <0.5>.^name; # OUTPUT: Ā«RatStrā¤Ā»
@*ARGS = "42";
sub MAIN($x) { say $x.^name } # OUTPUT: Ā«IntStrā¤Ā»
say IntStr.new(42, "42").^name; # OUTPUT: Ā«IntStrā¤Ā»
A couple of constructs above have a space after the opening angle bracket. That
space isn't accidental. Numerics that are often written using an operator, such
as 1/2
(Rat, division operator) and 1+2i
(Complex, addition) can be
written as a literal that doesn't involve the use of an operator: angle brackets
without any spaces between the angle brackets and the characters inside. By adding
spaces within the angle brackets, we tell the compiler that not only we want a Rat
or Complex literal, but we also want it to be an allomorph: the RatStr or
ComplexStr, in this case.
If the numeric literal doesn't use any operators, then writing it inside the angle brackets, even without including any spaces within, would produce the allomorph. (Logic: if you didn't want the allomorph, you wouldn't use the angle brackets. The same isn't true for operator-using numbers as some constructs, such as signature literals, do not let you use operators, so you can't just omit angle brackets for such numeric literals).
Available allomorphs
The core language offers the following allomorphs:
Type | Allomorph of | Example |
---|---|---|
IntStr | Int and Str | <42> |
NumStr | Num and Str | <42e0> |
ComplexStr | Complex and Str | < 1+2i> |
RatStr | Rat and Str | <1.5> |
Note: there is no FatRatStr
type.
Coercion of allomorphs
Keep in mind that allomorphs are simply subclasses of the types they
represent. Just as a variable or parameter type-constrained to Foo
can accept any subclass of Foo
, so will a variable or parameter
type-constrained to Int will accept an
IntStr allomorph:
sub foo(Int $x) { say $x.^name }
foo <42>; # OUTPUT: Ā«IntStrā¤Ā»
my Num $y = <42e0>;
say $y.^name; # OUTPUT: Ā«NumStrā¤Ā»
This also applies to parameter coercers:
sub foo(Int(Cool) $x) { say $x.^name }
foo <42>; # OUTPUT: Ā«IntStrā¤Ā»
The given allomorph is already an object of type Int, so it does not get converted to a "plain" Int in this case.
The power of allomorphs would be severely diminished if there were no way to "collapse" them to one of their components. Thus, if you explicitly call a method with the name of the type to coerce to, you'll get just that component. The same applies to any proxy methods, such as calling method .Numeric instead of .Int or using the prefix:<~> operator instead of .Str method call.
my $al := IntStr.new: 42, "forty two";
say $al.Str; # OUTPUT: Ā«forty twoā¤Ā»
say +$al; # OUTPUT: Ā«42ā¤Ā»
say <1/99999999999999999999>.Rat.^name; # OUTPUT: Ā«Ratā¤Ā»
say <1/99999999999999999999>.FatRat.^name; # OUTPUT: Ā«FatRatā¤Ā»
A handy way to coerce a whole list of allomorphs is by applying the hyper operator to the appropriate prefix:
say map *.^name, <42 50e0 100>; # OUTPUT: Ā«(IntStr NumStr IntStr)ā¤Ā»
say map *.^name, +Ā«<42 50e0 100>; # OUTPUT: Ā«(Int Num Int)ā¤Ā»
say map *.^name, ~Ā«<42 50e0 100>; # OUTPUT: Ā«(Str Str Str)ā¤Ā»
Object identity
The above discussion on coercing allomorphs becomes more important when we consider object
identity. Some constructs utilize it to ascertain whether two objects are "the same". And while
to humans an allomorphic 42
and regular 42
might appear "the same", to those constructs,
they're entirely different objects:
# "42" shows up twice in the result: 42 and <42> are different objects:
say unique 1, 1, 1, 42, <42>; # OUTPUT: Ā«(1 42 42)ā¤Ā»
# Use a different operator to `unique` with:
say unique :with(&[==]), 1, 1, 1, 42, <42>; # OUTPUT: Ā«(1 42)ā¤Ā»
# Or coerce the input instead (faster than using a different `unique` operator):
say unique :as(*.Int), 1, 1, 1, 42, <42>; # OUTPUT: Ā«(1 42)ā¤Ā»
say unique +Ā«(1, 1, 1, 42, <42>); # OUTPUT: Ā«(1 42)ā¤Ā»
# Parameterized Hash with `Any` keys does not stringify them; our key is of type `Int`:
my %h{Any} = 42 => "foo";
# But we use the allomorphic key of type `IntStr`, which is not in the Hash:
say %h<42>:exists; # OUTPUT: Ā«Falseā¤Ā»
# Must use curly braces to avoid the allomorph:
say %h{42}:exists; # OUTPUT: Ā«Trueā¤Ā»
# We are using a set operator to look up an `Int` object in a list of `IntStr` objects:
say 42 ā <42 100 200>; # OUTPUT: Ā«Falseā¤Ā»
# Convert it to an allomorph:
say <42> ā <42 100 200>; # OUTPUT: Ā«Trueā¤Ā»
# Or convert the items in the list to plain `Int` objects:
say 42 ā +Ā«<42 100 200>; # OUTPUT: Ā«Trueā¤Ā»
Be mindful of these object identity differences and coerce your allomorphs as needed.
Transforming non-Allomorphs
When we create variables explicitly as Str or Numeric types, they are not Allomorphs:
my $a = "010"; say $a.^name; # OUTPUT: Ā«Strā¤Ā»
my $b = 42; say $b.^name; # OUTPUT: Ā«Intā¤Ā»
We can then explicitly transform them to their Allomorph counterparts:
$a .= Numeric; # OUTPUT: Ā«10ā¤Ā»
say $a.^name; # OUTPUT: Ā«Intā¤Ā»
$b .= Str; ; # OUTPUT: Ā«42ā¤Ā»
say $b.^name; # OUTPUT: Ā«Strā¤Ā»
Or we can coerce them into their other forms naturally:
my $a = "010"; say $a.^name; # OUTPUT: Ā«Strā¤Ā»
my $b = $a + 1; # OUTPUT: Ā«11ā¤Ā»
say $b.^name; # OUTPUT: Ā«Intā¤Ā»
Native numerics
As the name suggests, native numerics offer access to native numericsāi.e. those offered directly by your hardware. This in turn offers two features: overflow/underflow and better performance.
NOTE: at the time of this writing (2018.05), certain implementations (such as Rakudo) offer
somewhat spotty details on native types, such as whether int64
is available and is of 64-bit
size on 32-bit machines, and how to detect when your program is running on such hardware.
Available native numerics
Native type | Base numeric | Size |
---|---|---|
int | integer | 64-bits |
int8 | integer | 8-bits |
int16 | integer | 16-bits |
int32 | integer | 32-bits |
int64 | integer | 64-bits |
uint | unsigned integer | 64-bits |
uint8 | unsigned integer | 8-bits |
uint16 | unsigned integer | 16-bits |
uint32 | unsigned integer | 32-bits |
uint64 | unsigned integer | 64-bits |
num | floating point | 64-bits |
num32 | floating point | 32-bits |
num64 | floating point | 64-bits |
atomicint | integer | sized to offer CPU-provided atomic operations. (typically 64 bits on 64-bit platforms and 32 bits on 32-bit ones) |
Creating native numerics
To create a natively-typed variable or parameter, simply use the name of one of the available numerics as the type constraint:
my int32 $x = 42;
sub foo(num $y) {}
class { has int8 $.z }
At times, you may wish to coerce some value to a native type without creating any usable variables.
There are no .int
or similar coercion methods (method calls are latebound, so they're not
well-suited for this purpose). Instead, simply use an anonymous variable:
some-native-taking-sub( (my int $ = $y), (my int32 $ = $z) )
Overflow/Underflow
Trying to assign a value that does not fit into a particular native type, produces an exception. This includes attempting to give too large an argument to a native parameter:
my int $x = 2Ā¹ā°ā°;
# OUTPUT:
# Cannot unbox 101 bit wide bigint into native integer
# in block <unit> at -e line 1
sub f(int $x) { $x }; say f 2ā¶ā“
# OUTPUT:
# Cannot unbox 65 bit wide bigint into native integer
# in sub f at -e line 1
# in block <unit> at -e line 1
However, modifying an already-existing value in such a way that it becomes too big/small, produces overflow/underflow behavior:
my int $x = 2ā¶Ā³-1;
say $x; # OUTPUT: Ā«9223372036854775807ā¤Ā»
say ++$x; # OUTPUT: Ā«-9223372036854775808ā¤Ā»
my uint8 $x;
say $x; # OUTPUT: Ā«0ā¤Ā»
say $x -= 100; # OUTPUT: Ā«156ā¤Ā»
Creating objects that utilize native types does not involve direct assignment by the programmer; that is why these constructs offer overflow/underflow behavior instead of throwing exceptions.
say Buf.new(1000, 2000, 3000).List; # OUTPUT: Ā«(232 208 184)ā¤Ā»
say my uint8 @a = 1000, 2000, 3000; # OUTPUT: Ā«232 208 184ā¤Ā»
Auto-boxing
While they can be referred to as "native types", native numerics are not actually classes that have any sort of methods available. However, you can call any of the methods available on non-native versions of these numerics. What's going on?
my int8 $x = -42;
say $x.abs; # OUTPUT: Ā«42ā¤Ā»
This behavior is known as "auto-boxing". The compiler automatically "boxes" the
native type into a full-featured higher-level type with all the methods. In
other words, the int8
above was automatically converted to an
Int and it's the Int class that then provided the
abs method that was called.
This detail is significant when you're using native types for performance gains. If the code you're using results in a lot of auto-boxing being performed you might get worse performance with native types than you would with non-natives:
my $a = -42;
my int $a-native = -42;
{ for ^1000_000 { $a.abs }; say now - ENTER now } # OUTPUT: Ā«0.38180862ā¤Ā»
{ for ^1000_000 { $a-native.abs }; say now - ENTER now } # OUTPUT: Ā«0.938720ā¤Ā»
As you can see above, the native variant is more than twice slower. The reason is the method call requires the native type to be boxed, while no such thing is needed in the non-native variant, hence the performance loss.
In this particular case, we can simply switch to a subroutine form of abs, which can work with native types without boxing them. In other cases, you may need to seek out other solutions to avoid excessive autoboxing, including switching to non-native types for a portion of the code.
my $a = -42;
my int $a-native = -42;
{ for ^1000_000 { abs $a }; say now - ENTER now } # OUTPUT: Ā«0.38229177ā¤Ā»
{ for ^1000_000 { abs $a-native }; say now - ENTER now } # OUTPUT: Ā«0.3088305ā¤Ā»
Default values
Since there are no classes behind native types, there are no type objects you'd
normally get with variables that haven't been initialized. Thus, native types
are automatically initialized to zero. In 6.c language, native floating point
types (num
, num32
, and num64
) were initialized to value NaN
; in 6.d
language the default is 0e0
.
Native dispatch
It is possible to have native candidates alongside non-native candidates to, for example, offer faster algorithms with native candidates when sizes are predictable, but to fallback to slower non-native alternatives otherwise. The following are the rules concerning multi-dispatch involving native candidates.
First, the size of the native type does not play a role in dispatch and an
int8
is considered to be the same as int16
or int
:
multi foo(int $x) { say "int" }
multi foo(int32 $x) { say "int32" }
foo my int $x = 42;
# OUTPUT:
# Ambiguous call to 'foo(Int)'; these signatures all match:
# :(int $x)
# :(int32 $x)
Second, if a routine is an only
āi.e. it is not a
multiāthat takes a non-native type but
a native one was given during the call, or vice-versa, then the argument will be
auto-boxed or auto-unboxed to make the call possible. If the given argument is
too large to fit into the native parameter, an exception will be thrown:
-> int {}( 42 ); # OK; auto-unboxing
-> int {}( 2Ā¹ā°ā° ); # Too large; exception
-> Int {}( 2Ā¹ā°ā° ); # OK; non-native parameter
-> Int {}( my int $ = 42 ); # OK; auto-boxing
When it comes to multi routines, native arguments will always be auto-boxed if no native candidates are available to take them:
multi foo (Int $x) { $x }
say foo my int $ = 42; # OUTPUT: Ā«42ā¤Ā»
The same luxury is not afforded when going the other way. If only a native candidate is available, a non-native argument will not be auto-unboxed and instead an exception indicating no candidates matched will be thrown (the reason for this asymmetry is a native type can always be boxed, but a non-native may be too large to fit into a native):
multi f(int $x) { $x }
my $x = 2;
say f $x;
# OUTPUT:
# Cannot resolve caller f(Int); none of these signatures match:
# (int $x)
# in block <unit> at -e line 1
However, this rule is waived if a call is being made where one of the arguments is a native type and another one is a numeric literal:
multi f(int, int) {}
f 42, my int $x; # Successful call
This way you do not have to constantly write, for example, $n +> 2
as $n +>
(my int $ = 2)
. The compiler knows the literal is small enough to fit to a
native type and converts it to a native.
Atomic operations
The language offers some operations that are guaranteed to be performed atomically, i.e. safe to be executed by multiple threads without the need for locking with no risk of data races.
For such operations, the atomicint native type is required. This type is similar to a plain native int, except it is sized such that CPU-provided atomic operations can be performed upon it. On a 32-bit CPU it will typically be 32 bits in size, and on an a 64-bit CPU it will typically be 64 bits in size.
# !!WRONG!! Might be non-atomic on some systems
my int $x;
await ^100 .map: { start $xā++ };
say $x; # OUTPUT: Ā«98ā¤Ā»
# RIGHT! The use of `atomicint` type guarantees operation is atomic
my atomicint $x;
await ^100 .map: { start $xā++ };
say $x; # OUTPUT: Ā«100ā¤Ā»
The similarity to int
is present in multi dispatch as well: an
atomicint, plain int
, and the sized int
variants are all considered
to be the same by the dispatcher and cannot be differentiated through
multi-dispatch.
Numeric infectiousness
Numeric "infectiousness" dictates the resultant type when two numerics of
different types are involved in some mathematical operations. A type is said to
be more infectious than the other type if the result is of that type rather than
the type of the other operand. For example, Num type is more infectious than
an Int, thus we can expect 42e0 + 42
to produce a Num as the result.
The infectiousness is as follows, with the most infectious type listed first:
Complex
Num
FatRat
Rat
Int
say (2 + 2e0).^name; # Int + Num => OUTPUT: Ā«Numā¤Ā»
say (Ā½ + Ā½).^name; # Rat + Rat => OUTPUT: Ā«Ratā¤Ā»
say (FatRat.new(1,2) + Ā½).^name; # FatRat + Rat => OUTPUT: Ā«FatRatā¤Ā»
The allomorphs have the same infectiousness as their numeric component. Native types get autoboxed and have the same infectiousness as their boxed variant.