- go-for-perl-hackers
- Your editor
- Go vs Perl
- Installing binaries:
- Constructs
- Comments
- Variables
- Environment Variables
- Variable Assignment
- Multiple Variables
- Double vs Single Quotes
- Multiline strings
- Boolean checks (true/false)
- Checking for (un)definedness
- Incrementing and Decrementing Integer
- Division with Integers
- String Concatenation
- String Concatenation (Existing String)
- string to byte array Conversion
- byte array to string Conversion
- Constants
- Lists
- Dumping Data Structures
- File Operations
- Flow Control
- Loops
- Regular Expressions
- Today's Date as YYYY-MM-DD
- Functions
- Running Tests
- Debugging
- Sleep
- Parsing URIs
- Changing URI Query Params
- Command Line Scripts
The primary aim of this cheat sheet is to help Perl programmers get up and running with Go.
I recommend setting up gopls.
cpm install -g App::perlimports
This will install a perlimports
binary in your $PATH
.
go install golang.org/x/tools/cmd/goimports@latest
This will install goimports
into $GOPATH/bin
In this section we'll document some commonly used Perl constructs and try to find their equivalents in Go.
# single line
=pod
Multi line
=cut
// single line (C++-style)
/*
Multi-line (C-Style)
*/
print 'hello, world';
package main
import "fmt"
func main() {
fmt.Print("hello, world")
}
See "Double vs Single Quotes" for more information on why single quotes are not used to quote strings in Go.
printf('We are open %i days per %s', 7, 'week');
package main
import (
"fmt"
)
func main() {
fmt.Printf("We are open %d days per %s", 7, "week")
}
diag 'foo happens';
t.Log("foo happens")
t.Logf("We are open %d days per %s", 7, "week")
$ENV{FOO} = 'bar';
local $ENV{FOO} = 'bar'; # Same as above, but with local scope
print "GOPATH: $ENV{GOPATH}\n";
os.Setenv("FOO", "bar")
fmt.Println("GOPATH: ", os.Getenv("GOPATH"))
my $foo = 'bar';
my $pi = 3.14;
my $no_assignment;
// the following assignments are equivalent
var foo = "bar"
foo := "bar"
var pi float32 = 3.14 // explicit cast as float32
pi := float32(3.14) // explicit cast as float32
pi := 3.14 // implicit cast as float64
pi := "3.14" // implicit cast as string
var noAssignment string // equivalent to: noAssignment := ""
https://go.dev/tour/basics/11:
When you need an integer value you should use int unless you have a specific reason to use a sized or unsigned integer type.
my ($foo, $bar);
var foo, bar int
var nothing []string // create an empty slice
my ($i, $j) = (1, 2);
var i, j int = 1, 2
my $foo = 'bar'; # no variable interpolation
my $bar = "$foo baz"; # allow for variable interpolation
foo := "本" // implicitly cast as a string
foo := '本' // implicitly cast as a rune
Rune is an alias for int32
and represents a Unicode code point.
For example: fmt.Println('a')
prints 97, which is hexadecimal 61. The Unicode
for a
is U+0061
.
To print the Hex value of 'a'
you can also do fmt.Println("%x", 'a')
Go expects only a single letter inside single quotes, so this will yield a compile time error:
fmt.Printf("%s", 'hello')
more than one character in rune literal
See golang.org/ref/spec#Rune_literals and https://go.dev/tour/basics/11
my $long_string = <<'EOF';
my multiline
string
EOF
Use double quotes <<"EOF";
if you need to interpolate variables.
longString := `
my multiline
string
`
my $success = 1; # true
$success = 'foo'; # true
$success = 0; # false
$success; # (undef) false
if ($success) {
print "This succeeded";
}
if ( !$success ) {
print "This failed";
}
var success bool
success = true
success = false
if success == true {
fmt.Println("This succeeded")
}
if success {
fmt.Println("This succeeded")
}
if success == false {
fmt.Println("This failed")
}
if !success {
fmt.Println("This failed")
}
my $foo;
if ( ! defined $foo ) {
...;
}
package main
import "fmt"
func main() {
var myString string
if myString == "" {
fmt.Println("Empty")
}
var mySlice []int
if mySlice == nil {
fmt.Println("nil")
}
if len(mySlice) == 0 {
fmt.Println("empty")
}
}
See https://perldoc.perl.org/perlop.html#Auto-increment-and-Auto-decrement
$i = 0; $j = 0;
print $i++; # prints 0
print ++$j; # prints 1
$i = 0; $j = 0;
print $i--; # prints 0
print --$j; # prints -1
counter := 1
counter++
counter--
my $amount = 18 / 20_000;
package main
import "fmt"
func main() {
amount := float32(18) / float32(20000)
fmt.Println(amount)
}
Note: without the float32
conversion, amount
will be 0.
https://go.dev/play/p/ZIotxD2kQen
my $foo = 'go';
my $bar = 'pher';
my $gopher = "$foo$bar";
$gopher = $foo . $bar;
$gopher = join q{}, $foo, $bar;
$gopher = sprintf '%s%s', $foo, $bar;
package main
import (
"fmt"
"strings"
)
func main() {
var gopher string
gopher = foo + bar
gopher = fmt.Sprintf("%s%s", foo, bar)
}
package main
import (
"fmt"
"strings"
)
func main() {
foo := "go"
bar := "pher"
gopher = strings.Join([]string{"go", "pher"}, "")
fmt.Println(gopher)
}
package main
import (
"bytes"
"fmt"
)
func main() {
var buffer bytes.Buffer
foo := "go"
bar := "pher"
buffer.WriteString(foo)
buffer.WriteString(bar)
fmt.Println(buffer.String())
}
my $foo = 'go';
$foo .= 'pher';
package main
func main() {
foo := "go"
foo += "pher"
}
https://go.dev/play/p/UJqGTHWCbQB
b := []byte("Each day provides its own gifts")
package main
import "fmt"
func main() {
// create the byte array
b := []byte("Each day provides its own gifts")
// convert back to a string by passing the array
contentAsString := string(b)
fmt.Println(contentAsString)
// convert back to a string by passing a slice referencing the storage of b
contentAsString = string(b[:])
fmt.Println(contentAsString)
}
https://go.dev/play/p/DKsQJmS5yuf
use Const::Fast;
const my $hello => 'Hello, world';
// Create an *untyped* string constant
const hello = "Hello, world"
// Create a typed string constant
const hello string = "Hello, World"
Create multiple constants with one const
declaration:
const (
hello = "Hello, world"
goodbye = "Goodbye!"
)
Constants cannot be declared using the := syntax.
my @foo = (1..3);
my $first = $foo[0];
foo := [3]int{1, 2, 3}
first := foo[0]
Note that creating an empty array in Go means that it will be populated by the type's default values:
var bar [5]int // creates an array of [0,0,0,0,0]
my $size = @array;
size := len(array)
use Data::Printer; # exports p()
my %foo = (
X => 1,
Y => 2,
);
$foo{X} = 4;
print $foo{X}; # prints 4
p %foo;
# prints:
# {
# X => 4,
# Y => 2,
# }
delete $foo{X};
https://play.golang.org/p/wyeohYSw-cf
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X) // prints 4
fmt.Printf("%+v\n", v) // prints {X:4 Y:2}
// additional examples
v1 := Vertex{1, 2} // has type Vertex
v2 := Vertex{X: 1} // Y:0 is implicit
v3 := Vertex{} // X:0 and Y:0
v.X = 0
fmt.Printf("1: %d, 2: %d, 3: %d", v1.X, v2.X, v3.X)
}
my @foo = ('foo', 'bar', 'baz');
for my $value ( @foo ) {
print "$value\n";
}
my @foo = ('foo', 'bar', 'baz');
for (@foo) {
print "$_\n";
}
// Print array index for each element
my @foo = ('foo', 'bar', 'baz');
for my $i (0..$#foo) {
print "$i: $foo[$i]\n";
}
package main
import (
"fmt"
)
func main() {
foo := [3]int{1, 2, 3}
for i, v := range foo {
fmt.Printf("index: %v value: %v\n", i, v)
}
}
my @list = split ',', 'a,b,c'
https://play.golang.org/p/RXUdiKhZsjG
package main
import (
"fmt"
"strings"
)
func main() {
mySlice := strings.Split("a,b,c", ",")
fmt.Printf("%v", mySlice)
}
my %hash = ( key_1 => 'foo', key_2 => 'bar', );
for my $key ( keys %hash ) {
printf( "key: %s value: %s\n", $key, $hash{$key} );
}
package main
import (
"fmt"
)
func main() {
myMap := map[string]string{"key1": "foo", "key2": "bar"}
for k := range myMap {
fmt.Printf("key: %s value: %s\n", k, myMap[k])
}
}
my %hash = ( key_1 => 'foo', key_2 => 'bar', );
for my $key, $value ( each %hash ) {
printf( "key: %s value: %s\n", $key, $value );
}
package main
import (
"fmt"
)
func main() {
myMap := map[string]string{"key1": "foo", "key2": "bar"}
for k, v := range myMap {
fmt.Printf("key: %s value: %s\n", k, v)
}
}
my %hash = ( key_1 => 'foo', key_2 => 'bar', );
for my $value ( values %hash ) {
printf( "value: %s\n", $value );
}
package main
import (
"fmt"
)
func main() {
myMap := map[string]string{"key1": "foo", "key2": "bar"}
for _, v := range myMap {
fmt.Printf("value: %s\n", v)
}
}
my %pages = ( home => 'https://metacpan.org' );
if ( exists $foo{home} ) {
...
}
https://stackoverflow.com/a/2050629/406224
package main
import "fmt"
func main() {
pages := make(map[string]string)
pages["home"] = "https://metacpan.org"
if _, ok := pages["home"]; ok {
fmt.Println("ok")
}
}
my %seen;
$seen{sunrise} = 1;
if ( exists $seen{sunrise} ) {
...
}
https://go.dev/play/p/d1RTCE1pmaH
package main
import "fmt"
func main() {
seen := make(map[string]struct{})
seen["sunrise"] = struct{}{}
if _, ok := seen["sunrise"]; ok {
fmt.Println("I have seen the sunrise")
}
}
The motivation here is that if the value has no inherent meaning, it could be
confusing to assign it something which could convey a meaning, like true
or
false
. There may be a small performance gain in terms of memory when using
struct{}
, but it may not be any faster.
https://gist.github.com/davecheney/3be245c92b61e5045f75
my %pages = ( home => 'https://metacpan.org' );
delete $pages{home};
package main
import "fmt"
func main() {
pages := make(map[string]string)
pages["home"] = "https://metacpan.org"
delete(pages, "home")
}
my %pages = ( home => 'https://metacpan.org' );
my @keys = keys %pages;
https://go.dev/play/p/Z8OlHN7Q3GY
package main
import (
"fmt"
)
func main() {
pages := make(map[string]string)
pages["home"] = "https://metacpan.org"
var keys []string
for k := range pages {
keys = append(keys, k)
}
fmt.Printf("%+v", keys)
}
my @array = (0..5);
my @slice = @list[2..4];
array := [6]int{0, 1, 2, 3, 4, 5}
var slice []int = array[2:4]
var myslice []int // create an empty slice of integers
var nothing []string // create an empty slice of strings
Note that arrays in Go have a fixed size, whereas slices are dynamically sized.
Also:
A slice does not store any data, it just describes a section of an underlying array.
Changing the elements of a slice modifies the corresponding elements of its underlying array.
Other slices that share the same underlying array will see those changes.
See https://tour.golang.org/moretypes/8
Note also that slices in Go can use defaults for lower and upper bounds. That
means that for the array of 10 integers var a [10]int
, the following slices
are equivalent:
var a [10]int
a[0:10] // explicit lower to upper bound
a[:10] // use default lower bound (0)
a[0:] // use default upper bound (0)
a[:] // use default upper and lower bounds (0 and 10)
Note that the lower bound is the starting point in the index (ie 0) and the
length of the slice is the upper bound, which is why the entire slice
consists of a[0:10
and not a[0:9]
.
See https://tour.golang.org/moretypes/10
my @array = (0..5);
my @slice = @list[2..4];
push @slice, 11, 12;
Note that in Perl, a slice of an array is also an array, so there's no need to make a distinction between the two.
array := [6]int{0, 1, 2, 3, 4, 5}
var slice []int = array[2:4]
slice = append(slice, 11, 12)
In Go, if you want to add two slices together, you'll need to treat the second
slice as a variadic parameter (...
).
package main
import "fmt"
func main() {
first := []int{1, 2}
second := []int{3, 4}
total := append(first, second...)
fmt.Printf("%+v", total)
}
See https://go.dev/play/p/ylbpv1KDjzE
use strict;
use warnings;
use feature qw( say );
use Data::Printer; # exports p() and np()
my %foo = ( a => 'b' );
p( %foo );
# or
say np( %foo );
package main
import "fmt"
func main() {
var config struct {
user string
pass string
}
config.user = "florence"
config.pass = "machine"
fmt.Printf("%+v", config)
return
}
Or:
package main
import "github.com/sanity-io/litter"
func main() {
var config struct {
user string
pass string
}
config.user = "florence"
config.pass = "machine"
litter.Dump(config)
return
}
Or:
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
data := []string{"one", "two", "three"}
fmt.Println(prettyJSON(data))
}
func prettyJSON(target any) string {
data, err := json.MarshalIndent(target, "", " ")
if err != nil {
log.Fatalf("Cannot create pretty json from %v: %v", target, err)
}
return string(data)
}
https://play.golang.org/p/FbSfHRNoVfP
use Data::Printer; # exports np()
use Path::Tiny qw(path);
my @list = ( 1..3 );
path('/tmp/foo.txt')->spew( np( @list ) );
package main
import (
"log"
"os"
"github.com/sanity-io/litter"
)
func main() {
list := [3]int{1, 2, 3}
err := os.WriteFile("/tmp/foo.txt", []byte(litter.Sdump(list)), 0666)
if err != nil {
log.Fatal(err)
}
}
use Data::Printer; # exports np()
use Path::Tiny qw(path);
my @list = ( 1..3 );
path('/tmp/foo.txt')->append( np( @list ) );
package main
import (
"log"
"os"
"github.com/davecgh/go-spew/spew"
)
func main() {
list := [3]int{1, 2, 3}
file, err := os.OpenFile(
"/tmp/foo.txt",
os.O_APPEND|os.O_CREATE|os.O_WRONLY,
0666,
)
if err != nil {
log.Fatal(err)
}
spew.Fdump(file, list)
file.Close()
}
use Path::Tiny qw( path );
use Try::Tiny qw( catch try );
my $dir = '.my-perl-cache';
try {
path($dir)->mkpath( { chmod => 0644 });
}
catch {
die sprintf "Could not create dir %s because of %s", $dir, $_;
};
package main
import (
"log"
"os"
)
func main() {
cacheDir := ".my-go-cache"
if err := os.MkdirAll(cacheDir, 0644); err != nil {
log.Printf("Cannot create dir %s because %v", cacheDir, err)
}
}
use Path::Tiny qw( path );
my $content = path('path', 'to', 'file')->slurp_utf8;
Note that in this case dat
is []byte
package main
import (
"fmt"
"log"
"os"
)
func main() {
err := os.WriteFile("test.txt", []byte("Hello, Gophers!"), 0666)
if err != nil {
log.Fatal(err)
}
dat, err := os.ReadFile("test.txt")
if err != nil {
log.Fatal(err)
} else {
fmt.Println(string(dat))
}
}
https://go.dev/play/p/3dCR2NJuZOF
In the Perl example, we'll chomp the line to make explicit that newlines need to be handled.
use Path::Tiny qw( path );
my $first_line;
my $fh = path('/path/to/file')->openr_utf8;
while (my $line = <$fh>) {
$first_line = $line;
chomp $first_line;
last;
}
print $first_line, "\n";
scanner.Scan()
helpfully trims newlines for us.
import (
"fmt"
"log"
)
func main() {
file, err := os.Open("/path/to/file")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var firstLine string
for scanner.Scan() {
firstLine = scanner.Text()
break
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
fmt.Println(firstLine)
}
if ( $foo > 1 ) {
print 'bar';
}
if foo > 1 {
fmt.Println("bar")
}
// parens are optional
if foo > 1 {
fmt.Println("bar")
}
if ( $foo > 1 ) {
print 'bar';
}
else {
print 'baz';
}
if foo > 1 {
fmt.Println("bar")
} else {
fmt.Println("baz")
}
if ( $foo > 1 ) {
print 'bar';
}
elsif ( $foo < 10 ) {
print 'baz';
}
if foo > 1 {
fmt.Println("bar")
} else if foo < 10 {
fmt.Println("baz")
}
my $sum;
for ( my $i = 0 ; $i < 10 ; $i++ ) {
$sum += $i;
}
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
my $sum = 0;
my $i = 0;
while ( $i < 10 ) {
$sum += $i++;
}
// The init and post statement in a Go for loop are optional.
sum := 0
i := 0
for i < 10 {
sum += i
i++
}
while (1) {
}
for {
}
Perl
while (1) {
...
next if $foo eq 'bar';
...
}
Go
for {
if foo == "bar" {
continue
}
// Won't get here if continue is called
}
Note that continue
will immediately begin the next iteration of the innermost for
loop.
Perl
while (1) {
...
last if $foo eq 'bar';
...
}
Go
for {
if foo == "bar" {
break
}
// Won't get here if break is called
}
Note that break
will exit the enclosing loop at the point where it is called.
Match a string prefix.
my $str = '5.5.1';
if ( $str =~ m{\A5\.5} ) {
print "ok\n";
}
https://go.dev/play/p/rpACvjzRt-K
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`\A5\.5`)
str := "5.5.1"
if re.MatchString(str) {
fmt.Println("ok")
}
}
https://go.dev/play/p/-NXI8SjMjJQ
package main
import (
"fmt"
"strings"
)
func main() {
str := "5.5.1"
if strings.HasPrefix(str, "5.5") {
fmt.Println("ok")
}
}
use DateTime ();
print DateTime->now->ymd;
https://play.golang.org/p/k0ijssDDOU6
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println(time.Now().Format("2006-01-02"))
}
sub foo {
print 'ok';
}
foo();
package main
import "fmt"
func main() {
foo()
}
func foo() {
fmt.Println("foo")
}
$ perl Makefile.PL
$ make
$ make test
or
$ prove -l t/path/to/test.t
Use the -v
flag for verbose output:
$ prove -lv t/path/to/test.t
$ go test
Use the -v
flag for verbose output:
$ go test -v
If you're using vim-go
, use :GoTest
either from your foo.go
or
foo_test.go
. (Note, you can also use :GoAlternate
to toggle between the
two files.)
To test a subset of functions:
$ go test -run regexp
If you're using vim-go
, move your cursor to the name of the function you'd
like to test. Running :GoTest
here will run the function you're currently
in.
To bypass Go's test caching:
$ go test -count=1
use Carp qw( longmess );
print longmess();
package main
import (
"runtime/debug"
)
func main() {
debug.PrintStack()
}
sleep 60;
package main
import (
"time"
)
func main() {
time.Sleep(60 * time.Second)
}
use Mojo::URL ();
my $url = Mojo::URL->new('https://www.google.com/search?q=schitt%27s+creek');
print $url->query->param('q'); # schitt's creek
import (
"fmt"
"log"
"net/url"
)
func main() {
url, err := url.Parse("https://www.google.com/search?q=schitt%27s+creek")
if err != nil {
log.Fatal(err)
}
q := url.Query()
fmt.Println(q.Get("q")) // schitt's creek
}
package main
import (
"fmt"
"net/url"
)
func main() {
url, _ := url.Parse("https://example.com")
q := url.Query()
q.Set("activity", "dance")
q.Set("type", "flash")
url.RawQuery = q.Encode()
fmt.Println(url)
}
print $ARGV[0], "\n" if $ARGV[0];
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) > 1 {
fmt.Printf("%v\n", os.Args[1])
}
}
exit(0);
import("os")
os.Exit(0)