Blog
May 25, 2016 Marie H.

Go for Perl and Python developers - a quick orientation

Go for Perl and Python developers - a quick orientation

Photo by <a href="https://unsplash.com/@jakubzerdzicki?utm_source=cloudista&utm_medium=referral" target="_blank" rel="noopener">Jakub Żerdzicki</a> on <a href="https://unsplash.com/?utm_source=cloudista&utm_medium=referral" target="_blank" rel="noopener">Unsplash</a>

I started my development career writing a lot of Perl, which means I'm familiar with the "default variable" $_ — the implicit loop variable that Perl uses constantly. It's looked down upon in style guides but it's deeply baked into how Perl works.

I was brushing up on Go for some microservice work and tripped over this:

for _, value := range x {
    total += value
}

That _ is Go's blank identifier — same idea as Perl's $_ in spirit, except it's explicit rather than implicit. You use it wherever the language requires you to receive a value you don't care about. Go enforces no unused variables, so _ is how you tell the compiler "yes, I know this exists, I'm intentionally ignoring it."

A few other things that feel familiar coming from Perl/Python

Multiple return values — Go functions return multiple values natively, no tuple unpacking needed:

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

result, err := divide(10, 3)
if err != nil {
    log.Fatal(err)
}
fmt.Println(result)

Slices feel like Perl arrays / Python lists:

// Perl: my @cities = ('austin', 'houston', 'dallas');
// Python: cities = ['austin', 'houston', 'dallas']
cities := []string{"austin", "houston", "dallas"}

for _, city := range cities {
    fmt.Println(city)
}

Maps feel like Perl hashes / Python dicts:

// Perl: my %counts = (); $counts{$city}++;
// Python: counts = {}; counts[city] = counts.get(city, 0) + 1
counts := map[string]int{}
for _, city := range cities {
    counts[city]++
}

Error handling is explicit, not exception-based — this is the biggest mental shift coming from Python. There are no try/except blocks; every function that can fail returns an error value and you check it inline:

f, err := os.Open("data.txt")
if err != nil {
    log.Fatal(err)  // or handle it gracefully
}
defer f.Close()

It feels verbose at first but it forces you to think about failure paths at the call site rather than somewhere up the stack.

The blank identifier in practice

Back to _ — here are the common places you'll use it:

// Ignoring the index in a range loop
for _, v := range mySlice {
    fmt.Println(v)
}

// Ignoring an error you're sure can't happen (use sparingly)
result, _ := strconv.Atoi("42")

// Satisfying an interface for side effects only
var _ io.Writer = (*MyWriter)(nil)  // compile-time interface check

Go feels like the love child of C, Python, and a strong opinion about simplicity. Once it clicks it's hard to go back.

Tell your friends...