Prototypes/Parameters to a Function/Sub-Routine

O

O. Olson

Hi,
I would like to know if it is possible to enforce the number and type
of the parameters in a function prototype. Assuming that a person does
not circumvent the prototypes using &.

I have tried the following that tries to say that the function
printScalar () should only accept a single Parameter. This does not
work i.e. does not compile.

# This function would print the time variable passed
sub printScalar(my $num)
{
print "$num";
}


I have looked at perlsub at http://perldoc.perl.org/perlsub.html - and
I am not sure if the answer to my Question is NO.

This would be of more importance when I am working with Objects i.e.
to ensure that the reference passed, is an object of a certain class.

Thanks a lot.
O.O.
 
P

Paul Lalli

I would like to know if it is possible to enforce the number and type
of the parameters in a function prototype. Assuming that a person does
not circumvent the prototypes using &.

That is supposed to be the idea behind subroutine prototypes, yes.
But they don't work correctly.
I have tried the following that tries to say that the function
printScalar () should only accept a single Parameter. This does not
work i.e. does not compile.

# This function would print the time variable passed
sub printScalar(my $num)

What part of perlsub gave you the idea that was valid syntax?
{
print "$num";

perldoc -q quoting
}

I have looked at perlsub athttp://perldoc.perl.org/perlsub.html- and
I am not sure if the answer to my Question is NO.

This would be of more importance when I am working with Objects i.e.
to ensure that the reference passed, is an object of a certain class.

What you are trying to do is:

sub printScalar($) {
my $num = shift;
print $num;
}

Doing that, when someone calls your subroutine with something other
than one scalar value, they will get a syntax error:

$ perl -le'
sub printScalar($) {
my $num = shift;
print $num;
}
printScalar(10, 20);
'
Too many arguments for main::printScalar at -e line 6, near "20)"
Execution of -e aborted due to compilation errors.


However, as I said, prototypes do NOT work correctly. For example,
you would probably expect this to give a similar error, wouldn't you?
$ perl -le'
sub printScalar($) {
my $num = shift;
print $num;
}
my @nums = (10, 20);
printScalar(@nums);
'

But instead, this program executes just fine, and displays the number
"2". Why? Because you told the subroutine to take a scalar
argument. The user passed an array. So Perl "helpfully" took that
array, and evaluated it in scalar context, and an array in scalar
context gives its size.

Similarly, you'd probably expect this to return an error that a list
is required:

$ perl -le'
sub printArray(@) {
my @nums = @_;
print for @nums;
}
printArray();
'

But this program executes just fine. Because an "empty" list is still
a list. We could go on and on. Say you want a subroutine to take two
scalars:
sub printBoth($$);
but if a user calls it like so:
my @mixmax = (5, 10);
printBoth(@minmax);
Perl will throw an error, even though the array does indeed contain
two scalars.


End result, you can't enforce this type of checking at compile time.
You have to do it manually at runtime.

sub printObject {
@_ == 1 or
croak "Invalid number of arguments passed to printObject";
my $obj = shift;
ref $obj or
croak "Argument to printObject is not a reference";
UNIVERSAL::isa($obj, "MyClass") or
croak "Argument to printObject is not a MyClass";
#etc . . .
}

Paul Lalli
 
K

Klaus

Hi,
I would like to know if it is possible to enforce the number and type
of the parameters in a function prototype. Assuming that a person does
not circumvent the prototypes using &.

I have tried the following that tries to say that the function
printScalar () should only accept a single Parameter. This does not
work i.e. does not compile.

# This function would print the time variable passed
sub printScalar(my $num)
{
print "$num";

}

I have looked at perlsub athttp://perldoc.perl.org/perlsub.html- and
I am not sure if the answer to my Question is NO.

Looking at the article "Far More Than Everything You've Ever Wanted to
Know about Prototypes in Perl" by Tom Christiansen:
http://library.n0i.net/programming/perl/articles/fm_prototypes/
I would say the answer to your question is probably no.
This would be of more importance when I am working with Objects i.e.
to ensure that the reference passed, is an object of a certain class.

You can test the package name of an object, see perldoc -f ref
 
M

Michele Dondi

I would like to know if it is possible to enforce the number and type
of the parameters in a function prototype. Assuming that a person does

No. Perl 5 does not have a type system. Perl 6 will, but it will still
be optional.
I have tried the following that tries to say that the function
printScalar () should only accept a single Parameter. This does not
work i.e. does not compile.

# This function would print the time variable passed
sub printScalar(my $num)

