Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Static typing with Interfaces

19 views
Skip to first unread message

Luke Palmer

unread,
Apr 17, 2003, 3:39:53 PM4/17/03
to perl6-l...@perl.org
=head1 Static typing with Interfaces

In Perl 6, the drive to add strict static typing seems like a bad decision,
according to this article:

http://ootips.org/static-typing.html

What they're saying is true, but I don't think the attack on static typing
is well-founded. Rather, it's the specific methods of static typing used
by Java and some parts of C++ that deserve criticism.

This document describes a method of static typing that may be able to provide
the run-time guarantees like those of Java and C++, without affecting too much
the versatility of the language. The type system of Apocalypse 6 is used,
and this just proposes a method of type checking to go along with it.

=head2 Why check at all?

It's obvious why types in general are needed: multimethod dispatch, interfaces
to other typed languages (like C), and all the stuff mentioned in A6. But
is static type checking really useful?

C++ checks types because it needs to. It needs to assure the memory it gets
is actually pointing to an object of the proper size with the proper things
at the proper offsets. Java checks types because it's pedantic. But a
dynamically typed language like Perl doesn't have any reason to.

But it's a good idea from a couple perspectives. It helps catch certain bugs,
it helps out the optimizer, it's good documentation, and it can be useful
in generic programming.

Enough convincing. On to the argument.

=head2 The idea of an Interface (and how Java missed the point)

Early(er) in the childhood of software design, the OO guys came up with
the idea that you could use interchangable object interchangably. So many
a software designer came up with pages of inheritance heirarchies to solve
their problem. And static typing made sure no object that wasn't derived
from the kind expected was actually used. And life was good.

Then entered reuse and maintainence. People started to complain that they wanted
to use I<their> object in someone else's code without deriving from their
stupid class. And thus the idea of an interface was born. "As long as you
assure me, by deriving from my special assuring class, that you can do I<these>
things, I'll use you in my code."

That turned out to be a stupid idea too. Or, rather, a stupid way to manifest
a good idea. Whenever you wanted to make a function that required a new
interface, you required anyone that used it to tell us explicitly, with an
C<implements> declaration, that it's compatible.

In the meantime, C++ nailed it (at compile-time) without even realizing it.
Using the C++ "template" feature, you could say that you wanted a certain
interface without having the client knowing at all that you wanted it. If
their object provided the interface, the code would compile, and otherwise
it wouldn't. Brilliant.

=head2 Where Perl might go

In the world of C++ outside of templates, it still requires explict heirarchy
compatibility, which leads to lots of casts and over-specifications everywhere.
But Perl is devoid of the implementation restrictions of C++, so it needn't be
so strict about heirarchy conformance.

Why not just unify the idea of type and interface for the type checking phase?
That is, if it acts like a Foo, it is a Foo. And it doesn't need to say it
acts like a Foo, the compiler can figure that out. It's just like Perl 5's type
checking, except at compile-time when it's actually useful.

The following generic code:

class HasNodeList {
method nodelist() returns List { ... }
}
sub traverse(HasNodeList $root, Code $code) {
$code.($root);
traverse($_, $code) for $root.nodelist
}

Would Just Work, for whatever class of $root, so long as it has a nodelist()
method that returns some kind of list (or something that I<behaves> like a
list). If $root's class doesn't have such a function, it would be caught
at compile time.

=head2 Fitting with other paradigms

There are, of course, times when this doesn't work so smoothly. Certain
interfaces can't be determined at compile-time. These include both objects
whose methods may dynamically change, and those classes which delegate
to an object of varying type.

These I call "dynamic" types. Their behavior is too complex to be analyzed
at compile-time, so they're just deemed with the "universal interface"
(one that supports everything, with caveats) in the type checking phase.
When run time rolls around, they may be checked on the spot or just run, in
accordance with a pragma.

