Skip to content

Instantly share code, notes, and snippets.

@pjlsergeant
Created November 21, 2012 15:35
Show Gist options
  • Select an option

  • Save pjlsergeant/4125482 to your computer and use it in GitHub Desktop.

Select an option

Save pjlsergeant/4125482 to your computer and use it in GitHub Desktop.
A mostly incorrect and misleading description of Perl's support of type classes...

Type classes elegantly allow the bringing together of both parametric and ad-hoc polymorphism. Functions with different implementations, different type signatures, but the same name can be both sensibly dispatched to depending on the data they're called on (ad-hoc polymorphism), and these functions can be composed and combined in ways agnostic to the underlying implementations, allowing the creation of functions that use the same implementation on different types (parametric polymorphism).

Type classes allow the definition of pseudo-data-types, defined by the named operations they support. Types can be expressed in terms of their capabilities, rather than in terms of their name - or as in object oriented programming, their ancestry.

While this is fantastically useful for statically typed languages, can type classes add utility to dynamically typed languages? Sadly the size of the set of all dynamically typed languages precludes a thorough examination in 1,000 words, leading to the choice to study how Perl handles the situations type classes help with, and a brief contemplation of how type classes might or might not help.

Operator Overloading

For simplification - and because it accurate reflects the experience of most Perl programmers - we'll pretend Perl's scalars can hold three types: numbers, strings, and references. It's difficult for a user to computationally discriminate between numbers and strings, as Perl goes to great lengths to magically coerce between these types, and this coercian happens as part of operator use. Consider the string concatenation operator .:

"foo" . "bar" # foobar
"foo" .  123  # foo123
 123  .  456  # 123456

All of Perl's operators are already defined for numbers and strings (and references, but we'll come back to this), and you are unable to easily change these behaviours. Numbers are easily coerced in to strings, and strings containing only numbers are easily coerced in to numbers, although coercing a string containing letters in to number throws a warning (but happily resolves to 0).

References in Perl are a little more exciting, and bear a passing resembelence to C's pointers. A reference represents a Perl data structure in memory, and a reference can be of type ARRAY for variable-length lists, HASH for key-value stores, and SCALAR, for references to scalars. References have their own numeric and string coercian rules, but references can also be bless'd with labels, and this is where the fun starts, because references are what gives the programmer user-defined types in Perl.

Perl's object system is often described as being "bolted on", but what's really meant is that Perl's object system is entirely built upon ad-hoc polymorphism... Consider:

# Lucky numbers
my $n1 = 42;
my $n2 = "Fourty Two";

# We can't directly bless native types, so we take references
my $r1 = \$n1;
my $r2 = \$n2;

# We give them user-defined type via `bless`
bless $r1, "Foo";
bless $r2, "Bar";

# And then as long as we avoid using functions that collide with built-in # functions, the following dispatch according to their user-defined type:
myfunc $r1; # Attempts to execute Foo::myfunc( $r1 )
myfunc $r2; # Attempts to execute Bar::myfunc( $r2 )

# Of course, there's precisely zero type safety. Watch in horror as we
# simply swap around the types...
bless $r2, "Foo";
bless $r1, "Bar";

# And as we force dispatch to the wrong type, without nary a word of
# complaint:
Foo::myfunc( $r1 );
Foo::myfunc( $r2 );

Perl programmers have spent a long time trying to convince the world that Perl's OO system is rather more advanced than this, but really it's ad-hoc polymorphism with some added syntactic sugar rolled in to the operator ->, and a scoping mechanism around the keyword package that we won't go in to here.

With no language-mandated type-checking, we can write functions that are parametrically polymorphic simply by assuming input arguments support the interface we're after, in this case a function called count:

sub sum {
    my @items = @_; # all pass-in arguments, save in the array @items
    return reduce { $a += count $b } 0, @items; # fold
}

This will die with a runtime error if the components of @items don't support a count function. We can't catch this at compile time, but we can implement a mechanism for explicitly testing at run time, giving us the ability - potentially - to catch the error programatically.

The simplest thing we can check for is whether or not a given bless'd reference has an implementation for a named function, and for this Perl has a built-in function, which we need to use a little syntactic sugar to access:

$foo->can('count'); # Returns a true or false value

Again, describing this as a "built-in function ... a little syntactic sugar to access" is - while largely true - liable to cause Perl programmers with burning torches and pitchforks showing up at your door, as the documentation for Perl suggests that ->can() is in some way related to Perl's "bolted on" OO systems, rather than a way of searching for ad-hoc implementations of type-sepcific functions.

Type cases however allow us to potentially say that a type has a whole range of implemented functions, without causing us to enumerate them each time, and it would be a nice to be able to similiarly tag Perl's user-types. For this, Perl provides a useful construct called ->isa which we can use.

The documentation suggests that ->isa has something to do with OO inheritance, but in fact all it does is search a deterministically named Perl data structure for a given string, making it a useful avenue for tagging certain user types as belonging to a given class. Consider:

# Mark the user-type 'Foo' as belonging to a ':countable' class. We use
# a string beginning with ':' to avoid conflicts with the OO system.
unshift( @Foo::ISA, ':countable' );

# Check that our datum from the previous example belongs to ':countable',
# as it's of type 'Foo'.
$rs2->isa(':countable'); # Returns a true value

This is useful, but provides no guarantees that a type tagged as :countable does actually implement the methods we expect - instead we have to rely on developer honesty and accuracy at runtime.

Genuine type-classes would allow us to catch user errors more sensibly, and while we can abuse various facets of Perl to provide a facility that looks at first vaguely similar, we struggle to emulate the full power they provide.

Conclusion

The flexibility of Perl's OO system both removes much of the need for type-classes, and allows us to somewhat emulate their function via abuse of the inheritence mechanism - at the cost of a very handy compiler/interpreter checking mechanism. Whereas with Haskell, "if it compiles it's probably correct", with Perl you have less assurance.

The flexibility of Perl and a wealth of historic code abusing that flexibility in various ways will probably defeat any attempt to introduce strong typing in Perl (to say nothing of the two parallel type systems), although as with any vaguely insane idea in Perl, someone will no doubt attempt it and upload their handiwork to CPAN.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment