Can you provide an x/y Point class in other languages?

Update: Thanks for all of the replies. However, I now need to block further replies due to the huge amount of spam this post is getting.

I'm writing a talk for Fosdem entitled "Perl 6 -- A Dynamic Language for Mere Mortals." The talk is about Perl 6 and is aimed at programmers, not just Perl devs. As a result, I'd love to find examples in Java, Python, Ruby, and so on, which are roughly equivalent to the following Perl 6 class.

class Point {
  subset PointLimit of Real where -10 <= * <= 10;
  has PointLimit $.x is rw = 0;
  has PointLimit $.y is rw = 0;
  method Str { "[$.x,$.y]" }
}

That class lets you do something like this:

my $point = Point.new( x => 5, y => 3.0 );
say "$point";                             # [5,3]
$point.y = 17.3;                          # Boom!

In other words, both X and Y are real numbers constrained between -10 and 10, inclusive, and overloads stringification. What follows are two roughly equivalent implementations in Perl 5.

The first is in core Perl 5:

package Point {
    use Carp;
    use overload '""' => \&Str, fallback => 1;
    sub _constraint {
        my ( $class, $num ) = @_;
        unless ( $num >= -10 && $num <= 10 ) {
            croak "'$num' is not a real number between -10 and 10, inclusive";
        }
    }
    sub new {
        my ( $class, $x, $y ) = @_;
        $class->_constraint($_) foreach $x, $y;
        return bless { x => $x, y => $y } => $class;
    }
    sub x {
        my ( $self, $x ) = @_;
        return $self->{x} unless 2 == @_;
        $self->_constraint($x);
        $self->{x} = $x;
    }
    sub y {
        my ( $self, $y ) = @_;
        return $self->{y} unless 2 == @_;
        $self->_constraint($y);
        $self->{y} = $y;
    }
    sub Str {
        my $self = shift;
        return sprintf "[%f,%f]" => $self->x, $self->y;
    }
}

Obviously, Modern Perl developers are going to write that in Moose, but it's still much longer:

package Point {
    use Moose;
    use overload '""' => \&Str, fallback => 1;
    use Moose::Util::TypeConstraints;
    subtype "PointLimit" => as 'Num'
      => where { $_ >= -10 && $_ <= 10 }
      => message { "$_ must be a Num between -10 and 10, inclusive" };

    has [qw/x y/] => (
      is      => 'rw',
      isa     => 'PointLimit',
      default => 0,
    );

    sub Str {
      my $self = shift;
      return sprintf "[%f,%f]" => $self->x, $self->y;
  }
}

I know I have readers who love multiple other languages, so if you could provide canonical examples of the natural way to write that code, I'd love to see it.

25 Comments

maybe a bit exotic, but nevertheless, supercollider language:

https://gist.github.com/anonymous/8315fcab5fd44685fca4

p.s. commenting with code snippets is terrible here

This is how I'd do it in Python 3:

class PointLimit:
    def __init__(self, name):
        self.name =  name
    def __get__(self, point, owner):
        return point.__dict__.get(self.name)
    def __set__(self, point, value):
        if not -10 < value < 10:
            raise ValueError('Value %d is out of range' % value)
        point.__dict__[self.name] = value

class Point:
    x = PointLimit('x');
    y = PointLimit('y');

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return "[%f,%f]" % (self.x, self.y)

point = Point(5, 5)
point.y = 20 # Boom!
print(point)

Hope to see you at Fosdem!

