# Ruby to Perl You know Ruby. You pass blocks to `each` and `map` without thinking, you reach for a `Hash` by reflex, and `arr.select { |x| ... }` is muscle memory. This guide maps each of those habits onto Perl, so you can be productive in `pperl` in an afternoon instead of relearning programming from scratch. Two shifts land in the first five minutes, so meet them now: - **Sigils mark the type, not the scope.** Ruby uses `@var` for instance state, `$var` for globals, and a bare `var` for locals. Perl uses `$x` for a single value, `@x` for an ordered list, `%x` for a key/value map - the sigil tells you the *shape* of the data, and it is the same whether the variable is local or global. Section one is about nothing else. - **Not everything is an object.** In Ruby `"foo".upcase` and `[1, 2].sum` are method calls because numbers, strings, and arrays are all objects. Perl is pragmatic: a scalar is not an object, there is no universal base class, and those operations are plain functions - `uc "foo"`, `sum @a`. You call methods only on things you blessed into a class. Every example here runs on `pperl`, a Rust reimplementation of Perl 5.42. Each Perl snippet was executed on `pperl`; the `# ...` comments show its real output. Start your scripts with: ```perl use v5.42; ``` That one line turns on `strict` (no accidental globals), `warnings`, the `say` built-in (a `print` with a trailing newline, like Ruby's `puts`), subroutine signatures, and the modern feature set used throughout this guide. ## How to read this - [Variables and sigils](#variables-and-sigils) - the `$`/`@`/`%` shift. - [Strings](#strings) - interpolation and concatenation. - [Numbers](#numbers) - one surface type, float division. - [Arrays and lists](#arrays-and-lists) - your `Array`, plus block-taking `map`/`grep`. - [Hashes](#hashes) - your `Hash`, and where symbols go. - [Context](#context) - the concept Ruby has no word for. Read this one. - [Truthiness](#truthiness) - what counts as false, and it differs sharply. - [Control flow](#control-flow) - `if`, loops, the postfix forms. - [Subroutines](#subroutines) - `def` becomes `sub`. - [References](#references) - nested data, and blocks as values. - [Regular expressions](#regular-expressions) - regex as syntax, like Ruby's. - [File and stream I/O](#file-and-stream-io) - `open`, line reads, slurp. - [Modules and packages](#modules-and-packages) - `require` becomes `use`. - [Objects](#objects) - the modern `class` syntax. - [Error handling](#error-handling) - `begin`/`rescue` becomes `eval` or `try`/`catch`. - [Idioms](#idioms) - the daily one-liners. - [Gotchas coming from Ruby](#gotchas-coming-from-ruby) - scan before you write. ## Variables and sigils In Ruby a sigil tells you *scope*: `@x` is an instance variable, `$x` a global, `x` a local. In Perl the **sigil tells you shape**: `$` for a single value, `@` for an ordered list, `%` for a key/value map. `my` declares a lexically scoped variable, the Perl equivalent of an ordinary local binding - it is how you get a local in Perl, where Ruby just uses a bare name. ```ruby name = "Ada" langs = ["Ruby", "Perl"] ages = {"Ada" => 36, "Yukihiro" => 60} ``` ```perl use v5.42; my $name = "Ada"; my @langs = ("Ruby", "Perl"); my %ages = (Ada => 36, Yukihiro => 60); say $name; # Ada say $langs[0]; # Ruby say $ages{Ada}; # 36 ``` The gotcha: when you reach *into* an aggregate for one element, the sigil follows the value you get back, not the container. A single element of `@langs` is a scalar, so it is `$langs[0]`, not `@langs[0]`; one value from `%ages` is `$ages{Ada}`. The `@`/`%` form is the whole collection; the `$` form is one piece of it. (And forget the Ruby mapping: a Perl `$x` is never "a global" the way Ruby's `$x` is - the `$` here just means "one value.") ## Strings Double quotes interpolate variables, single quotes do not, exactly like Ruby's `"..."` versus `'...'`. The Ruby `#{expr}` form has a direct counterpart for plain variables and a slightly longer one for arbitrary expressions. Concatenation is `.`, not `+`. ```ruby name = "Ada" greeting = "Hello, #{name}" total = "Total: #{6 * 7}" shout = "AB" * 3 ``` ```perl use v5.42; my $name = "Ada"; my $greeting = "Hello, $name"; # Hello, Ada my $literal = 'Hello, $name'; # Hello, $name (no interpolation) my $total = "Total: @{[ 6 * 7 ]}"; # Total: 42 my $joined = "AB" . "CD"; # ABCD my $shout = "AB" x 3; # ABABAB ``` The gotcha: a bare `"$name"` interpolates a variable just like Ruby's `#{name}`, but to interpolate an *expression* you wrap it in the `@{[ ... ]}` form (it builds a throwaway array and inlines it). String repetition is `x`, not `*`, and the join operator is `.`, not `+`; `+` on strings coerces them to numbers and warns. Array elements and hash values interpolate inside double quotes too: `"First: $langs[0]"` works as written. ## Numbers Perl has one numeric type at the surface; you do not distinguish `Integer` from `Float`. Division always produces a float, like Ruby's `Float` division when either operand is a float. ```ruby puts 7.0 / 2 # 3.5 puts 7 / 2 # 3 (integer division in Ruby) puts 2 ** 10 # 1024 puts 10 % 3 # 1 ``` ```perl use v5.42; say 7 / 2; # 3.5 (always float) say int(7 / 2); # 3 say 2 ** 10; # 1024 say 10 % 3; # 1 ``` The gotcha: comparison and equality operators come in two families, and you pick by the *operands' type*, not the variables'. Numbers use `==`, `!=`, `<`, `>`, `<=>`; strings use `eq`, `ne`, `lt`, `gt`, `cmp`. This has no Ruby analogue - Ruby's `==` dispatches on the object's class, while Perl's `==` always means *numeric* equality. `"10" == "10.0"` is true (numeric), but `"10" eq "10.0"` is false (string). Using `==` on strings, or `eq` on numbers, is a frequent and quiet bug. ## Arrays and lists A Perl array is your Ruby `Array`. The big shift is method calls becoming functions: Ruby's `arr.push(x)`, `arr.pop`, `arr.length` become `push @arr, x`, `pop @arr`, `scalar @arr` - the array is an argument, not a receiver. ```ruby nums = [1, 2, 3, 4, 5] nums.push(6) last = nums.pop # 6 puts nums.length # 5 puts nums[1, 2].join(" ") # 2 3 ``` ```perl use v5.42; my @nums = (1, 2, 3, 4, 5); push @nums, 6; my $last = pop @nums; # 6 say scalar @nums; # 5 say "@nums[1,2]"; # 2 3 (a slice) ``` Ruby's block-taking iterators are the richest part of this mapping. `map`, `select`, and `sort_by` become Perl `map`, `grep`, and `sort`, which take a `{ ... }` block exactly the way Ruby methods do. Inside the block, `$_` is the current element - Perl's implicit block variable, the role Ruby's `|x|` parameter plays. ```ruby squares = nums.map { |x| x * x } evens = nums.select { |x| x.even? } desc = nums.sort { |a, b| b <=> a } ``` ```perl use v5.42; my @nums = (1, 2, 3, 4, 5); my @squares = map { $_ * $_ } @nums; # 1 4 9 16 25 my @evens = grep { $_ % 2 == 0 } @nums; # 2 4 my @desc = sort { $b <=> $a } @nums; # 5 4 3 2 1 ``` The gotcha: `sort` compares as strings by default, so `sort (10, 9, 100)` gives `10 100 9`. For numbers you must pass a comparator: `sort { $a <=> $b } @nums`. `$a` and `$b` are the two elements being compared (the equivalent of Ruby's `|a, b|`), and `<=>` is the numeric three-way operator - the very same spaceship you know from Ruby (`cmp` is its string sibling). ## Hashes A Perl hash is your Ruby `Hash`. Declared with `%`, accessed one value at a time with `$h{key}`. The fat comma `=>` you already use in Ruby hash literals works here too, and it auto-quotes the bareword on its left. ```ruby h = {apple: 3, pear: 5, plum: 2} puts h[:apple] # 3 puts h.keys.sort.join(",") # apple,pear,plum puts h.key?(:pear) # true h.delete(:plum) h.sort.each { |k, v| puts "#{k} => #{v}" } ``` ```perl use v5.42; my %h = (apple => 3, pear => 5, plum => 2); say $h{apple}; # 3 say join ",", sort keys %h; # apple,pear,plum say exists $h{pear} ? "yes" : "no"; # yes delete $h{plum}; for my $k (sort keys %h) { say "$k => $h{$k}"; } ``` The gotcha: Perl has **no symbol type**. Where Ruby reaches for `:apple` as a lightweight, interned key, Perl just uses the string `"apple"` - and the `=>` fat comma auto-quotes it, so `apple => 3` and `$h{apple}` need no quotes. A Ruby symbol and a Ruby string are different objects; in Perl there is only the string, so the `:sym` versus `"sym"` distinction simply vanishes. Membership is `exists $h{key}`, not Ruby's `key?`, and a hash has no order - iterate `sort keys %h` when you want a stable one. ## Context This is the chapter to read twice. Ruby has no equivalent, and almost every "Perl is weird" story traces back to it. Every expression in Perl runs in either **list context** or **scalar context**, decided by where it appears, not by what it is. The same array behaves differently in each. Assigning an array to a *scalar* gives its length; assigning it to a *list* (a parenthesised set of variables) copies elements. Ruby never overloads assignment this way - `a = arr` always just aliases the array - so this is genuinely new. ```perl use v5.42; my @list = (10, 20, 30); my $count = @list; # scalar context -> 3 (the length) say $count; # 3 my ($first) = @list; # list context -> first element copied say $first; # 10 my ($a, $b) = @list; # 10 and 20 say "$a $b"; # 10 20 ``` The parentheses on the left are the whole difference. `my $x = @list` asks "how many?" and gets `3`. `my ($x) = @list` asks "give me the elements" and gets `10`. The Ruby reflex reads both as "assign the array," which is exactly the trap. A subroutine can ask which context it was called in, with `wantarray`, and return different things accordingly: ```perl use v5.42; sub items { return wantarray ? (1, 2, 3) : "three items"; } my @got = items(); # list context my $msg = items(); # scalar context say "@got"; # 1 2 3 say $msg; # three items ``` Built-ins use this too. `scalar @array` forces scalar context to get a count; a regex match with `/g` returns the list of matches in list context and a true/false in scalar context. The classic idiom for "count the matches" forces a list assignment in scalar context with the so-called *countof* form: ```perl use v5.42; my $n = () = "a1b2c3" =~ /\d/g; say $n; # 3 ``` The gotcha: there is no single answer to "what does this array do" - it depends on the surrounding expression. When something returns a number you expected to be a list (or vice versa), context is the first suspect. Reach for `scalar` to force a count and parentheses on the left to force element copying. ## Truthiness This is the sharpest break from Ruby in the whole guide. In Ruby only `nil` and `false` are falsy - `0` and `""` are both **true**. In Perl the false values are a short, specific list: the number `0`, the empty string `""`, the string `"0"`, and `undef` (Perl's `nil`). Everything else is true. ```perl use v5.42; for my $v (0, 1, "", "0", "00", "0.0", "false") { say "'$v' is ", ($v ? "true" : "false"); } ``` This prints, exactly: ```text '0' is false '1' is true '' is false '0' is false '00' is true '0.0' is true 'false' is true ``` The gotcha: `0` and `""` are **false** in Perl but **true** in Ruby - the single most disruptive habit to unlearn. A Ruby guard like `if count` fires for `count == 0`; the Perl `if ($count)` does not. On the other side, `"00"` and `"0.0"` are **true** in Perl even though they look numerically zero, because only the exact string `"0"` is false. And unlike Ruby, where you might lean on `nil` checks, Perl distinguishes "false" from "undefined" with defined-or `//`, which falls back only on `undef` (not on `0` or `""`): ```perl use v5.42; my %cfg = (host => "localhost"); my $port = $cfg{port} // 8080; # key absent -> undef -> 8080 say $port; # 8080 ``` ## Control flow The keywords are familiar; the spelling differs. `elsif` is Perl's `elsif` (Ruby's `elsif` too - one of the few exact matches), and blocks are braced, with no `then` and no terminating `end`. ```ruby if n < 0 then sign = "neg" elsif n == 0 then sign = "zero" else sign = "pos" end nums.each { |x| puts x } while running step end ``` ```perl use v5.42; my $sign; if ($n < 0) { $sign = "neg" } elsif ($n == 0) { $sign = "zero" } else { $sign = "pos" } for my $x (@nums) { say $x } while ($running) { step() } ``` Perl's **postfix modifiers** are the direct counterpart of Ruby's trailing `puts x if cond` and `do_it while cond` - a simple statement carrying its own condition or loop: ```perl use v5.42; say $_ for @nums; # loop, one statement say "negative" if $n < 0; # guarded statement $sum += $_ for @nums; # accumulate ``` And `unless`/`until` are the negated forms of `if`/`while`, just like Ruby's `unless`/`until`: `say "ok" unless $error`. The gotcha: the ternary uses `?`/`:`, not Ruby's `cond ? a : b` - which is actually identical, so this one is free. There is no `x if c else y` expression form; chain ternaries instead: `my $sign = $n < 0 ? "neg" : $n == 0 ? "zero" : "pos"`. ## Subroutines `def` becomes `sub`. Modern Perl has signatures that look much like Ruby's parameter lists, including defaults. And the good news a Ruby developer wants to hear: **the last evaluated expression is the return value**, exactly as in Ruby - `return` is optional. ```ruby def greet(name, greeting = "Hello") "#{greeting}, #{name}!" end def total(*nums) nums.sum end ``` ```perl use v5.42; sub greet ($name, $greeting = "Hello") { return "$greeting, $name!"; } say greet("Ada"); # Hello, Ada! say greet("Yukihiro", "Hi"); # Hi, Yukihiro! sub area ($w, $h) { $w * $h } # no return needed; last expr wins say area(3, 4); # 12 sub total (@nums) { my $sum = 0; $sum += $_ for @nums; return $sum; } say total(1, 2, 3, 4); # 10 ``` Ruby keyword arguments map to a hash parameter, since `key => value` pairs flatten into a list that a `%opts` signature gathers up: ```perl use v5.42; sub connect_to (%opts) { return "host=$opts{host} port=$opts{port}"; } say connect_to(host => "localhost", port => 8080); # host=localhost port=8080 ``` The gotcha: a sub returns the value of its last expression even without `return` (the Ruby reflex carries over), but in Perl that return is also **context-sensitive** (see [Context](#context)). If you `return @list`, the caller gets the list in list context but the *count* in scalar context. Be deliberate about what you return when it might be used either way. ## References Perl's arrays and hashes do not nest directly; you nest them through **references**, a scalar that points at an aggregate. In Ruby every container is already a reference, so nesting is invisible to you there; in Perl you take a reference with `\` (or build one with `[ ]`/`{ }`) and follow it with `->`. ```ruby data = {name: "Ada", langs: ["Perl", "Ruby"]} puts data[:langs][1] # Ruby data[:langs].push("Python") ``` ```perl use v5.42; my $data = { name => "Ada", langs => ["Perl", "Ruby"] }; say $data->{name}; # Ada say $data->{langs}[1]; # Ruby push $data->{langs}->@*, "Python"; # postfix deref to push say "@{$data->{langs}}"; # Perl Ruby Python ``` `{ ... }` builds an anonymous hash reference and `[ ... ]` an anonymous array reference - the literal building blocks of nested data, matching Ruby's `{}` and `[]` literals. The `->` arrow follows a reference one step; between two subscripts it is optional, so `$data->{langs}[1]` and `$data->{langs}->[1]` are the same. The payoff for a Ruby developer is blocks-as-values. A Ruby block, `proc`, or `lambda` becomes a Perl anonymous sub: a closure stored in a scalar, called with `->`. It closes over its lexicals exactly like a Ruby block: ```perl use v5.42; my $doubler = sub { map { $_ * 2 } @_ }; say join ",", $doubler->(1, 2, 3); # 2,4,6 my $factor = 10; my $scaler = sub ($x) { $x * $factor }; # closes over $factor say $scaler->(5); # 50 ``` The gotcha: a Perl anonymous sub is a value you must *store and call explicitly* with `->( )` - there is no implicit `yield`, no block passed invisibly alongside the arguments. When a builtin wants a block (`map`/`grep`/`sort`, or `first`/`reduce` from `List::Util`), you write the `{ ... }` inline; when you want to pass a callable around as data, you make a `sub { ... }` and call it through its scalar. The postfix deref forms (`->@*`, `->%*`, `->$*`) are the modern, readable way to dereference a whole aggregate. ## Regular expressions Good news: Perl regex will feel like home. Ruby borrowed its regex literal `/.../`, its `=~` operator, and `$1`/`$2` capture variables straight from Perl. The match operator `m//`, the substitution `s///`, and named captures in `%+` round it out. ```ruby s = "2026-06-20" if s =~ /(\d{4})-(\d{2})-(\d{2})/ puts "#{$1} #{$2} #{$3}" end t = "a,b,c".gsub(",", ";") parts = "one,two,three".split(",") ``` ```perl use v5.42; my $s = "2026-06-20"; if ($s =~ /(\d{4})-(\d{2})-(\d{2})/) { say "$1 $2 $3"; # 2026 06 20 } if ($s =~ /(?\d{4})/) { say $+{year}; # 2026 } (my $t = "a,b,c") =~ s/,/;/g; # a;b;c my @parts = split /,/, "one,two,three"; # one two three ``` A `/g` match in list context returns *all* captures, the direct analogue of Ruby's `String#scan`: ```perl use v5.42; my @nums = "x1y2z3" =~ /(\d)/g; # 1 2 3 say "@nums"; ``` The gotcha: where Ruby's `gsub` returns a new string and leaves the original alone, Perl's `s///` modifies its target *in place* and returns the number of substitutions. That is why the example copies first with `(my $t = "a,b,c")` and then substitutes on the copy. To get a new string without touching the original - the `gsub` behaviour you expect - add the `/r` flag: `my $t = $s =~ s/,/;/gr`. ## File and stream I/O The idiom is the three-argument `open` with a lexical filehandle, the counterpart of Ruby's `File.open(path, mode) do |f| ... end`. The angle-bracket operator `<$fh>` reads a line, the same role as Ruby's `f.each_line` or `f.gets`. ```ruby File.open("/tmp/sample.txt", "w") do |f| (1..3).each { |i| f.write("line #{i}\n") } end File.open("/tmp/sample.txt") do |f| f.each_line { |line| puts "got: #{line.chomp}" } end ``` ```perl use v5.42; my $path = "/tmp/sample.txt"; open my $wh, '>', $path or die "cannot write: $!"; print {$wh} "line $_\n" for 1 .. 3; close $wh; open my $rh, '<', $path or die "cannot read: $!"; while (my $line = <$rh>) { chomp $line; say "got: $line"; # got: line 1 ... got: line 3 } close $rh; ``` To slurp every line at once, read the handle in list context: ```perl use v5.42; open my $fh, '<', "/tmp/sample.txt" or die $!; my @lines = <$fh>; # all lines, including newlines say scalar @lines; # 3 ``` The gotcha: a line read with `<$fh>` keeps its trailing newline; call `chomp` to strip it - Ruby's `line.chomp` is the same verb, and Perl borrowed the name. And `open` returns false on failure rather than raising the way Ruby's `File.open` does, so the `or die "...: $!"` idiom is mandatory; `$!` is the system error message, Perl's equivalent of the message in Ruby's `Errno` exception. ## Modules and packages `require`/`include` becomes `use`. The namespace separator is `::`, which you already know from Ruby constants like `Set::Foo`; Perl uses it everywhere, for both packages and the names inside them. A module `Foo::Bar` lives at `Foo/Bar.pm` on the include path `@INC` (Perl's `$LOAD_PATH`). A module's import list is a selection of names, much like pulling specific methods into scope. ```ruby require "set" nums = [1, 2, 3, 4].sum biggest = [3, 7, 2].max ``` ```perl use v5.42; use List::Util qw(sum0 max first); say sum0(1 .. 10); # 55 say max(3, 7, 2); # 7 say first { $_ % 2 == 0 } (1, 3, 4, 5); # 4 ``` The standard library is large, and much of what you would reach to RubyGems for ships in core or on CPAN, the Perl package archive. `List::Util`, `Scalar::Util`, `POSIX`, `File::Spec`, and `Data::Dumper` are the everyday ones. The gotcha: `use` runs at *compile time*, not at the point in the file where it appears, so the order of `use` lines relative to your code does not matter the way `require` placement can. Importing is opt-in per name: `use List::Util qw(sum0 max)` brings exactly `sum0` and `max` into your namespace, nothing else - there is no monkey-patching of built-in types the way Ruby lets you reopen `Array`. ## Objects Ruby is everything-is-an-object; Perl is pragmatic, and the difference showed up already in [Arrays](#arrays-and-lists) (functions, not methods). But when you *do* want objects, Perl 5.42 has a first-class `class` syntax that will look familiar. Fields are declared with `field`, methods with `method`, and `$self` is available inside every method just as `self` is in Ruby (you do not list it as a parameter). ```ruby class Point attr_accessor :x, :y def initialize(x: 0, y: 0) @x = x @y = y end def to_string "(#{@x}, #{@y})" end def move(dx, dy) @x += dx @y += dy self end end p = Point.new(x: 3, y: 4) puts p.to_string # (3, 4) p.move(1, 1) puts p.to_string # (4, 5) ``` ```perl use v5.42; use feature 'class'; class Point { field $x :param = 0; field $y :param = 0; method to_string { return "($x, $y)" } method move ($dx, $dy) { $x += $dx; $y += $dy; return $self; } } my $p = Point->new(x => 3, y => 4); say $p->to_string; # (3, 4) $p->move(1, 1); say $p->to_string; # (4, 5) ``` The constructor is generated for you, the equivalent of Ruby's `attr_accessor` plus `initialize` collapsed into the field declarations: `:param` marks a field that the auto-built `new` accepts as a named argument, and `= 0` is its default. Inheritance is `:isa`, the counterpart of Ruby's `class Point3D < Point`: ```perl use v5.42; use feature 'class'; class Point3D :isa(Point) { field $z :param = 0; method z { return $z } } my $q = Point3D->new(x => 1, y => 2, z => 3); say $q->to_string, " z=", $q->z; # (1, 2) z=3 ``` The gotcha: the `class` feature is still marked experimental in Perl 5.42, and on `pperl` the "class is experimental" notices print to **stderr** even with `no warnings 'experimental::class'` in scope. They do not touch your program's output, so a script that prints to stdout behaves exactly as shown above; just expect the notices on the error stream. Note also that `field` gives you a `:reader` for read access but no Ruby-style `attr_accessor` setter by default - you write a `method` to mutate, as `move` does above. For the older `bless`-based object model you will meet in existing code, and for roles, inheritance details, and migration, see the [Object-oriented Programming guide](../oop/index). ## Error handling Ruby's `begin`/`rescue`/`ensure` has two counterparts. The classic one wraps code in `eval { }` and inspects `$@` (the caught error) afterward - note that `eval` here is exception *trapping*, not Ruby's `eval` of a string: ```ruby begin risky(-1) rescue => e puts "caught: #{e.message}" end ``` ```perl use v5.42; my $result = eval { risky(-1) }; if ($@) { print "caught: $@"; # caught: negative! } ``` The modern form is `try`/`catch`/`finally`, which reads almost exactly like `begin`/`rescue`/`ensure`: ```perl use v5.42; use feature 'try'; try { risky(-4); } catch ($e) { print "caught: $e"; # caught: negative! } finally { say "done"; # done (runs either way, like ensure) } ``` where the raising side is `die` - Perl's `raise`: ```perl use v5.42; sub risky ($n) { die "negative!\n" if $n < 0; return sqrt $n; } ``` The gotcha: `die` with a string that ends in `\n` produces exactly that message; `die` with a string that does *not* end in a newline gets `" at SCRIPT line N.\n"` appended automatically. End your error strings with `\n` when you want a clean message and omit it when you want the file-and-line trace. The `finally` block, like Ruby's `ensure`, runs whether or not an exception was thrown. Note also the two error variables: `$@` is the last caught exception (Ruby's `rescue => e`), while `$!` is the last *system* error (a failed `open`). Under `pperl`, the `try`/`catch`/`finally` feature prints an "experimental" notice on stderr; stdout is unaffected. ## Idioms A few patterns you will see daily and want in your fingers. Sort a list by a computed key. Ruby has `sort_by` built in; the Perl hand-rolled form is the Schwartzian transform - decorate, sort, undecorate - and is worth recognising on sight: ```perl use v5.42; my @words = qw(apple fig pear); my @by_length = map { $_->[1] } sort { $a->[0] <=> $b->[0] } map { [ length $_, $_ ] } @words; say "@by_length"; # fig pear apple ``` Build a hash from a list, the Ruby `words.to_h { |w| [w, w.length] }` analogue: ```perl use v5.42; my @words = qw(apple pear fig); my %len = map { $_ => length $_ } @words; say "$_ => $len{$_}" for sort keys %len; # apple => 5 # fig => 3 # pear => 4 ``` And `pperl` is a command-line tool in the `awk`/`sed` niche - the role `ruby -e` and `ruby -ne` fill for shell glue. The implicit-loop one-liner is the Perl developer's everyday reflex: ```bash pperl -lane '$s += $F[-1]; END { print $s }' data.txt ``` The gotcha: read the [One-Liners guide](../oneliner/index) for the switches (`-n`, `-p`, `-a`, `-l`, `-e`) and the dozens of recipes built on them; it is one of the highest-leverage things to learn coming from a shell-and-Ruby background. ## Gotchas coming from Ruby The traps, collected for a final scan before you start writing: - **Sigils mark shape, not scope.** `$x`/`@x`/`%x` say single/list/map; they are not Ruby's instance/global/local markers. One element of `@a` is `$a[0]`; one value of `%h` is `$h{k}`. - **Not everything is an object.** `uc "foo"` and `sum @a` are functions, not methods on a string or array. You call methods only on blessed references. - **Truthiness is the big one.** `0` and `""` are **false** in Perl but **true** in Ruby. `if ($count)` is false when `$count` is `0`. Only `0`, `""`, `"0"`, and `undef` are false; `"00"` and `"0.0"` are **true**. - **No symbols.** `:name` has no Perl equivalent; hash keys are plain strings, and `=>` auto-quotes the bareword on its left. - **`.` joins strings, `+` adds numbers.** Never `+` two strings to concatenate; that coerces them to numbers and warns. String repeat is `x`, not `*`. - **Two comparison families.** `==`/`<`/`<=>` for numbers, `eq`/`lt`/ `cmp` for strings. Pick by operand type, not by variable - unlike Ruby's class-dispatched `==`. - **`sort` is string-sorted by default.** Pass `{ $a <=> $b }` for numeric order. - **Context.** `my $x = @a` is the *length*; `my ($x) = @a` is the *first element*. The parentheses are the whole difference. Re-read [Context](#context) when a value comes back the wrong shape. - **`s///` edits in place** and returns a count, unlike Ruby's `gsub`; use the `/r` flag to get a new string instead. - **`open` does not raise.** Use `open ... or die "...: $!"`. - **Blocks are not implicit.** A stored `sub { ... }` is called explicitly with `->( )`; there is no `yield` and no invisible block argument. - **Non-ASCII source needs `use utf8;`.** Under `use v5.42;` a literal non-ASCII byte in your source is a compile error without it. Keep string data ASCII unless you specifically add `use utf8;`. - **The `class` feature prints experimental notices to stderr** on `pperl`. Correct output, noisy error stream.