Raku Programming/Types and Context
We've already talked about the various fundamental data types scalars, arrays and hashes. Each variable's sigil puts it into a specific context. Different types of variables act differently when they are used in different contexts. There are two basic types of context, at least two that we are going to talk about right now: Scalar context and List context. Scalars are anything with the $ sigil, while Lists are things like Arrays and Hashes.
We're going to take this time to talk about one of Raku's built-in functions:
say prints a line of text to the console while the program is running. This is part of a larger system of input and output (I/O) that we will talk about in more detail later.
say takes a string or a list of strings and prints them to the console.
say "hello world!"; say "This is ", "Raku speaking!";
A range is a list of values with some kind of numerical relationship between them. Ranges are created with the
my @digits = 0..9; # (0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
Ranges can also use variables as the delimiters:
my $max = 15; my $min = 12; my @list = $min .. $max; # (12, 13, 14, 15);
A range is a separate type of object from an array entirely, even though a range will create an array-like list of values. Ranges implement a behavior called lazy evaluation: Instead of calculating a list of all values in the range first, the range is stored compactly as a starting and ending point only. Values can be calculated from a range later when a value is actually being read from it. This means that we can easily have infinite ranges without eating up all our computer's memory:
my @range = 1..Inf; # Infinite range, finite memory use my $x = @range; # Calculated on demand
Lazy evaluation isn't just a behavior of ranges, it's actually built into Raku directly and used in many places automatically. What this means is that large arrays won't necessarily take up a lot of memory, but instead the values can be calculated only when they are needed.
We can specify the context of a data item using various casting techniques.
$( ) forces whatever is between the brackets to be treated like a scalar, even if it isn't one normally.
@( ) and
%( ) do the same for arrays and hashes too. Since
~ is always associated with strings and
+ is always associated with numbers, we can use these to cast values to strings and numbers respectively:
my Num $x = +"3"; # Becomes a number my Str $y = ~3; # The string "3" my $z = $(2..4); # Three elements, but still a single Range object my @w = @("key" => "value"); # Convert hash to array
The examples above are not the only cases where things can be cast from one type to another. Casting allows us to force variables into a specific type. In some cases, complicated variable types or classes can demonstrate very different behavior depending on how it is cast. We'll talk about these kinds of cases in later chapters.
Here is a quick list of context specifiers:
- Convert to a number
- Convert to a string
- Convert to a scalar
- Convert to an array
- Convert to a hash
We talked earlier about how Raku is a dynamic language and therefore the variables and data items in it don't have pre-defined types. We also mentioned, almost in a footnote, that Raku also had a type system that could be used optionally if you want it. Raku is a very flexible language and was designed to give the programmer a lot of latitude to program in different ways. One of those ways that Raku makes available to program in is structured, statically-typed programming.
If you specify a type for a variable, Raku will follow that and only allow data of that type to be used in that variable. This is very helpful in some cases because certain classes of operations will raise compile-time errors instead of runtime errors. Also, the compiler can be free to perform certain types of optimizations if it knows the type of a variable never changes. These behaviors are implementation-dependent, of course - and will only be helpful if you try to make use of them. The general rule is that the more information that you supply to the compiler, the more helpful analysis the compiler can make for you.
We also mentioned earlier that variables do not need to be explicitly declared before they are used. This was also a little bit of a stretch of the truth. Variables don't need to be declared beforehand, but Raku gives you the flexibility to do it if you want. To predeclare a variable, you use the
my $x = 5;
my defines a variable to be a local, lexical variable. The alternative is to define it as
our to be a global shared variable. Another name for a global variable in Raku is a "package variable", because it's available to the entire software package, or file.
our $x = 5;
Global variables are nice because you can use them all over the place without having to pass them around and keep track of them. However, unlike in kindergarten, sharing is not always the best idea in large programs.
Raku provides some built-in types that the Raku compiler knows about beforehand. You can always define your own types, but Raku makes a number of them available to you from the beginning. Here is a partial list of some of the basic types built in to Raku. This isn't a comprehensive list because some of the types that Raku has won't make any sense at this point.
- A boolean value, true or false. Booleans are an enumerated type, which we will talk about in more depth a little bit later. Bool values can be
- A basic integer value
- An array of values, indexed by an integer subscript
- A hash of values indexed by a string
- A floating-point number
- like a floating point number but also allows imaginary and complex data types too.
- We briefly mentioned pairs when talking about hashes. A pair is a combination of a data object and a string.
- A string data object
With these values, you can write statically-typed code just like you can in a normal statically-typed language:
my Int $x; $x = 5; # Good, it's an integer $x = "five"; # Bad, just the name of an integer
We can also use the type system to catch errors as we move data between variables:
my Int $foo = 5; my Num $bar = 3.14; $foo = $bar; # ERROR! $bar is not an Int!
In this way, the compiler can tell you if you are trying to use a variable in a way you hadn't intended.