C++ (Can't be bothered to figure out how to format code on this site): http://pastebin.com/BVtvf3yU

PHP 5...

<?php
class Point {
  public $x, $y;

  public function __construct ($x, $y) {
    $this->x = self::validate_number($x);
    $this->y = self::validate_number($y);
  }

  public function __toString() {
    return sprintf('[%d,%d]', $this->x, $this->y);
  }

  public static function validate_number ($n) {
    if (!is_numeric($n))
      throw new Exception("Value '$n' is not numeric.");
    if ($n  10)
      throw new Exception("Value '$n' is too big.");

    return $n;
  }
}

$a = new Point(4, 6);
echo $a, "\n";

Here is one more in JavaScript:

function definePointLimit(point, name) {
    Object.defineProperty(point, name, {
        set: function (value) {
            if (value < -10 || value > 10)
                throw 'Value ' + value + ' is out of range';
            point['_' + name] = value;
        },
        get: function () {
            return point['_' + name];
        }
    });
}

function Point(x, y) {
    definePointLimit(this, 'x');
    definePointLimit(this, 'y');

    this.x = x;
    this.y = y;
}

Point.prototype.toString = function() {
    return '[' + this.x + ',' + this.y + ']';
}

var point = new Point(5, 5);
point.y = 100; // Boom!

It is interesting how Perl 6 example will look, if constraint/validation range can be parametrized:

Point->new($x,$y, { valid_range => [$m, $n] })

ES6 might look like this:

"use strict";
function PointLimit(v) {
  if (typeof v === "number" && v >= -10 && v <= 10) return v
  throw new Error(v + " must be a number between -10 and 10, inclusive")
}

class Point {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
  get x()  { return this._x }
  set x(v) { this._x = PointLimit(v) }
  get y()  { return this._y }
  set y(v) { this._y = PointLimit(v) }
  toString () { return `[${this.x},${this.y}]` }
}

var point = new Point(5, 3.0)
console.log(""+point)
point.y = 17.3

This is one of the examples I like to talk about in my bad examples rant during Intermediate Perl classes. Everyone thinks the Point class is simple, but here you're giving it a default value that it doesn't deserve and you don't have a way to distinguish a truly initialized Point from one that isn't. Why not choose a default point of (-137, 92)? If you wouldn't choose that, why is (0,0) so special? Why would you make a Point class that constrains the points instead of a Point class and then a subclass that constrains the Point?

That is, in real world programming where it matters, the things we do to make things look simple in these sorts of talks disappear. The simple things we do to show a particular feature is merely pedagogical.

For instance, in the Perl 6 example in actual production work, you'll probably want to move away from default error handling. You don't want things to just blow up. You'll add logging, your own domain-specific error messages, and so on.

This is the same reason I hate to see people bash on Java because Hello World is so verbose. You don't write Hello Worlds in Java. It's a disingenuous thing to point out because it doesn't matter for things that matter.

Normally I automatically defer to brian's opinion in matters like these (because [a] he's a lot smarter than me, and [b] he lives in the real world a lot more than me), but I do have to disagree about "people who bash on Java because Hello World is so verbose".

At least some of us who bash on Java because Hello World is so verbose aren't doing so because Hello World in Java is so verbose, or because we think anyone needs to write Hello Worlds in Java. We're bashing on Java because the fact that its Hello World is so verbose is symptomatic of Java's underlying broken premise, which is that you should need to understand 12 different complex ideas before you can write anything in Java, even something as simple as Hello World.

Of course, for developers like brian I really don't mind that, in order to write any program, Java requires them to understand the concepts of classes, public classes, methods, static methods, public access, the special status of public static methods called "main", return types, the special status of void as a return type, parameter lists, arrays, the String class, and the System library. Developers of brian's calibre can easily cope with all that.

But for the average developer encountering Java for the first time, or for the average student encountering programming for the first time through Java, I really do care that Java requires that insanely high degree of understanding before you can truly comprehend a program...be it Hello World or whatever.

I'm not bashing Java because its Hello World is so verbose. I'm bashing Java because its Hello World (and therefore any other Java program) is so complicated, and because Hello World (and therefore any other Java program) requires so much deep conceptual knowledge before you can genuinely understand it.

And, most of all, because very few developers seem ever to achieve that level of deep conceptual knowledge, so you end up with the vast majority of Java code seemingly being developed via copy-paste cargo culting.

At least, that was my experience when I was forced to teach Java as a first language.

That's why I often point out the absurdity of Hello World in Java. Not because I think anyone is ever seriously going to write anything as simple as Hello World in Java, but because the complexity of Hello World in Java encapsulates—in an instantly graspable example—the serious underlying design issues with the language. Issues that prevent most developers from ever truly mastering it. Issues that really do matter.

Thanks for giving me a vain motivation to finally finish designing and write Object::Properties, which I’ll be releasing to CPAN shortly. :-)