If an object C<AUTOLOAD>s or specifies itself with an C<is dynamic> trait,
it is considered dynamic. If an object derives from or delegates to
an object of a dynamic class, it is also dynamic. The default polymorphic
scalar is a dynamic type, because it delegates to its stored reference if
it has one.

When a dynamic object is assigned to a statically-typed variable, type checking
just accepts it. At run time, the I<assignment> is checked for type
compatibility, reducing bugs (and increasing efficiency) later on. This
also applies to binding.


Luke

Michael Lazzaro

unread,
Apr 17, 2003, 4:41:06 PM4/17/03
to Luke Palmer, perl6-l...@perl.org

On Thursday, April 17, 2003, at 12:39 PM, Luke Palmer wrote:
> That turned out to be a stupid idea too. Or, rather, a stupid way to
> manifest
> a good idea. Whenever you wanted to make a function that required a
> new
> interface, you required anyone that used it to tell us explicitly,
> with an
> C<implements> declaration, that it's compatible.

Can you explain this further, re: your objection to the Java approach?
Not quite sure I understand.

MikeL

John Williams

unread,
Apr 17, 2003, 5:18:58 PM4/17/03
to Luke Palmer, perl6-l...@perl.org
> =head2 Fitting with other paradigms
>
> There are, of course, times when this doesn't work so smoothly. Certain
> interfaces can't be determined at compile-time. These include both objects
> whose methods may dynamically change, and those classes which delegate
> to an object of varying type.
>
> These I call "dynamic" types. Their behavior is too complex to be analyzed
> at compile-time, so they're just deemed with the "universal interface"
> (one that supports everything, with caveats) in the type checking phase.
> When run time rolls around, they may be checked on the spot or just run, in
> accordance with a pragma.
>
> If an object C<AUTOLOAD>s or specifies itself with an C<is dynamic> trait,
> it is considered dynamic. If an object derives from or delegates to
> an object of a dynamic class, it is also dynamic. The default polymorphic
> scalar is a dynamic type, because it delegates to its stored reference if
> it has one.
>
> When a dynamic object is assigned to a statically-typed variable, type checking
> just accepts it. At run time, the I<assignment> is checked for type
> compatibility, reducing bugs (and increasing efficiency) later on. This
> also applies to binding.

I interpret this section as agreeing that we need some way of specifying
when typechecking should be deferred until runtime. Whether it's called
"dynamic", "untyped", "relaxed", or whatever is not so important.

Besides assignment, we have to also consider method calls, and maybe other
things, such as return values and other parts of method signatures.

So hopefully this would give me a ParkingLot which can only store
Vehicles, but doesn't require me to typecast before trying to call the
.OpenDoor method on things I take out of the ParkingLot.

my @ParkingLot is Array returns (Vehicle is dynamic);

~ John Williams

Buddha Buck

unread,
Apr 17, 2003, 5:29:30 PM4/17/03
to Michael Lazzaro, Luke Palmer, perl6-l...@perl.org
Michael Lazzaro wrote:

I'm not Luke, but based on other things he said....

For one thing, it restricts the ability to create generic algorithms.

Take, for example, a "max" function. Ideally, one would like to write
something like:

sub min($a, $b) {
return $a if $a < $b;
return $b;
}

and have it work for all possible types of $a, $b, as long as $a < $b
made sense. It shouldn't matter if you were dealing with numbers,
strings, EmployeeSalaries, SurrealNumbers, etc. You should be able to
write the min function once and forget about it.

If you wanted to do that in Java, you'd have to declare types
everywhere. You'd use an interface, and get something like:

