2
use strict;
use warnings;

package LineSegment;

sub new
{       
        my $class = shift; 
        my ($ax, $ay, $bx, $by) = @_;
        my $self = {"ax"=>$ax,
                    "ay"=>$ay,
                    "bx"=>$bx,
                    "by"=>$by,
                   }; 
        bless ($self, $class);
        return $self;
}
sub getA{
        #Issue on get A
        my $self = shift;
        return ($self->{ax}, $self->{ay});
}
sub getB{
        #Issue on get B
        my $self = @_;
        return ($self->{bx}, $self->{by});
}
sub setA{
        #Can print correct value. Is the return statement where it goes wrong?
        my($self, $ax, $ay) = @_;
        $self->{ax} = $ax if defined($ax);
        $self->{ay} = $ay if defined($ay);
        print "Setting ax: $self->{ax}\n";
        print "Setting ay: $self->{ay}\n";

        return ($self->{ax}, $self->{ay});
}
sub setB{
         #Can print correct value. Is the return statement where it goes wrong?
        my($self, $bx, $by) = @_;
        $self->{bx} = $bx if defined($bx);
        $self->{by} = $by if defined($by);
        return $self->{bx}, $self->{by};
}
1;

I am trying to create a class called LineSegment. ax and ay are a point and so are bx and by. I cannot get getA or getB to return what I want. They only return the second value, which would be ay for getA and by for getB. I want it to return both values (ax, ay) or (bx,by). How do I get it to do this? In my setA and setB methods, the values will print. However, could I be returning them wrong in setA and setB? Or does my problem lie in my getter methods?

Here is my main:

print "Starting Line Segment\n";
use LineSegment;
$line = new LineSegment(10,20,30,40);
$line->setA(15,10);
$a = $line->getA();
print "Point A is: $a\n";

Here is my Point class:

use strict;
use warnings;

#class name 
package Point;

#constructor
sub new
{
        my $class = shift;
        my($x, $y) = @_;
        my $self = {"x"=>$x,
                    "y"=>$y,
                   };
        bless ($self, $class);
        return $self;
}

sub getX{
        my($self) = @_;
        return $self->{x};
}
sub setX{
        my ($self, $x) = @_;
        $self->{x} = $x if defined($x);
        return $self->{x};
}
sub setY{
        my ($self, $y) = @_;
        $self->{y} = $y if defined($y);
        return $self->{y};
}
sub random{
        my $self = shift;
        my $range = 50;
        $self->{x} = int(rand($range));
        $self->{y} = int(rand($range));

        return ($self->{x}, $self->{y});
}
1;

Updated main:

use strict;
use warnings;

    use Point;
    use LineSegment;

    my $line = LineSegment->new(Point->new()->random, Point->new()->random);
    my $pointA = $line->getA;
    my $pointB = $line->getB;
    printf "Point = (%d,%d)\n", $pointA->getX, $pointA->getY;
XXIV
  • 348
  • 1
  • 5
  • 24

3 Answers3

6

As Tanktalus has pointed out, you are returning a list of two values and expecting to be able to treat it as a single Point object. A list in scalar context evaluates to the last element of the list, so you are getting just the Y coordinate

I've written some functioning code below. One thing that may confuse you is the hash slice syntax @{$self}{qw/ _x _y /} = @_ which is the same as

$self->{_x} = $_[0];
$self->{_y} = $_[1];

You should remember to use strict and use warnings at the top of every Perl source file. You should also avoid using $a and $b as they are used internally by Perl. Longer, more descriptive identifiers are better anyway

If I alter your Point.pm so that its constructor takes parameters (I have also fixed your random method) like this

Point.pm

use strict;
use warnings 'all';

package Point;

sub new {
    my $class = shift;

    my $self = { };
    @{$self}{qw/ _x _y /} = @_ if @_;

    bless $self, $class;
}

sub getX{
    my $self = shift;

    return $self->{_x};
}

sub getY{
    my $self = shift;

    return $self->{_y};
}

sub setX {
    my $self = shift;

    $self->{_x} = $_[0] if @_;

    return $self->{_x};
}

sub setY {
    my $self = shift;

    $self->{_y} = $_[0] if @_;

    return $self->{_y};
}

use constant RANGE => 50;

