I Contain Multitudes: Returning Multiple Values (in Go, Python, and Perl)

Weekly Challenge 359 Task 1: Digital Root:

You are given a positive integer, $int.

 
Write a function that calculates the additive persistence of a positive integer and also return the digital root.

 

Digital root is the recursive sum of all digits in a number until a single digit is obtained.

 

Additive persistence is the number of times you need to sum the digits to reach a single digit.

This is an easy problem, with not much variety in ways to do it. So I'm going to focus on what is distinctive about it: a function that returns two values. Only some languages support a direct way to do this, but most if not all languages allow it do be done in a variety of ways, with advantages and disadvantages to each.

Direct return of multiple values

In Go, functions can return no values, a single value, or multiple values with these syntaxes[1]:

func Foo(i int) {
func Foo(i int) int {
func Foo(i int) (int, int) {

It is very common to return an error as a second return value.

In Perl, when called in list context[2], a sub can simply return multiple values:

sub foo { return 7, 42 }

In Python, a function always returns a single value. If no values are given to return, it returns None. But since Python has native support for tuples, those are an easy way to return multiple values:

def foo(i: int) -> tuple[int, int]:

Return an aggregate type

In Go, you could return a slice or array, rather than individual values, but only if the values have the same type. And even if they are the same type, if they have different meanings (as with the persistence and root in this exercise), I would avoid doing this.

In Perl, you can return an array reference or hash reference:

return [ $persistence, $root ]
return { 'persistence' => $persistence, 'root' => $root }

though with different meanings as here, I would chose the hash reference.

In Python, a tuple is the natural type to use for this. Again, a list could be used, but with different meanings to the return values I would choose a tuple.

Return a structure

In Go, a struct can be returned by reference (normal for larger structs) or value, as shown here (using a named return value):

type DigitalRootInfo struct {
    persistence int
    digitalRoot int
}
func DigitalRoot(i int) (info DigitalRootInfo) {
    info.persistence = 0
    for i > 9 {
        info.persistence++
        sum := 0
        for _, digit := range strconv.Itoa(i) {
            sum += int(digit - '0')
        }
        i = sum
    }
    info.digitalRoot = i
    return
}

In Python, a structure can be represented multiple ways; a class or dataclass or named tuple (shown here) are common:

from typing import NamedTuple
DigitalRootInfo = NamedTuple('DigitalRootInfo', [
    ('persistence', int),
    ('digital_root', int),
])
def digital_root(i: int) -> DigitalRootInfo:
    persistence = 0
    while i > 9:
        persistence += 1
        i = sum(int(digit) for digit in str(i))
    return DigitalRootInfo(persistence, i)

In Perl, you can return a class instance:

sub digital_root($int) {
    my $persistence = 0;
    # not the recommended way to do this, but cute:
    ++$persistence, $int = eval $int while $int =~ s/\B/+/g;
    return DigitalRoot->new(persistence => $persistence, digital_root => $int);
}

Historically, Perl has not provided much built in support for class attributes. You can use one of the many fine CPAN modules to declare the class:

package DigitalRoot { use Moo; has persistence=>(is=>'ro'); has digital_root=>(is=>'ro') } # or Mouse or Moose instead of Moo
package DigitalRoot { use Mo; has 'persistence'; has 'digital_root' }
package DigitalRoot { use Mom ' persistence digital_root' }
package DigitalRoot { use Venus::Class; attr 'persistence'; attr 'digital_root' }
package DigitalRoot { use Marlin 'persistence','digital_root' }

Or in modern Perl (but still marked experimental, as of 5.42):

use experimental 'class';
class DigitalRoot { field $persistence:param:reader; field $digital_root:param:reader }

Summary

With multiple return values, where they have separate meaning, I would chose to return a structure, in any of these languages. This prevents callers from inverting the order, or, in the case of a Perl hash reference, having a typo'd name silently fail.

As always, comments warmly welcome; see you next time.


  1. Optionally, the return values can be named, allowing setting them by assignment and doing a bare return rather than a return val1, val2. ↩︎

  2. For a sub called in scalar context, the expression being returned is executed in scalar context; in the case of the comma operator in 7, 42, it then returns the second operand rather than a list. Sometimes a sub will check context and return appropriately, like:

    sub foo { return wantarray ? 7, 42 : [ 7, 42 ] } ↩︎





Read more