class {
ICompleteOrdered min(ICompleteOrdered a, ICompleteOrdered b)
{
if ($a.lessthan($b)) {
return $a;
else
return $b;
}
}

And that would even fail to work properly, if a and b were different
base-types incapable of being compared to each other.

Whereas in ML (which has a very strong type inference system), you could
write

fun min(a,b) =
if a<b then a else b ;

and it will work for all a, b that a<b is defined.

With C++ templates, you could do the same thing, except you'd have to
declare the types:

template<T>
T min(T a, T b) {
if (a < b) {
return a;
} else {
return b;
}
}

But in Java, you are forced to define min() for every type you want to use.

>
>
> MikeL
>

Austin Hastings

unread,
Apr 17, 2003, 5:35:36 PM4/17/03
to Luke Palmer, perl6-l...@perl.org

--- Luke Palmer <fibo...@babylonia.flatirons.org> wrote:
> =head1 Static typing with Interfaces
>
> In Perl 6, the drive to add strict static typing seems like a bad
> decision,
> according to this article:
>
> http://ootips.org/static-typing.html

The article is pretty biased, it seems to me, but it does make some
good points.

> What they're saying is true, but I don't think the attack on static
> typing is well-founded. Rather, it's the specific methods of static
> typing used by Java and some parts of C++ that deserve criticism.
>
> This document describes a method of static typing that may be able to
> provide
> the run-time guarantees like those of Java and C++, without affecting
> too much
> the versatility of the language. The type system of Apocalypse 6 is
> used,
> and this just proposes a method of type checking to go along with it.
>
> =head2 Why check at all?
>
> It's obvious why types in general are needed: multimethod dispatch,
> interfaces to other typed languages (like C), and all the stuff
> mentioned in A6. But is static type checking really useful?
>
> C++ checks types because it needs to. It needs to assure the memory
> it gets is actually pointing to an object of the proper size with
> the proper things at the proper offsets. Java checks types because
> it's pedantic. But a dynamically typed language like Perl doesn't
> have any reason to.
>
> But it's a good idea from a couple perspectives. It helps catch
> certain bugs, it helps out the optimizer, it's good documentation,
> and it can be useful in generic programming.

Not to mention, as pointed out in A6, that two diffent objectmethods,
or two different multis, may provide different context to their args.
Not knowing what the context of your call should be is likely to
produce some REALLY slow code.

> Enough convincing. On to the argument.
>
> =head2 The idea of an Interface (and how Java missed the point)
>
> Early(er) in the childhood of software design, the OO guys came up
> with the idea that you could use interchangable object
> interchangably. So many a software designer came up with pages of
> inheritance heirarchies to solve their problem. And static typing
> made sure no object that wasn't derived from the kind expected was
> actually used. And life was good.
>
> Then entered reuse and maintainence. People started to complain that
> they wanted to use I<their> object in someone else's code without
> deriving from their stupid class. And thus the idea of an interface
> was born. "As long as you assure me, by deriving from my special
> assuring class, that you can do I<these> things, I'll use you in my
> code."
>
> That turned out to be a stupid idea too. Or, rather, a stupid way to
> manifest a good idea. Whenever you wanted to make a function that
> required a new interface, you required anyone that used it to tell
> us explicitly, with an C<implements> declaration, that it's
> compatible.

The point here being that having to explicitly modify a class
declaration to retroactively add an C<implements> directive to detail
things you may ALREADY have methods for is pretty lame.

OTOH, declaring C<implements> is a good piece of static typechecking in
itself, especially for those "dusty corner" methods that only get
called under weird circumstances.

>
> In the meantime, C++ nailed it (at compile-time) without even
> realizing it. Using the C++ "template" feature, you could say that
> you wanted a certain interface without having the client knowing at
> all that you wanted it. If their object provided the interface, the
> code would compile, and otherwise it wouldn't. Brilliant.

You have *NO* idea how hearing someone call C++ templates "Brilliant"
makes the hairs on my neck stand up. But I take your point.

>
> =head2 Where Perl might go
>
> In the world of C++ outside of templates, it still requires explict
> heirarchy compatibility, which leads to lots of casts and over-
> specifications everywhere. But Perl is devoid of the implementation
> restrictions of C++, so it needn't be so strict about heirarchy
> conformance.
>
> Why not just unify the idea of type and interface for the type
> checking phase? That is, if it acts like a Foo, it is a Foo. And
> it doesn't need to say it acts like a Foo, the compiler can figure
> that out. It's just like Perl 5's type checking, except at
> compile-time when it's actually useful.
>
> The following generic code:
>
> class HasNodeList {
> method nodelist() returns List { ... }
> }
> sub traverse(HasNodeList $root, Code $code) {
> $code.($root);
> traverse($_, $code) for $root.nodelist
> }
>
> Would Just Work, for whatever class of $root, so long as it has a
> nodelist() method that returns some kind of list (or something that
> I<behaves> like a list). If $root's class doesn't have such a
> function, it would be caught at compile time.

So here you advocate declaring "interface" types to specify the
required bits of external objects, right?

(Obviously when referencing classes that are explicitly enumerated in
your source code you'll have full access to those things.)

>
> =head2 Fitting with other paradigms
>
> There are, of course, times when this doesn't work so smoothly.
> Certain interfaces can't be determined at compile-time. These
> include both objects whose methods may dynamically change, and those
> classes which delegate to an object of varying type.
>
> These I call "dynamic" types. Their behavior is too complex to be
> analyzed at compile-time, so they're just deemed with the "universal
> interface" (one that supports everything, with caveats) in the type
> checking phase. When run time rolls around, they may be checked on
> the spot or just run, in accordance with a pragma.
>
> If an object C<AUTOLOAD>s or specifies itself with an C<is dynamic>
> trait, it is considered dynamic. If an object derives from or
> delegates to an object of a dynamic class, it is also dynamic. The
> default polymorphic scalar is a dynamic type, because it delegates
> to its stored reference if it has one.
>
> When a dynamic object is assigned to a statically-typed variable,
> type checking just accepts it. At run time, the I<assignment> is
> checked for type compatibility, reducing bugs (and increasing
> efficiency) later on. This also applies to binding.

So going back to (was it John's) example:

class Vehicle;
class Car is Vehicle;
class MotorCycle;

class ParkingLot is Array of Vehicle {
method park(Vehicle $v) returns Int {...}
}

my @lot = ParkingLot.new(100);
my $vette = Car.new(type => Corvette);

my $space = @lot.park($vette);

{ my GangMember&CrackHead&Male $crook; $crook.StealRandomCar(@lot); }

{ given $newcomer { @lot.park(.wheels); } }

$vette = @lot[$space];

give $driver {

$CART.UnlockViaRemoteControl();

when Gentleman { $CART.OpenDoor($girlfriend); }
when Redneck { say("Get in, bitch!"); }
}

The problem is that you're potentially losing data again, which means
you're giving up the static typing bits. But if $vette winds up being a
Motorcycle, kaboom.

Perhaps the right balance here is for there to be a collection of
classes associated with each object (including classes). When the
compiler looks up the method or multi being called, it records the
class used to confirm the validity of the invocation (or undef). Then
if the object is associated with the same class as the
method-call-lookup, there's a fast "go ahead". Otherwise, there's a
full-up easter-egg hunt for the method.

This "granular class" idea has a lot to it. More thought required.

=Austin

Austin Hastings

unread,
Apr 17, 2003, 5:42:43 PM4/17/03
to perl6-l...@perl.org

--- Austin Hastings <austin_...@yahoo.com> wrote:
> $vette = @lot[$space];
>
> give $driver {
>
> $CART.UnlockViaRemoteControl();
>
> when Gentleman { $CART.OpenDoor($girlfriend); }
> when Redneck { say("Get in, bitch!"); }
> }

Before Luke catches it:

s/CART/vette/g

Luke Palmer

unread,
Apr 17, 2003, 6:21:55 PM4/17/03
to Austin_...@yahoo.com, perl6-l...@perl.org
> --- Luke Palmer <fibo...@babylonia.flatirons.org> wrote:
> > =head1 Static typing with Interfaces
> >
> > In Perl 6, the drive to add strict static typing seems like a bad
> > decision,
> > according to this article:
> >
> > http://ootips.org/static-typing.html
>
> The article is pretty biased, it seems to me, but it does make some
> good points.

Right. Which is why I'm not advocating to strip Perl of a type
system.

> > In the meantime, C++ nailed it (at compile-time) without even
> > realizing it. Using the C++ "template" feature, you could say that
> > you wanted a certain interface without having the client knowing at
> > all that you wanted it. If their object provided the interface, the
> > code would compile, and otherwise it wouldn't. Brilliant.
>
> You have *NO* idea how hearing someone call C++ templates "Brilliant"
> makes the hairs on my neck stand up. But I take your point.

Lol. :)

But I honestly haven't seen a more powerful system for strongly typed
generic programming yet, even if C++ templates are a
little... well... screwy.

Something makes me excited with fear about any type system that allows
you to compute prime numbers at compile time.

> > The following generic code:
> >
> > class HasNodeList {
> > method nodelist() returns List { ... }
> > }
> > sub traverse(HasNodeList $root, Code $code) {
> > $code.($root);
> > traverse($_, $code) for $root.nodelist
> > }
> >
> > Would Just Work, for whatever class of $root, so long as it has a
> > nodelist() method that returns some kind of list (or something that
> > I<behaves> like a list). If $root's class doesn't have such a
> > function, it would be caught at compile time.
>
> So here you advocate declaring "interface" types to specify the
> required bits of external objects, right?

If you want to. That's how generic code would probably do it.

But you can also write your code for a specific class you have in
mind, and then others can pass you an object that behaves (on the
interface side) just like that class and it'll still work. And if it
doesn't support the interface, _compile_ error. i.e., just like I
said, like Perl 5 but at compile time.

> > When a dynamic object is assigned to a statically-typed variable,
> > type checking just accepts it. At run time, the I<assignment> is
> > checked for type compatibility, reducing bugs (and increasing
> > efficiency) later on. This also applies to binding.
>
> So going back to (was it John's) example:
>
> class Vehicle;
> class Car is Vehicle;
> class MotorCycle;
>
> class ParkingLot is Array of Vehicle {
> method park(Vehicle $v) returns Int {...}
> }
>
> my @lot = ParkingLot.new(100);
> my $vette = Car.new(type => Corvette);
>
> my $space = @lot.park($vette);
>
> { my GangMember&CrackHead&Male $crook; $crook.StealRandomCar(@lot); }
>
> { given $newcomer { @lot.park(.wheels); } }
>
> $vette = @lot[$space];
>
> give $driver {
>
> $CART.UnlockViaRemoteControl();
>
> when Gentleman { $CART.OpenDoor($girlfriend); }
> when Redneck { say("Get in, bitch!"); }
> }
>
> The problem is that you're potentially losing data again, which means
> you're giving up the static typing bits. But if $vette winds up being a
> Motorcycle, kaboom.

That's the way it has to work for untyped variables, in one way or
another. As far as this example, you couldv'e typed $vette and not
lost that information. Then it wouldn't let you put a motorcycle in
that variable.

And when you have a container that you need more specific type
information, that's when you use a cast.

my Corvette $vette = @lot[$space] as Corvette;

(Which might be implicit, based on how assignment works), which would
check at run time that @lot[$space] supports the Corvette interface,
even if it isn't really a Corvette.

I guess the big idea behind this system is that you can use
interchangable types interchangably, and you don't have to say which
types are interchangable---the compiler figures that out.

'Course inheritance still has a place, just not in the type system :)
(That plays with my mind... how can a type system not know about
inheritance:?)

> Perhaps the right balance here is for there to be a collection of
> classes associated with each object (including classes). When the
> compiler looks up the method or multi being called, it records the
> class used to confirm the validity of the invocation (or undef). Then
> if the object is associated with the same class as the
> method-call-lookup, there's a fast "go ahead". Otherwise, there's a
> full-up easter-egg hunt for the method.

Hmm, like method caching. IIRC, Perl 5 already does something of this
nature. Implementation detail---not important.

> This "granular class" idea has a lot to it. More thought required.

Agreed. I'll be seeing how far I can take it when I start thinking of
real problems in this respect.

Luke

Luke Palmer

unread,
Apr 17, 2003, 6:48:21 PM4/17/03
to will...@tni.com, perl6-l...@perl.org
> > When a dynamic object is assigned to a statically-typed variable, type checking
> > just accepts it. At run time, the I<assignment> is checked for type
> > compatibility, reducing bugs (and increasing efficiency) later on. This
> > also applies to binding.
>
> I interpret this section as agreeing that we need some way of specifying
> when typechecking should be deferred until runtime. Whether it's called
> "dynamic", "untyped", "relaxed", or whatever is not so important.

Verdad.

> Besides assignment, we have to also consider method calls, and maybe other
> things, such as return values and other parts of method signatures.
>
> So hopefully this would give me a ParkingLot which can only store
> Vehicles, but doesn't require me to typecast before trying to call the
> .OpenDoor method on things I take out of the ParkingLot.
>
> my @ParkingLot is Array returns (Vehicle is dynamic);

That's not what I had in mind, but it may have promise anyway....

What I was thinking is, for instance:

class Scalar is dynamic {
# stuff with an AUTOLOAD, or delegation, or whatever
};

And then uses of Scalar would be free of type checking, because it's
impossible to check the interface of such a thing.

But your code says "I can call whatever methods I want on these
objects; trust me, I know what I'm doing". I think that kind of
assertion would be helpful.

Although, if what you're wanting is just a way to do dynamic checking
instead of static, a simple scalar would work.

my $car = @ParkingLot[$spot];
$car.OpenDoor();

Because scalar is dynamic, so it is exempt from static checking. So I
don't think your use of C<is dynamic> really buys us anything. It
looked like a good misinterpretation, though.

Luke

John Williams

unread,
Apr 17, 2003, 7:07:56 PM4/17/03
to Luke Palmer, perl6-l...@perl.org
On 17 Apr 2003, Luke Palmer wrote:
> Although, if what you're wanting is just a way to do dynamic checking
> instead of static, a simple scalar would work.
>
> my $car = @ParkingLot[$spot];
> $car.OpenDoor();

Yes, but that's just like saying
$a = my $xxx = $b;
to get runtime typechecking, which didn't get received very well...


~ John Williams

Luke Palmer

unread,
Apr 17, 2003, 7:15:19 PM4/17/03
to will...@tni.com, perl6-l...@perl.org

No, that was run-time _coersion_. We have C<as> (or whatever it's
equivalent would be) for that. I personally liked this idea:

$a = $b as $a;

And that's if both sides are typed. If one side isn't typed, you get
run-time type checking anyway.

$a = my $xxx = $b;

Would likely work, too, it just wouldn't be the cleanest way. I was
deperately trying to find some rule that would make that not work, but
I don't think there is one that is at all desirable.

Oh, I get the gut feeling that

$a = $b as $a;

Should use $a's static type, not its dynamic one. It just seems
better, for some reason that may become clearer later.

The whole conversion/coersion thing is really fuzzy for me (and I
think for most others, too). There's got to be something that is both
strong and sufficiently dynamic hiding somewhere in the quantum foam.

Luke

Chromatic

unread,
Apr 19, 2003, 6:22:06 PM4/19/03
to perl6-l...@perl.org
On Thu, 17 Apr 2003 13:39:53 +0000, Luke Palmer wrote:

> Why not just unify the idea of type and interface for the type checking phase?
> That is, if it acts like a Foo, it is a Foo. And it doesn't need to say it acts
> like a Foo, the compiler can figure that out

See CLass::ActsLike for a Perl 5 implementation. The interface is
unspectacular, but I think it does what you want.

By the way, I think saying "is a" gives the wrong idea. You ought to care only
that what you're getting can handle the methods you're about to call on it, not
how it gets those methods.

-- c

0 new messages