Operators provide a simple syntax for manipulating values. A few characters take the place of a function call, or even several function calls. On the positive side this makes them incredibly convenient. On the negative side they’re also sometimes difficult to learn because they pack so much meaning into a small space. Many of the Perl 6 operators will be familiar, especially to Perl 5 programmers. The new operators either add new features to the language, or move Perl’s operator set toward a more consistent system.
The =
operator is for ordinary
assignment.
It
creates a copy of the values on the righthand side and assigns them
to the variables or data structures on the lefthand side:
$copy = $original; @copies = @originals;
$copy
and $original
both have
the same value, and @copies
has a copy of every
element in @originals
.
The :=
operator is for binding assignment.
Instead of copying the value from one variable or structure to the
other, it creates an alias. An alias is an additional entry in the
symbol table with a different name for the one container:
$a := $b; # $a and $b are aliases @c := @d; # @c and @d are aliases
In this example, any change to $a
also changes
$b
and vice versa, because
they’re just two separate names for the same
container. Binding assignment requires the same number of elements on
both sides, so both of these would be an error:
# ($a, $b) := ($c); # error # ($a, $b) := ($c, $d, $e); # error
The ::=
operator is a variant of the binding
operator that binds at compile time.
The arithmetic
operators
are
addition (+
),
subtraction
(-
),
multiplication
(*
), division
(/
), modulus
(%
), and
exponentiation
(**
). Each has a corresponding assignment operator
(+=
, -=
, *=
,
/=
, %=
, **=
)
that combines the arithmetic operation with assignment:
$a = 3 + 5; $a += 5; # $a = $a + 5
The unary arithmetic operators are the
prefix and
postfix autoincrement
(++
) and
autodecrement
(--
) operators. The prefix operators modify their argument
before it’s evaluated, and the postfix operators
modify it afterward:
$a++; $a--; ++$a; --$a;
The ~
operator
concatenates
strings. The corresponding ~=
operator
concatenates the righthand side of the assignment to the end of the
string:
$line = "The quick brown " ~ $fox ~ jumps_over( ) ~ " the lazy " ~ $dog; $line ~= "Belgium"; # appends to the string
The x
operator replicates strings. It always returns a string no matter
whether the left side of the operation is a single element or a list.
The following example assigns the string
“LintillaLintillaLintilla”:
$triplet = "Lintilla" x 3;
The corresponding x=
operator replicates the
original string and assigns it back to the original variable:
$twin = "Lintilla"; $twin x= 2; # "LintillaLintilla"
The xx
operator replicates
lists.
It returns a list no matter whether it operates on a list of elements
or a single element. The following example assigns a list of three
elements to @array
, each with a copy of the value
“Lintilla”:
@array = "Lintilla" xx 3; # ("Lintilla", "Lintilla", "Lintilla")
The corresponding xx=
operator creates a list that contains the specified number of copies
of every element in the original array and assigns it back to the
array variable:
@array = (4, 2); @array xx= 2; # now (4, 2, 4, 2) @array = (@array, @array); # equivalent
The
range
operator .
. returns a list of values from a
starting point to an ending point:
@range = 3..7; # 3,4,5,6,7
Ranges evaluate lazily, so a range containing an infinite value won’t try to calculate all the values before assigning the list. Instead, it returns a list generator that only generates elements as they’re requested.
@range = 3..Inf; # lazy
The . .
. operator is equivalent to
..Inf
:
@range = 3 . . . ;
Each comparison operator
has
two
forms, one for numeric comparisons and one for string comparisons.
The comparison operators are greater-than (>
,
gt
), less-than (<
,
lt
), greater-than-or-equal
(>=
, ge
), less-than-or-equal
(<=
, le
), equal (= =
, eq
), and not-equal
(!=
, ne
). The identity operator
(=:=
) tests whether the two arguments are aliases
to the same object. Each returns a true value if the relation is true
and a false value otherwise. The generic comparison operators
(<=>
, cmp
) return
0
if the two arguments are equal,
1
if the first is greater, and
-1
if the second is greater:
if ($age > 12) { . . . }
Comparison operators can also be chained. Chained comparisons evaluate each value in the chain only once:
if (24 < $age < 42) { . . . } # 24 < $age and $age < 42
The binary logical operators test two values and return one value or the other depending on certain truth conditions. They’re also known as the short-circuit operators because the righthand side will never be evaluated if the overall truth value can be determined from the lefthand side. This makes them useful for conditionally assigning values or executing code.
The AND
relation has the
&&
operator and the low-precedence and
operator. If
the lefthand side evaluates as false, its value is returned. If the
lefthand value is true, the righthand side is evaluated and its value
is returned:
$splat = $whale && $petunia; $splat = ($whale and $petunia);
The OR relation
has the
||
operator and
the low-precedence or
operator. The lefthand value
is returned if it is true; otherwise, the righthand value is
evaluated and returned:
$splat = $whale || $petunia; $splat = ($whale or $petunia);
A variant of the OR relation tests for definedness instead of truth.
It uses the //
operator and the low-precedence
err
operator. The lefthand value is returned if it
is defined; otherwise, the righthand side is evaluated and its value
returned:
$splat = $whale // $petunia; $splat = ($whale err $petunia);
The XOR
relation has the ^^
operator and the low-precedence
xor
operator. It returns the value of the true
operand if any one operand is true, and a false value if both are
true or neither is true. xor
isn’t short-circuiting like the others, because it
always has to evaluate both arguments to know if the relation is
true:
$splat = $whale ^^ $petunia; $splat = ($whale xor $petunia);
Perl 6 also has Boolean variants of the logical operators:
?&
(AND), ?|
(OR), and
?^
(XOR). These always return a true or false
value.
$whale = 42; $petunia = 24; $value = $whale || $petunia # $value is 42 $truth = $whale ?| $petunia # $truth is 1
The context of an expression specifies the type of value it is expected to produce. An array expects to be assigned multiple values at the same time, so assignment to an array happens in list context. A scalar variable expects to be assigned a single value, so assignment to a scalar happens in scalar context. Perl expressions often adapt to their context, producing values that fit with what’s expected.
Contexts have proven to be valuable tools in Perl 5, so Perl 6 has a few more added. Void context still exists. Scalar context is subdivided into Boolean, integer, numeric, string, and object contexts. List context is subdivided into flattening-list context, nonflattening-list context, lazy list context, and hashlist context.
- Void context
Expects no value.
- Scalar context
Expects a single value. A composite value returns a reference to itself in scalar context.
- Boolean context
Expects a true or false value. This includes the traditional definitions of truth—where
0
,undef
, and the empty string are false and all other values are true—and values flagged with the propertiestrue
orfalse
.- Integer context
Expects an integer value. Strings are treated as numeric and floating-point numbers are truncated.
- Numeric context
Expects a number, whether it’s an integer or floating-point, and whether it’s decimal, binary, octal, hex, or some other base.
- String context
Expects a string value. It interprets any information passed to it as a string of characters.
- Object context
Expects an object, or more specifically, a reference to an object. It may also expect an object of a particular type.
- List context
Expects a collection of values. Any single value in list context is treated as a one-element list.
- Flattening-list context
Expects a list. Flattens out arrays and hashes into their component parts.
- Nonflattening-list context
Expects a list. Treats arrays, hashes, and other composite values as discrete entities.
- Lazy list context
Expects a list, just like nonflattening-list context, but doesn’t require all the elements at once.
- Hashlist context
Expects a list of pairs. A simple list in hashlist context pairs up alternating elements.
The unary context operators force a particular context when it wouldn’t otherwise be imposed. Generally, the default context is the right one, but at times you might want a little more control.
The unary ?
operator and its low-precedence
equivalent true
force Boolean context. Assignment
of a scalar to a scalar only imposes generic scalar context, so the
value of $number
is simply copied. With the
?
operator, you can force Boolean context and
assign the truth value of the variable instead of the numeric value:
$value = $number; $truth = ?$number;
The unary !
operator and the low-precedence
not
also force Boolean context, but they negate
the value at the same time. They’re often used in a
Boolean context, where only the negating effect is visible.
$untruth = !$number;
The unary +
operator forces numeric context, and
-
forces numeric context and negates the number at
the same time:
$number = +$string; $negnum = -$string;
The unary ~
operator forces string context:
$string = ~$number;
You can also create a scalar, list,
or hashlist context with $( . . . )
, @( . . . )
, and %( . . . )
.
Perl 6 has two sets of
bitwise
operators, one for integers and one for strings. The
integer bitwise operators combine the AND,
OR, and XOR relation symbols with the general numeric symbol
+
(the unary numeric context operator). These are
the binary +&
, +|
, and
+^
and the unary +^
for bitwise
negation (ones complement). The default integer type in Perl 6 is a
signed int, so the results are equivalent to working with the
use integer
pragma turned on in Perl 5:
$number = 42 +& 18; # $number is 2 $number = 42 +| 18; # $number is 58 $number = 42 +^ 18; # $number is 56 $number = +^ 42; # $number is -43
The numeric bitwise shift operators shift
the value of the left operand by the number of bits in the right
operand, either to the left (<<
) or to the
right (>>
):
$number = 4 << 1; # $number is 8 $number = 4 >> 1; # $number is 2
The string bitwise operators combine the
AND, OR, and XOR relation symbols with the general string symbol
~
(the same symbol as string concatenation and the
unary string context operator). These are ~&
,
~|
, and ~^
.
$string = 'jj' ~& 'gg'; # $string is 'bb' $string = 'aa' ~| 'bb'; # $string is 'cc' $string = "GG" ~^ "**"; # $string is 'mm'
Each of the binary bitwise operators has an assignment counterpart:
+&=
, +|=
,
+^=
, <<=
,
>>=
, ~&=
,
~|=
, and ~^=
.
The ternary ??:
:
operator
evaluates either its second or third operand, depending on whether
the first operand evaluates as true or false. It’s
basically an if-then-else statement acting as an expression:
$form = ($heads = = 2) ?? "Zaphod" :: "ape-descended lifeform";
The hyper operators are designed to work with lists.
They’re simply modified versions of the standard
scalar operators.
Every operator has a hyper version,
even user-defined operators. They have the same basic forms as their
scalar counterparts, but are marked with the bracketing characters
»
and
«
,[5] or their plain-text
equivalents >>
and
<<
. For example, the hyper addition operator
is >>+<<
.
Hyper operators impose list context on their operands and distribute their operations across all the operands’ elements. Hyper addition takes each element from the first list and adds it to the corresponding element in the second list:
@sums = @first >>+<< @second;
The resulting array contains the sums of each pair of elements, as if each pair were added with the scalar operator:
@sums = ( (@first[0] + @second[0]), (@first[1] + @second[1]), etc . . . );
If one side of a hyper operation is a simple scalar, it is distributed across the list as if it were a list of identical elements:
@sums = @numbers >>+<< 5; @sums ( (@numbers[0] + 5), (@numbers[1] + 5), etc . . . );
Unary operators may also take a one-sided hyper on the side of their single operand:
@less = @numbers >>--; @nums = +<< @strings;
At the simplest level, junction
operators are no more than AND, OR, XOR, and NOT for values instead
of expressions. The binary junction operators are
&
(AND), |
(OR), and
^
(XOR). There isn’t an operator
for junctive NOT, but there is a function, as you’ll
see shortly. So, while ||
is a logical operation
on two expressions:
if ($value = = 1) || ($value = = 2) { . . . }
|
is the same logical relation between two values:
if $value = = 1 | 2 { . . . }
In fact, those two examples have exactly the same result: they return
true when $value
is 1
or
2
and false otherwise. In the common case,
that’s all you’ll ever need to
know.
But junctions are a good deal more powerful than that, once you learn their secrets. A junctive operation doesn’t return an ordinary single value, it returns a composite value containing all of its operands. This return value is a junction, and it can be used anywhere a junction operation is used:
$junc = 1 | 2; if ($value = = $junc) { . . . }
Here, the variable $junc
is used in place of
1 | 2
, and has exactly the same effect as the
earlier example.
A junction is basically just an unordered set with a logical relation defined between its elements. Any operation on the junction is an operation on the entire set. Table 4-1 shows the way the four different types of junctions interact with other operators.
Table 4-1. Picture junctions
Function |
Operator |
Relation |
Meaning |
---|---|---|---|
|
|
AND |
Operation must be true for all values |
|
|
OR |
Operation must be true for at least one value |
|
|
XOR |
Operation must be true for exactly one value |
|
NOT |
Operation must be false for all values |
The simplest possible example is the result of evaluating a junction
in Boolean context. The operation on the set is just
“is it true?” This operation on an
all
junction is true if all
the values are true:
true( $a & $b ) true( all($a,$b) )
So, if both $a
and $b
are true,
the result is true.
On an any
junction, it’s true if
any one value is true:
true( $a | $b ) true( any($a,$b) )
So, if $a
or $b
is true or if
both are true, the result is true.
On a one
junction, it’s true only
if exactly one value is true:
true( $a ^ $b ) true( one($a,$b) )
So, if either $a
or $b
is true,
the result is true. But, if $a
and
$b
are both true or neither is true, the result is
false.
On a none
junction, it’s true
only when none of the values are true—that
is, when all the values are false:
true( none($a,$b) )
So, if $a
and $b
are both
false, the result is true.
Ordinary arithmetic operators interact with junctions much like hyper operators on arrays. A junction distributes the operation across all of its elements:
$junc = any(1, 2); $junc += 5; # $junc is now any(6, 7)
Junctions can be combined to produce compact and powerful logical comparisons. If you want to test that two sets have no intersection, you might do something like:
if all($a, $b) = = none($c, $d) { . . . }
which tests that all of the elements of the first set are equal to none of the elements of the second set. Translated to ordinary logical operators that’s:
if ($a != $c) && ($a != $d) && ($b != $c) && ($b != $d) { . . . }
If you want to get back a flat list of values from a junction, use
the .values
method:
$junc = all(1, 2, 3); # create a junction $sums = $junc + 3; # add 3 @set = $sums.values( ); # (4, 5, 6)
The .dump
method returns a string
that shows the structure of a junction:
$string = $sums.dump( ); # "all(4,5,6)"
The .pick
method selects one value from an any
junction or a
one
junction that has exactly one value, and
returns it as an ordinary scalar:
$junc = any(1, 2, 3); $single = $junc.pick( ); # may be 1, 2, or 3
On an all
junction, a none
junction, or a one
junction with more than one
value, .pick
returns undef
.
(With some levels of
error strictness, it may raise an
exception.)
The binary ~~
operator
makes a smart match between its two
terms. It returns a true value if the match is successful and a false
value if the match fails.[6] The negated
smart match operator !~
does the exact opposite:
it returns true if the match fails and false if it is successful. The
kind of match a smart match does is determined by the kind of
arguments it matches. If the types of the two arguments
can’t be determined at compile time, the kind of
match is determined at run time. Smart match is usually a symmetric
operator, so you can reverse A ~~ B
to B ~~ A
and it will have the same truth value.
Any scalar value (or any code that results in a scalar value)
matched
against a string tests for string equality. The following match is
true if $string
has the value
“Ford”:
$string ~~ "Ford"
Any scalar value matched against a numeric value tests for numeric
equality. The following is true if $number
has the
numeric value 42, or the string value
“42”:
$number ~~ 42
An expression that results in the value 42 is also true:
( (5 * 8) + 2 ) ~~ 42
Any scalar value matched against an undefined value checks for
definedness. The following matches are true if
$value
is an undefined value and false if
$value
is any defined value:
$value ~~ undef $value ~~ $undefined_value
Any scalar value matched against a rule (regex) does a pattern match.
The following match is true if the sequence
“towel” can be found anywhere
within $string
:
$string ~~ /towel/
Any scalar value matched against a substitution attempts that
substitution on the value. This means the value has to be modifiable.
The following match is true if the substitution succeeds on
$string
and false if it fails:
$string ~~ s/weapon/towel/
Any scalar value matched against a Boolean value simply takes the truth value of the Boolean. The following match will always be true, because the Boolean on the right is always true:[7]
$value ~~ (1 = = 1)
The Boolean value on the right must be an actual Boolean: the result
of a Boolean comparison or operation, the return value of a
not
or true
function, or a
value forced into Boolean context by !
or
?
. The Boolean value also must be on the right; a
Boolean on the left is treated as an ordinary scalar value.
Any scalar value matched
against
a list compares each element in sequence. The match is true if at
least one element of the list would match in a simple
expression-to-expression match. The following match is true if
$value
is the same as any of the three strings on
the right:
$value ~~ ( "Zaphod", "Ford", "Trillian" )
This match is short-circuiting: it stops after the first successful
match. It has the same truth value as a series of
or
-ed matches:
($value ~~ "Zaphod") or ($value ~~ "Ford") or ($value ~~ "Trillian")
A smart-matched list can contain any combination of elements: scalar values, rules, Boolean expressions, arrays, hashes, etc.:
$value ~~ ( "Zaphod", 5, /petunias/ )
A match of a list against another list sequentially compares each element in the first list to the corresponding element in the second list. The match is true if every element of the first list matches the corresponding element in the second list. The following match is true, because the two lists are identical:
( "Zaphod", "Ford", "Trillian" ) ~~ ( "Zaphod", "Ford", "Trillian" )
The two lists don’t have to be identical, as long as they’re the same length and their corresponding elements match:
( $zaphod, $ford, $trillian ) ~~ ( "Zaphod", /Ford/, /^T/ )
The list-to-list match is also short-circuiting. It stops after the
first failed match. This has the same truth value as a series of
single-element smart matches linked by and
:
($zaphod ~~ "Zaphod") and ($ford ~~ /Ford/) and ($trillian ~~ /^T/)
A nonnumeric expression matched
against
an array sequentially searches for that value in the array. The match
is true if the value is found. If @array
contains
the values “Zaphod”,
“Ford”, and
“Trillian”, the following match is
true when $value
matches any of those three
strings:
$value ~~ @array
An integer value matched against an array tests the truth of the
value at that numeric index. The following match is true if the
element @array[2]
exists and has a true value:
2 ~~ @array
An integer value matched against an array reference also does an index lookup:
2 ~~ [ "Zaphod", "Ford", "Trillian" ]
This match is true, because the third element of the array reference is a true value.
An array matches just like a list of scalar values if
it’s flattened with the *
operator (See Section 4.2.13 later in this chapter). So, the
following example searches the array for an element with the value
2
, instead of doing an index lookup:
2 ~~ *@array
An array matched against a rule does a pattern match against every
element of the array. The match is true if any element matches the
rule. If “Trillian”,
“Milliways”, or
“million” is an element of
@array
, the following match is true, no matter
what the other elements are:
@array ~~ /illi/
A match of an array against an array sequentially compares each element in the first array to the corresponding element in the second array:
@humans ~~ @vogons
This match is true if the two arrays are the same length and
@humans[0]
matches @vogons[0]
,
@humans[1]
matches @vogons[1]
,
etc.
A hash matched against any scalar value tests the truth value of the hash entry with that key:
$key ~~ %hash
This match is true if the element %hash{$key}
exists and has a true value.
A hash matched against a rule does a pattern match on the hash keys:
%hash ~~ /blue/
This match is true if at least one key in %hash
matches the string “blue”.
A hash matched against a hash checks for intersection between the keys of the two hashes:
%vogons ~~ %humans
So, this match is true if at least one key from
%vogons
is also a key of
%humans
. If you want to see that two hashes have
exactly the same keys, match their lists of keys:
%vogons.keys.sort ~~ %humans.keys.sort
A hash matched against an array checks a slice of a hash to see if its values are true. The match is true if any element of the array is a key in the hash and the hash value for that key is true:
%hash ~~ @array
If @array
has one element
“blue” and %hash
has a corresponding key “blue”, the
match is true if %hash{'blue'}
has a true value,
but false if %hash{'blue'}
has a false value.
An expression matched against
an
any
junction is a recursive disjunction. The match
is true if at least one of the elements of the list would match in a
simple expression-to-expression match:
$value ~~ any("Zaphod", "Ford", "Trillian")
This example matches if $value
is the same as any
of the three strings on the right. The effect of this comparison is
the same as a simple comparison to a list, except that it
isn’t guaranteed to compare in any particular order.
A smart match of an all
junction is only true when
the expression matches every value in the junction:
/illi/ ~~ all("Gillian", "million", "Trillian") # match succeeds /illi/ ~~ all("Zaphod", "Ford", "Trillian") # match fails
A smart match of a one
junction is only true when
the expression matches exactly one value in the junction:
/illi/ ~~ one("Zaphod", "Ford", "Trillian") # match succeeds /illi/ ~~ one("Gillian", "million", "Trillian") # match fails
A smart match of a none
junction is true when it
doesn’t match any values in the junction:
/illi/ ~~ none("Zaphod", "Ford", "Marvin") # match succeeds /illi/ ~~ none("Zaphod", "Ford", "Trillian") # match fails
An any
junction matched against another
any
junction is a recursive disjunction of every
value in the first junction to every value in the second junction.
The match is true if at least one value of the first junction matches
at least one value in the second junction:
any("Ford", "Trillian") ~~ any("Trillian", "Arthur")
This match is true, because “Trillian” is in both junctions.
An object matched
against
a class name is true if the object belongs to that class or inherits
from that class. It’s essentially the same as
calling the .isa
method on the object:
$ship ~~ Vogon::Constructor # $ship.isa(Vogon::Constructor)
Any expression matched against a subroutine tests the return value of the subroutine. If the subroutine takes no arguments, it is treated as a simple Boolean:
$value ~~ my_true
If the subroutine has a one argument signature and it is compatible with the variable type of the expression, the subroutine is called with the expression as its argument:
$value ~~ &value_test # value_test($value) @array ~~ &array_test # array_test(@array) %hash ~~ &hash_test # hash_test(%hash)
The return value of the subroutine determines the truth of the match.
A block matches as an anonymous subroutine. The return value of the
block determines the truth of the match. It’s
treated as a simple Boolean if it takes no arguments, or passed the
value on the left side if it uses $_
or
placeholder variables inside the
block (see Section 5.2.7 in Chapter 5).
$value ~~ { $_ + 5; } # $_ is $value %hash ~~ { $_.keys; } # $_ is \%hash @array ~~ { @^a.elems; } # @^a is @array
The unary \
operator returns a
reference
to its operand. The referencing operator isn’t
needed very often, since scalar context automatically generates
references to arrays, hashes, and functions, but it is still needed
in flattening contexts and other contexts that don’t
auto-reference:
@array_of_refs = ( \@a, \@b, \@c );
Ordinarily, an array assigned a list of arrays would flatten the elements of all the arrays into a single array. With the referencing operator, @array_of_refs is assigned a list of three arrayrefs.
The unary *
operator (known as the splat operator)
flattens a list in a context where it would usually be taken as a
reference. On an rvalue, *
causes the array to be
treated as a simple list:
@combo = (\@array, \%hash); @a := @combo; # @a is @combo (@b, %c) := *@combo; # @b is @array, %c is %hash
Since the @combo
array contains an arrayref and a
hashref, an ordinary binding assignment of @combo
to @a
treats @combo
as a single
element and binds it to @a
. With the flattening
operator, the @combo
array is treated as a simple
list, so each of its elements are bound to a separate element on the
lefthand side. @b
is bound to the original
@array
and %c
is bound to the
original %hash
.
On an lvalue, *
tells the array to slurp all
available arguments. An ordinary binding of two arrays to two arrays
simply binds the first element on the righthand side to the first
element on the lefthand side, and the second to the second. So,
@a
is bound to @c
, and
@b
is bound to @d
:
(@a, @b) := (@c, @d); # @a is @c, @b is @d
With the *
operator, the first element on the
lefthand side flattens all the elements on the righthand side into a
list before the binding assignment. So, @a
contains all the elements from @c
and
@d
:
*@a := (@c, @d); # @a contains @c and @d
One common use for *
is in defining subroutine and
method signatures, as you will see in Section 5.2.3 in Chapter 5.
The ¦ operator takes two or more lists (arrays, hash keys, etc.) and returns a single list with alternating elements from each of the original lists. This allows loops and other iterative structures to iterate through the elements of several lists at the same time:
@a = (1, 2, 3); @b = (4, 5, 6); @c = @a ¬¦ @b; # @c is (1, 4, 2, 5, 3, 6)
There is no equivalent ASCII operator for the zip operator, but the
zip
function is much more fully featured than the
operator. It is described in Section 4.3.2.3 later in this chapter.
[5] These are the Unicode RIGHT POINTING GUILLEMET (U+00BB) and LEFT POINTING GUILLEMET (U+00AB) characters.
[6] This is an oversimplification. Some matches return a more complex value, but in Boolean context it will always evaluate as true for a successful match, and false for a failed match.
[7] At the moment this relation won’t seem particularly useful. It makes much more sense when you realize that the switch statement duplicates all the smart match relations. More on that in Section 4.3.1.3 later in this chapter.
Get Perl 6 and Parrot Essentials, Second Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.