In fact that's not a valid syntax. You want

sub printScalar ($) { ... }

instead.
This would be of more importance when I am working with Objects i.e.
to ensure that the reference passed, is an object of a certain class.

You're out of luck! Prototypes do not work at all when subs are called
as methods. Still talking 'bout Perl 5, of course...


Michele
 
M

Michele Dondi

Similarly, you'd probably expect this to return an error that a list
is required:

$ perl -le'
sub printArray(@) {
my @nums = @_;
print for @nums;
}
printArray();
'

But this program executes just fine. Because an "empty" list is still
a list.

In fact this is perfectly fine, if you ask me, and not a good example
of prototypes failing. I'm not really sure but I think that even Perl
6's powerful signatures do not provide means to specify that a list to
be passed must have a specific size, or even more simply that it is
non empty.


Michele
 
P

Paul Lalli

In fact this is perfectly fine, if you ask me, and not a good example
of prototypes failing.

Perhaps not, but I think it is a decent example of prototypes not
doing what someone new to Perl programming would *expect* them to do.

Paul Lalli
 
M

Michele Dondi

Perhaps not, but I think it is a decent example of prototypes not
doing what someone new to Perl programming would *expect* them to do.

I'm thinking of it and I'm fairly sure that it's not the case, and in
particular that as a newbie I wouldn't have expected so. But then it's
probably because we tend to project our own way to see things on the
others too.


Michele
 
A

anno4000

[prototype question, has been answered]
This would be of more importance when I am working with Objects i.e.
to ensure that the reference passed, is an object of a certain class.

It is usually an indication of a design error if a method has to
care about the class an object comes from. If an object *can* invoke
a method (through inheritance, for instance) the method should be able
to handle it.

Anno
 
O

O. Olson

Thanks Paul for your detailed discussion.

What part of perlsub gave you the idea that was valid syntax?

No, I did not mean to say that I got this idea from perlsub - just
that I wanted to do something like this - but this was not available
in Perlsub. Anyway now I got the idea.
UNIVERSAL::isa($obj, "MyClass") or
croak "Argument to printObject is not a MyClass";


I think this is what I wanted i.e. a way to check if an object is of a
certain class.

Thanks again,
O.O.
 
O

O. Olson

No. Perl 5 does not have a type system. Perl 6 will, but it will still
be optional.

Thanks Michele. I think would wait for Perl 6 - Whenever that's going
to come out.
O.O.
 
O

O. Olson

Perhaps you'll find the Params::Util module from CPAN helpful for
checking parameters.
Thanks Steven for pointing this out. I would try this out as soon as I
get the time.
O.O.
 
O

O. Olson

It is usually an indication of a design error if a method has to
care about the class an object comes from. If an object *can* invoke
a method (through inheritance, for instance) the method should be able
to handle it.

Anno


Thanks Anno.
Consider if I have to compare two objects the same class. Something
like:

$obj1->diff($obj2)

Where both $obj1 and $obj2 belong to the same class and the function
diff() calculates the difference between them.

In this case diff() needs to be able to ensure that the object passed
as a parameter is of the same common class to be able to calculate the
difference.

O.O.
 
A

anno4000

O. Olson said:
Thanks Paul for your detailed discussion.



No, I did not mean to say that I got this idea from perlsub - just
that I wanted to do something like this - but this was not available
in Perlsub. Anyway now I got the idea.



I think this is what I wanted i.e. a way to check if an object is of a
certain class.

If you know that $obj *is* an object, that's better written as

$obj->isa( 'MyClass');

Anno
 
A

anno4000

O. Olson said:
Thanks Anno.
Consider if I have to compare two objects the same class. Something
like:

$obj1->diff($obj2)

Where both $obj1 and $obj2 belong to the same class and the function
diff() calculates the difference between them.

In this case diff() needs to be able to ensure that the object passed
as a parameter is of the same common class to be able to calculate the
difference.

Not necessarily. A user could present $obj2 coming from a subclass,
in which case the test may fail, depending how it it done.

In a purely OO design, the diff method would use accessor methods of
the common class to compare values, not rely on a particular structure
of the objects.

Anno
 
O

O. Olson

Not necessarily. A user could present $obj2 coming from a subclass,
in which case the test may fail, depending how it it done.

In a purely OO design, the diff method would use accessor methods of
the common class to compare values, not rely on a particular structure
of the objects.

Anno

Dear Anno,

