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.
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:
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...
Here is one more in JavaScript:
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:
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. :-)
Turn on sub signatures in 5.20+, and it becomes really, really nice (for not-Perl 6):
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 ?
brian, your point about the default value is well taken. Thanks.
For the Java comments, I'll leave that to Damian, but I'll add this: I've sat in those Java classes and it's a bloody nightmare with students either trying to juggle tons of new concepts at once, or cargo-culting everything and hoping they pick it up later. And there is a point where verbosity matters. I was working in COBOL on a 150 line procedure that was hard to unravel, but after I cleaned it up, I only got it down to 80 lines, as opposed to 10 lines of Perl I wrote for comparison. I recall thinking how easy it was to figure out the 10 lines of Perl by skimming, but you couldn't do that with the COBOL code. Further, in multiple papers I've read on testing (I gotta get a life), defect rates are often correlated to KLOC. I know that's not causative, but it is indicative. And that's not even getting into the productivity discussions. The trade-offs are often significant and they shouldn't be ignored.
You also wrote:
Generally I agree. However, you seem to imply (pardon me if you did not) that the default error handling isn't what we want, but in reality, that's often all you're going to get (we've both hacked on enough codebases to know that what developers should do and what they actually do are not the same thing). Thank goodness it's so easy in Perl 6.
In my slides I make reference to the myriad ways that Perl 5 developers handle argument validation and I have a brief hint that most of those ways have bugs. This happens all the time. The poor developer writing all of that validation code over and over and over and over and over and over and over and over and after a while they say "screw this" and stop writing it. Or they have a deadline and they stop writing it. Or they have "tests" (for some value of "tests") and they stop writing it. Or they abstract this out and write abominations like MooseX::Params::Validate which is, nonetheless, one of the better parameter validation tools in Perl (my comment isn't to attack Dave Rolsky, but to lament the fact that there's a need for that functionality in Perl 5). Or you can just write a subset, slap it into your method signature and be done with it.
Note this isn't just a failing of most popular dynamic languages (though it's worse): all languages have a contention between the types they provide and the types you need. That extra validation is there to bridge the gap. So while the programmer should spend the time to handle this correctly, in reality they often don't (this is an debate I have with one of the AOP authors: don't rely on programmers to do the right thing if you don't have to). So a subset provides a way to make this easy and therefore more likely to occur. I'd much rather have a strange error message and a runtime failure than no error message and incorrect output. I want my program to fail early.
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:
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)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).
Note that this also captures the read-only nature of x and y (because by default everything is immutable).
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:
And now, without cheating:
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:
You could make an argument for using
eval
instead ofdefine_method
so you can set the ivar directly, but I prefer to avoideval
ing strings at all costs.Oops, that should be
unless constraint === value
, notrange
. It should probably raise anArgumentError
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...