sub random {
    my $self = shift;

    $self->{_x} = int rand RANGE;
    $self->{_y} = int rand RANGE;

    return $self;
}

1;

and write LineSegment.pm like this

LineSegment.pm

use strict;
use warnings 'all';

package LineSegment;

sub new {
    my $class = shift;

    my $self = { };
    @{$self}{qw/ _pA _pB /} = @_ if @_;

    bless $self, $class;
}

sub getA {
    my $self = shift;

    return $self->{_pA};
}

sub getB {
    my $self = shift;

    return $self->{_pB};
}

sub setA {
    my $self = shift;

    $self->{_pA} = $_[0] if @_;

    return $self->{_pA};
}

sub setB {
    my $self = shift;

    $self->{_pB} = $_[0] if @_;

    return $self->{_pB};
}

1;

then I can write a program which does what I think you want like this

main.pl

use strict;
use warnings 'all';

use Point;
use LineSegment;

my $line = new LineSegment(
    Point->new(10, 20),
    Point->new(30, 40),
);

$line->setA( Point->new(15, 10) );

my $point = $line->getA;

printf "Point = (%d,%d)\n",
    $point->getX,
    $point->getY;

output

Point = (15,10)
Borodin
  • 126,100
  • 9
  • 70
  • 144
  • Thanks I wish I could give both you and @Tanktalus the credit for the answer because both of you were very helpful. However, your code seems to be more readable to my beginner knowledge of perl. Thanks – XXIV Feb 16 '17 at 20:20
  • One question, why for setY and setB do you do `= $_[0] if @_;`? Why isn't it `= $_[1] if @_;`? Because y in setY and pb in setB are both assigned to _[1] in the constructor. So why does that change when using the set method? – XXIV Feb 16 '17 at 20:27
  • 1
    @XXIV The `set` methods have the first line `my $self = shift;`. The [shift](http://perldoc.perl.org/functions/shift.html) builtin removes (and returns) the first element of the array that is passed to it, and it uses `@_` by default (if nothing is passed to it). Thus the first line is really `my $self = shift @_`. So the first parameter, `$_[0]` (which is the object), is removed from `@_` at that point (and assigned to `$self`). So what was passed as the second parameter is now fist, thus it then uses `$_[0]`. – zdim Feb 16 '17 at 20:49
  • Ah, ok. Thank you for the explanation. – XXIV Feb 16 '17 at 20:53
  • 1
    @XXIV: It's common practice to `shift` the object or class name off the beginning of the `@_` array in methods, so that the array is left containing only the explicit parameters passed to the call. It makes things a little more like other object-oriented languages. – Borodin Feb 16 '17 at 21:09
  • 1
    @zdim: *"[`shift`] uses `@_` by default (if nothing is passed to it)"* or `@ARGV` if it is called outside a subroutine. – Borodin Feb 16 '17 at 21:37
  • Heh, yes it does. Thank you for correcting that. I still find it hard to write such statements and not leave something out :( – zdim Feb 16 '17 at 21:52
  • @zdim: That's what the rest of us are here for :) – Borodin Feb 16 '17 at 21:54
3
my ($ax, $ay) = $line->getA();

getA() is returning a list of variables, you need to receive it into a list of variables. An array would work as well, but this is probably clearer.

But that's not really what you want. What you want to do is to have a line segment be made up of two Point objects (which you may have to create as well), and each Point object store its own x and y coordinates. And then you can return the points as objects, and query their x and y coordinates, e.g.:

my $a_point = $line->getA();
print "Point A is (", $a_point->getX(), ",", $a_point->getY(), ")";

(You can also have the Point class override stringification, but I suspect that's more than you want to think about just yet.)

Apologies for not catching this the first time, but not only are single-letter variable names poor taste in general, $a and $b are particularly bad in perl because they're reserved for the sort function. So I've renamed it here.


With your update, your Point class is missing the getY method. Your main script becomes:

use strict;
use warnings;
use LineSegment;

print "Starting Line Segment\n";
my $line = new LineSegment(10,20,30,40);
$line->setA(15,10);
my $p = $line->getA();
print "Point A is: (", $p->getX(), ",", $p->getY(), ")\n";

and your LineSegment.pm becomes:

package LineSegment;

use strict;
use warnings;
use Point;

sub new
{
    my $class = shift;

    my @points;
    if (@_ == 4)
    {
        @points = (
                   Point->new($_[0], $_[1]),
                   Point->new($_[2], $_[3]),
                  );
    }
    else
    {
        @points = @_;
    }
    my $self = \@points;
    bless ($self, $class);
    return $self;
}
sub getA{
    #Issue on get A
    my $self = shift;
    return $self->[0];
}
sub getB{
    #Issue on get B
    my $self = shift;
    return $self->[1];
}
sub setA{
    #Can print correct value. Is the return statement where it goes wrong?
    my $self = shift;
    my $point = $_[0];
    if (@_ > 1)
    {
        $point = Point->new(@_);
    }
    $self->[0] = $point;
}

sub setB{
    my $self = shift;
    my $point = $_[0];
    if (@_ > 1)
    {
        $point = Point->new(@_);
    }
    $self->[1] = $point;
}
1;

This may be a bit overkill, but the right answer is to only pass in/around Point objects in your LineSegment, and let the caller create the Point objects instead of massaging them in here. In my experience, this makes the whole thing clearer.

Tanktalus
  • 21,664
  • 5
  • 41
  • 68
  • Maybe make a note about how `$a = $line->getA();` was wrong, and perhaps advise against using `$a` as a var name? – stevieb Feb 16 '17 at 19:51
  • I have added my Point class (because what you are mentioning is exactly what I'm doing). So how are you saying I could implement getA in my LineSegment class? Because in your example, it seems as though you use the getA as an assignment but end up just using getX and getY to show getA's value – XXIV Feb 16 '17 at 19:54
  • `($ax, $ay) = $line->getA();` `print "Point A is: $ax, $ay\n";` – XXIV Feb 16 '17 at 20:07
  • Is what I have done, and it now returns two points. Are you saying this is a problem? Because what I'm trying to do is make a line segment and then see if those two lines cross. Would the way I've made this be an issue? – XXIV Feb 16 '17 at 20:07
  • @Tanktalus thanks a lot for your answer. It helps bring together some of my misconceptions. – XXIV Feb 16 '17 at 20:17
  • How about changing `new LineSegment()` to `LineSegment->new()` ... ? So we don't advertise the indirect notation. Perhaps with a comment, since OP uses it (which is I suppose why you have it). – zdim Feb 16 '17 at 20:55
  • I think that it may be a good idea to add a comment on using an arrayref for data (attributes) instead of the common hashref. One can choose as one wishes, of course, and this is a quiet demonstration of that fact, but it may confuse people who are new to this. – zdim Feb 16 '17 at 21:01
1

You have complete answers by Borodin and Tanktalus showing how to write this class, with other comments. They also emphasize that a segment class should fully utilize a point class.

This is an important point. We encapsulate a certain aspect of our problem in a class. Then we want to use that class for other aspects of the problem, and this is crucial in the object-oriented approach. It usually requires iterations in design and coding, to get those classes right.

This post demonstrates the process by adding a method for the length of a segment, what prompts addition of other methods. I also add a few other pieces to your classes

  • A couple of utility methods are added in the Point class that are helpful for the length method, and that belong there in general. This is typical -- we desire new functionality and realize that the other classes should provide a part (or all) of it.

  • Defaults are added to the constructors. Once new is called the objects should be initialized and ready to go, if possible. Your Point::random method is used for this.

  • A setter and getter are combined in one method, which sets data when called with parameters

Some comments follow the code.

Point.pm

package Point;

use strict;
use warnings;

sub new {
    my $class = shift;
    my $self = { };
    bless $self, $class;           # now we can call methods on $self
    if (@_) {
        @{$self}{qw(_x _y)} = @_;  # initialize via parameters
    } else { 
        $self->random();           # initialize using random()
    }
    return $self;
}

sub x {
    my $self = shift;
    $self->{_x} = $_[0] if $_[0];  # set if parameter was passed
    return $self->{_x};
}

sub y {
    my $self = shift;
    $self->{_y} = $_[0] if $_[0];
    return $self->{_y};
}

sub coords {
    my $self = shift;
    @{$self}{qw(_x _y)} = @_ if @_;
    return $self->{_x}, $self->{_y};
}

sub distance {
    my ($self, $pt) = @_; 
    my ($x1, $y1) = $self->coords();
    my ($x2, $y2) = $pt->coords();
    return sqrt( ($x1 - $x2)**2 + ($y1 - $y2)**2 );
}

sub random {
    my $self = shift;
    my $range = $_[0] // 50;
    $self->{_x} = int rand $range;
    $self->{_y} = int rand $range;
    return $self;
}

1;

The random method takes an optional range, so both $pt->random() and $pt->random(10) set random coordinates for $pt. It has default 50, set using defined-or operator, //. Since it returns the object itself you can chain methods, like

my $pt = Point->new(10, 20);
my @coords = $pt->random()->coords();
print "@coords\n";

or, since new itself also returns the object, even

my @coords = Point->new()->random(10)->coords();

This wouldn't be of much use though as we now don't get the object.

LineSegment.pm

package LineSegment;

use strict;
use warnings;
use Point;

sub new {
    my $class = shift;
    my $self = { };
    bless $self, $class;
    if (@_) { @{$self}{qw(_pA _pB)} = @_ }
    else    { @{$self}{qw(_pA _pB)} = (Point->new, Point->new) }
    return $self;
}

sub pA {
    my $self = shift;
    $self->{_pA} = $_[0] if $_[0];
    return $self->{_pA};
}

sub pB {
    my $self = shift;
    $self->{_pB} = $_[0] if $_[0];
    return $self->{_pB};
}

sub pts {
    my $self = shift;
    @{$self}{qw(_pA _pB)} = @_ if @_;
    return @{$self}{qw(_pA _pB)};
}

sub len {
    my $self = shift;
    return $self->{_pA}->distance($self->{_pB});
}

1;

The default in the constructor calls Point's default constructor for each point, if no arguments were passed to initialize the segment object.

The len() method doesn't need coordinates, since we added distance() method to Point. It is natural and needed in a point class and this is better than having LineSegment compute. Often we need to calculate in the class, of course. Think of mid_point (of a segment), intersection (between two segments), etc.

main.pl

use warnings 'all';
use strict;
use feature 'say';

use Point;
use LineSegment;

my $line = LineSegment->new(
    Point->new(10, 20),
    Point->new(30, 40),
);

my $pt_A = $line->pA( Point->new(15, 10) );
my $pt_B = $line->pB;

printf "Point A = (%d,%d)\n", $pt_A->coords();
printf "Point B = (%d,%d)\n", $pt_B->coords();    
printf "Length of the segment: %.3f\n", $line->len();

my @coords = $pt_A->random(10)->coords();
say "Random point, set on existing object: @coords";

my $segm = LineSegment->new();
my @ends = $segm->pts();

print "Segment created with defaults, ends: ";
printf "(%d,%d) ", $_->coords()  for @ends;
say '';

This prints

Point A = (15,10)
Point B = (30,40)
Length of the segment: 33.541
Random point, set on existing object: 3 8
Segment created with defaults, ends: (34,19) (16,14)

What is notably missing here are checks of various kinds. However, once that becomes important one should probably start looking toward Moose or the similar but much lighter Moo.


A comment on new LineSegment() syntax used in the question.

A constructor in Perl is just a method, but the one that blesses the object into the class (package). The name new is indeed common but that is merely a convention. Thus the "normal" way to call a constructor is like any other method, ClassName->new().

One can use new ClassName, which is called "indirect object notation" (or syntax). However, here is what perlobj itself has to say about it (original emphasis)

Outside of the file handle case, use of this syntax is discouraged as it can confuse the Perl interpreter. See below for more details.

Also see this post and its links, for example. Just use ClassName->new().

Community
  • 1
  • 1
zdim
  • 64,580
  • 5
  • 52
  • 81
  • So for the new set function, if I provide parameters then it sets it, but if I provide no parameters it gets it? I've never thought of doing it that way. Pretty cool. And I was just wondering about the `ClassName->new()` issue and happened to come back to this to see if there was an answer and saw that you had posted this. I'm fiddling around and want to make a call to random when creating the LineSegment. How do I do that? I have `my $line = LineSegment->new(Point->new->random, Point->new->random);` in place of the main given by Borodin. – XXIV Feb 16 '17 at 22:33
  • However, it gives me this error "Can't call method "getX" without a package or object reference at main.pl line 10." So how do I properly call random? – XXIV Feb 16 '17 at 22:33
  • Ok thanks. And you mentioned methods that need calculation and put the distance method into Point. I'm going to see if two LineSegments intersect. Do you suggest that method go into Point or LineSegment? I, at first, thought that it should go into LineSegment since thats where the lines will be made. Is that a mistake? – XXIV Feb 16 '17 at 22:40
  • @XXIV That's the design part -- the intersect method should be in segment, that's good. It would be awkward that a 'point' computes ... intersection between segments. That operation doesn't belong to a point. (The segment's intersection does return a point though :) So your new method in segment takes a segment object, computes intersection, and returns a point, like so `my $segm = LineSegment->new($pt1, $pt2); $segm->intersection($segm2)` where `$pt1, $pt2, $segm2` are defined in code. If they don't touch you can return `undef` (easy to test), for example. – zdim Feb 16 '17 at 22:53
  • @XXIV I added the `random` method, and some further comments right below the `Point` class – zdim Feb 16 '17 at 22:54
  • @XXIV That code above would be `my $pi = $segm->intersection($segm2)` (intersection point returned) – zdim Feb 16 '17 at 22:59
  • @XXIV You can also make it a "_class method_", that would take two segment objects. That's a little different topic, which comes with other points and design decisions/deliberations. – zdim Feb 16 '17 at 23:02
  • Yea my first thought was to make another class with certain functions in it that you could do to line segments. But what you are saying makes more sense. I thought I understood how to make the random after your update, but it's still giving me the same error. I've updated it to: `my $line = LineSegment->new(Point->new()->random(), Point->new()->random());` What am I doing wrong? In my mind, I'm creating a new empty object and then calling random function, therefore assigning the values. – XXIV Feb 16 '17 at 23:07
  • @XXIV Hm ... this is what I just tested: `my $seg = LineSegment->new(Point->new()->random, Point->new->random); say $seg->pA()->x();` and it prints the number. The `say` is like `print` except that it adds the newline. You need to say at the top `use feature 'say';` to have it. I'll add this to the example in `main`. – zdim Feb 16 '17 at 23:24
  • Ok I will check it out once updated. I added my current main that keeps giving me the error in case it was something else that I'm missing. – XXIV Feb 16 '17 at 23:35
  • @XXIV Updated. I test the exact code that I then post (unless there is some silly copy-paste error while posting -- but I checked, seems OK). – zdim Feb 16 '17 at 23:38
  • Okay, I hadn't implemented the coord function yet bc I was waiting to implement it after I got the random to work. But I just went ahead and implemented coord that way I could use your main, and it works perfectly. I really really appreciate all the time you took to help me better understand perl. You've been a tremendous help. – XXIV Feb 16 '17 at 23:48
  • @XXIV I see text in your first comment only now, sorry. Yes, this is (another) common way for setter/getter -- if you pass values it sets it (_and_ returns it), if not it just returns it. It's a little like an overloaded method in C++. As for constructing a `Point` with `random` --- you can have that as a default, if no values are passed in the constructor. It makes sense in principle. Saying `Point->new()` now is uncomfortable (x,y are undef!). Would it work for you? I can add that (_if_ it's good), it would round up this post. – zdim Feb 17 '17 at 00:44
  • If you didn't mind adding it, I'd definitely appreciate it. – XXIV Feb 17 '17 at 00:47
  • Sounds great. Thanks again. – XXIV Feb 17 '17 at 00:54
  • @XXIV I added what I had in mind, and cleaned up around (found a typo in an example, too). I will probably edit a little more in the next couple of days, but this should be worth looking at now. The upshot is that if you want a segment with random ends: `my $segm = LineSegment->new();` – zdim Feb 17 '17 at 06:08
  • Perfect. Thanks. I do like that you don't have to call random that it will automatically be called if no parameters are given. I (just like combining get and set) have never thought about doing that, but it can be very useful. – XXIV Feb 17 '17 at 19:52
  • @XXIV Great :). Let me know if any explanation should be added/improved. (1) As for constructor, you always want it to initialize data in some reasonable way, if at all possible. (In some complex classes it may not be.) What you have here is a very generic way -- read passed arguments, or do something. This is often done when initialization involes a complex process, which is split into its own routines. Note that you first have to `bless` it before being able to call methods. (2) As for set/get, I'd call this is a reasable "default" way, unless there are clear reasons to split them. – zdim Feb 17 '17 at 19:59