Thanks for taking interest - But I'm still not entirely clear what
you mean. ( Let's not consider inheritance for a while, because it may
complicate matters - and I'm not using it right now).

Consider for example the following class

-----------------------------------------------------
package MyTime;

sub new {
my $inv = shift;
my $class = ref( $inv ) || $inv;
die "Too many arguments\n" unless @_ <=3;
my $self;

if ( @_ == 0 ) {

my @time = localtime( );
$self = { HOUR => $time[ 2 ],
MINUTE => $time[ 1 ],
SECOND => $time[ 0 ] };
} else {
$self = { HOUR => $_[ 0 ],
MINUTE => $_[ 1 ] || 0,
SECOND => $_[ 2 ] || 0 };
}

return bless $self, $class;
}

sub timeDiff {

my $start_time = shift; # Start Time - First argument
my $stop_time = shift; # Stop Time - Second argument


print "Calculating the Time Difference between: " . $start_time-
as_text . " to " . $stop_time->as_text; #print "\n";

my $start_seconds = $start_time->{HOUR}*3600 + $start_time-
{MINUTE}*60 + $start_time->{SECOND};
my $stop_seconds = $stop_time->{HOUR}*3600 + $stop_time-
{MINUTE}*60 + $stop_time->{SECOND};

my $diff_seconds = $stop_seconds - $start_seconds;

print " = $diff_seconds \n";

return $diff_seconds;

}

-----------------------------------------------------

Assuming timeDiff() is called as:

$time_start-> timeDiff($time_stop)

-----------------------------------------------------


My question is how can I ensure that the second argument passed to
timeDiff() is of The same class MyTime.

Thanks a lot,
O.O.
 
A

anno4000

O. Olson said:
Dear Anno,

Thanks for taking interest - But I'm still not entirely clear what
you mean. ( Let's not consider inheritance for a while, because it may
complicate matters - and I'm not using it right now).

Consider for example the following class

There are ready-made time classes on CPAN that do time calculations.
In a real application I would urge you to use one of those. I'll
assume your implementation of MyTime is an exercise.
-----------------------------------------------------
package MyTime;

sub new {
my $inv = shift;
my $class = ref( $inv ) || $inv;
die "Too many arguments\n" unless @_ <=3;

You mean ">=".
my $self;

if ( @_ == 0 ) {

my @time = localtime( );
$self = { HOUR => $time[ 2 ],
MINUTE => $time[ 1 ],
SECOND => $time[ 0 ] };
} else {
$self = { HOUR => $_[ 0 ],
MINUTE => $_[ 1 ] || 0,
SECOND => $_[ 2 ] || 0 };
}

return bless $self, $class;
}

sub timeDiff {

my $start_time = shift; # Start Time - First argument
my $stop_time = shift; # Stop Time - Second argument


print "Calculating the Time Difference between: " . $start_time-
as_text . " to " . $stop_time->as_text; #print "\n";

my $start_seconds = $start_time->{HOUR}*3600 + $start_time-
{MINUTE}*60 + $start_time->{SECOND};
my $stop_seconds = $stop_time->{HOUR}*3600 + $stop_time-
{MINUTE}*60 + $stop_time->{SECOND};

my $diff_seconds = $stop_seconds - $start_seconds;

print " = $diff_seconds \n";

return $diff_seconds;

}

-----------------------------------------------------

Assuming timeDiff() is called as:

$time_start-> timeDiff($time_stop)

-----------------------------------------------------


My question is how can I ensure that the second argument passed to
timeDiff() is of The same class MyTime.

You could say

die "..." unless $time_stop->isa( 'MyTime);

but I simply wouldn't bother.

If someone calls ->timeDiff with an inappropriate argument, access to
->{ HOUR} will fail and you'll see warnings about using an undefined
value. (Your *are* running with warnings and strict, aren't you?)
That should be enough to detect and correct the error.

On the other hand, you are duplicating the code to transform a
time value to seconds in your timeDiff method. Instead, define
a method (code untested)

sub inSecs {
my $time =shift;
($time->{ HOUR}*60 + $time->{ MINUTE})*60 + $time->{ SECOND};
}

and base timeDiff on that:

sub timeDiff {
my ( $start, $stop) = @_;
$stop->inSecs - $start->inSecs;
}

Besides the better code re-use, this makes sure that a call with a
non-time will fail because the ->inSecs method won't be defined.

Anno
 
O

O. Olson

Dear Anno,

Thank you for your explanations - I think this is what I wanted. If
the have any more doubts, I would post them to another thread as they
would be unrelated to this topic.

Thanks again,
O.O.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,821
Latest member
AleidaSchi

Latest Threads

Top