package Point {
    use Object::Properties '+x' => \&_check, '+y' => \&_check;
    use overload '""' => \&Str, fallback => 1;
    use Carp ();
sub new { my ( $class, $x, $y ) = @_; $class->NEW( x => $x // 0, y => $y // 0 ); }
sub _check { my ( $self, $value ) = @_; Carp::croak "'$value' is not a real number between -10 and 10, inclusive" if $value < -10 or $value > 10; $value; }
sub Str { my ( $self ) = @_; "[${\$self->x},${\$self->y}]"; } }

Turn on sub signatures in 5.20+, and it becomes really, really nice (for not-Perl 6):

package Point {
    use Object::Properties '+x' => \&_check, '+y' => \&_check;
    use overload '""' => \&Str, fallback => 1;
    use Carp ();
sub new ($class, $x, $y) { $class->NEW( x => $x // 0, y => $y // 0 ) }
sub _check ($self, $value) { Carp::croak "'$value' is not a real number between -10 and 10, inclusive" if $value 10; $value; }
sub Str ($self) { "[${\$self->x},${\$self->y}]" } }

I agree with brian that I wouldn’t add defaults, though. I.e. I’d leave out the new method (which changes the constructor interface, mind) and add an undef check to _check. The upshot would be that you couldn’t instantiate an object of this class without initialising it validly.

Thank you for providing X/Y point class in PHP and JavaScript. Can anyone kindly provide this code in .NET ?

I think Damian makes the mistake most people do when criticizing a language. They conflate the language itself with the stupid libraries and idiotic ecosystem. It's not the syntax and design of the language that makes it so and I've seen some really talented programmers make Java do some amazing things. They just go around all the other shit though.

Consider that a lot of the criticism of Perl is CPAN. It's not what you can do with the language that's to blame for what other people did with it.

But, remember mjd's Why I Like Java.

My C# is a bit rusty, but here it is:

public class Point {
    double x, y;
    public double X { 
        get { return x; } 
        set { x = checkRange(value); } 
    }
    public double Y { 
        get { return y; } 
        set { y = checkRange(value); } 
    }
    double checkRange(double value) {
        if (value  10)
            throw new System.ArgumentException("Value out of range");
        return value;
    }
}

public class Program {
    public static void Main() {
        var point = new Point { X = 5, Y = -5 };
        point.X = 100; // Boom!
    }
}

Anybody thought about Smalltalk ?

This is taken from the existing Point class in Pharo (www.pharo.org).

Object subclass: #Point instanceVariableNames: 'x y' classVariableNames: '' poolDictionaries: '' category: 'Kernel-BasicObjects'

x "Answer the x coordinate." ^x

y "Answer the y coordinate." ^y

So I would do something like :-

| myPoint | myPoint := Point new. myPoint setX: 2 setY: 4.

No 'constraint' method (as far as I can see from a very brief glance) but it would be easy to add.

Hmmmmm. I don't feel that I am making the mistake brian suggests.

Note that I said that Java's problem was the "need to understand 12 different complex ideas before you can write anything in Java". Not that you need to understand 12 different complex libraries or class structures or IDEs (though that's frequently true as well).

Of the 12 complex ideas I listed, only two (String and System) were part of the "stupid libraries and idiotic ecosystem". The other ten were fundamental language constructs: classes, access control, instance methods vs class methods, designated identifiers with special semantics, parameter passing quirks and mandatory return behaviours, the core type system and its edge cases. These are, I would argue, all fundamental features of "the language itself".

Not inherently bad features, mind you. But an extensive set of complex and subtle notions, distinctions, and special cases that form an initial learning cliff that (I believe) most developers fail to ever successfully scale.

That's why Java's Hello World bugs me: because its comparative verbosity is merely the syntactic manifestation of the underlying semantic complexity that must be mastered in order to write or understand any Java program.

Why should I have to understand classes, static typing, static methods (note: a completely different type of "static"), the type system as variously applied to parameters and return values, access control, and the language's automatic calling conventions...before I can write a program that doesn't actually make meaningful use of any of those concepts?

I don't for a moment claim that you can't do great and terrible things in Java. Only that in order to do those great and terrible things, you need to have somehow mastered enough of the inherent complexity of the language design, as well as the much greater ambient complexity of the libraries and ecosystem.

And I think that this huge step function at the very start of the learning curve—which is, ironically, so succinctly illustrated by Java's Hello World—creates an enormous cognitive load that makes the initial jump to competence damn near impossible for the vast majority of ordinary decent developers.

Maybe I'm completely wrong about that. Or maybe I'm just expressing it poorly. But I sincerely believe that Java's problems are fundamentally problems of core design, not merely problems of evolution or ecosystem.

Ruby's attr_reader, attr_accessor create methods, but you can't specify any attribute constrains without going into meta programming or using not-common gem packages like Reindeer (Moose for Ruby)

class Point
  attr_reader :x, :y

  def initialize(x:null, y:null) # Ruby >= 2.1 just (x:, y:)
    _constraint(x); @x = x
    _constraint(y); @y = y
  end

  def x=(num)
    _constraint(num); @x = num
  end

  def y=(num)
    _constraint(num); @y = num
  end

  def to_s
    "[%f,%f]" % [x,y]
  end

  private

  def _constraint(num)
    unless num >= -10 && num <= 10
      raise "'#{num}' is not a real number between -10 and 10, inclusive"
    end
  end
end

http://pleac.sourceforge.net/ has many examples of similar things done in various languages. Having a perl6 version would be helpful for them too.

They didn't do a point class for showing classes, but it does show how to do things in various programming languages.

another resource: http://www.angelfire.com/tx4/cus/shapes/index.html

Has OO code for shapes in around 60 languages.

Here's a very barebones Clojure implementation. Much more could be done to tighten it up. Clojure is the only language I've come across in 20 years that equals or surpasses the expressiveness of Perl (IMHO, of course).

(defrecord Point [x y]
  Object
  (toString [_] (format "[%f, %f]" x y)))

(defn new-point [x y]
  {:pre [(> 10.0 x -10.0) (> 10.0 y -10.0)]}
  (->Point x y))

Note that this also captures the read-only nature of x and y (because by default everything is immutable).

(new-point 8.0 4.3) #point.core.Point {:x 8.0 :y 4.3}
(new-point 11.0 3.2) #Assert failed: (> 10.0 x -10.0)
(str (new-point 3.4 3.4)) #[3.4000, 3.4000]

Ack, sorry, mis-read the example - your original is mutable. That wouldn't ever be done in Clojure, so my impl still stands. Sorry for the confusion.

Old good Lisp:

(defstruct point-2d
  (x 0.0 :type (float -10.0 10.0))
  (y 0.0 :type (float -10.0 10.0)))

(defmethod print-object ((p point-2d) stream)
  (format stream "[~d,~d]" (point-2d-x p) (point-2d-y p)))

(defvar a (make-point-2d :x -3.0 :y 4.0))
(print a)
(setf (point-2d-x a) 15.0)

And now, without cheating:

(defun in-interval-10 (f)
  (and (> f -10.0)
       (> 10.0 f)))

(deftype float-10 ()
  `(and float (satisfies in-interval-10)))

(defstruct point-2d
  (x 0.0 :type float-10)
  (y 0.0 :type float-10))

(defmethod print-object ((p point-2d) stream)
  (format stream "[~d,~d]" (point-2d-x p) (point-2d-y p)))

(defvar a (make-point-2d :x -3.0 :y 4.0))
(print a)
(setf (point-2d-x a) 15.0)

Here is the Gist for Go. https://gist.github.com/gdey/f70ccadd4f9393b46571

It's a bit on the verbose side, but I was trying to make it as close to your example as possible.

If I were writing the Ruby version, I'd write a DSL to encapsulate the logic of my constraints:

class Point
  POINT_RANGE = -10..10

  attr_constrained x: POINT_RANGE, y: POINT_RANGE

  def initialize
    @x = 0
    @y = 0
  end

  def to_s
    "[#{x},#{y}]"
  end

private
  def self.attr_constrained(attrs_and_constraints)
    attrs_and_constraints.each do |attr, constraint|
      attr_reader attr
      define_method(:"#{attr}=") do |value|
        raise "#{value} is not a valid value for #{attr}" \
            unless constraint === range
        instance_variable_set :"@#{attr}", value
      end
    end
  end
end

You could make an argument for using eval instead of define_method so you can set the ivar directly, but I prefer to avoid evaling strings at all costs.

Oops, that should be unless constraint === value, not range. It should probably raise an ArgumentError too, but that's more of a stylistic thing.

That's what I get for writing code in a comment box in the middle of the night...

About Ovid

user-pic Freelance Perl/Testing/Agile consultant and trainer. See http://www.allaroundtheworld.fr/ for our services. If you have a problem with Perl, we will solve it for you. And don't forget to buy my book! http://www.amazon.com/Beginning-Perl-Curtis-Poe/dp/1118013840/