Preamble: In most C-inspired programming languages, comparisons such as 3 < 2 < 1 would produce a slightly unsettling true result. This is because it's evaluated as (3 < 2) < 1, and 3 < 2, being false, numifies to 0. Whilst thinking upon this, a possible solution came to me...

Consider the comparison $x OP $y (where OP is one of (<|>|<=|>=|==|!=)). If $x is undef, or the comparison is false, it should return undef. Otherwise, it should return a true value that is numerically equivalent to $y ($y or "0 but true", as necessary).

This returned value will then become the LHS of the next comparison. If it is undef, that will propogate, giving an overall false result. Otherwise, it will be the correct number, as seen on the immediate LHS of this new comparison in the source code.

I have hacked a module to this effect together in perl, through extensive use of use overload;.

Unfortunately, perl's syntax does not allow you to chain together comparisons, even if they _are_ overloaded, so I have used the bitshift operators in their place. In true domino style, this has resulted in my having to omit the "*-or-equal" operators, as <<= and >>= (1) are right-associative and (2) must have an lvalue (ie not a constant) on their RHS.

Regardless of all that, here's a first draft of that module (beware: dangerously uncommented code follows):

[ UPDATE: taking diotalevi's comments into account ]

package ChainComparable; use strict; use UNIVERSAL (); sub new { my $class = shift; # allow for returning either a list or a scalar return wantarray ? (map { my $i = $_; bless \$i, $class } @_) : (bless \shift, $class); } sub comparison { # whee, currying my $sub = shift; return sub { my ($a, $b, $swap) = @_; $a = $$a; # allow for comparisons between two instances of this class $b = $$b if UNIVERSAL::isa($b, __PACKAGE__); # put the args in the right order ($a, $b) = ($b, $a) if $swap; if (defined $a and $sub->($a, $b)) { if ($b != 0) { return __PACKAGE__->new($b) } else { return __PACKAGE__->new("0 but true") } } else { return __PACKAGE__->new(undef) } }; } use overload ( bool => sub { $$_[0] }, '""' => sub { $$_[0] }, "0+" => sub { $$_[0] }, "==" => comparison(sub { $_[0] == $_[1] }), "!=" => comparison(sub { $_[0] != $_[1] }), "<<" => comparison(sub { $_[0] < $_[1] }), ">>" => comparison(sub { $_[0] > $_[1] }), );

Thoughts?

Replies are listed 'Best First'.
Re: numbers with chainable comparisons
by diotalevi (Canon) on Jan 18, 2006 at 22:43 UTC

    Be wary of map { bless \$_, ... } @_. I'm mostly certain that's going to cause your parameters to ->new() to get modified. Make a copy of the value and bless that instead: map { my $v = $_; bless \ $v, ... } @_

    Aloo beware of ref $b and $b->isa(...). You'll get an error if $b is a reference that isn't also an object. __PACKAGE__ eq ref $b is much safer for this.

    ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

Re: numbers with chainable comparisons
by Anonymous Monk on Jan 18, 2006 at 23:38 UTC
    Overloading is an attempt to rewrite aspects of the language to make them more palatable to the program author.

    I'd rather struggle with clunkier syntax than deal with subtle bugs introduced by an almost-but-not-quite perfect overloading attempt. Ideally, the language we'ld write it would *have* the syntax we wanted in the first place. I'd rather work with the language we know than spent time rewriting it and learning the abstractions, and the limitations of the abstractions in theory, and the bugs in the abstractions in practice. Enough of that ad hoc development work, and you've got your own entirely new language which no one understands, and which doesn't actually work.

    So, while interesting, if I want to write the mathematical expression "x <y <=z" in perl, I'd probably just use the alternative math syntax, "x is bounded by the interval (x,z]", and write:

    $x->is_bounded_by("(",$y,$z,"]" );

    or more likely just the simple and obvious:

    $x < $y and $y <= $z

    which does force you to repeat part of the expression, but has the advantage that a grade school kid knows what the code does.

    Just my $0.02
    --
    Ytrew

      $x < $y and $y <= $z is good but then you'll quickly need to write very differently when instead of $y there will be complex expression with may be side effects.
      ... unfortunately...

      Best regards,
      Courage, the Cowardly Dog

        I would never put a complex expression with side effects inside a comparison anyway. I prefer simple code; if I have to have a complex expression, I'd evaluate it separately, then go on to do the comparison.

        I hate those map-grep-map-fold-spindle-mutilate commands where everything is done in a single, incomprehensible line riddled with nested ternary operators and uncommented regexps. When the code breaks, there's no intermediate stages to check for correctness; there's just a mess of nested context and conditional return values that are supposed to work, but somehow don't.

        Yes, I'm bitter. My boss got fired, and I have to maintain the code he left behind, most of it undocumented, and often attempting to solve problems we don't actually have, and may never encounter. Sometimes code that is probably never hit looks buggy; but it's hard to tell because of all the state and flags flying around.

        It's done nothing to lessen my hatred for complex expressions where simple ones do the job. If you really need complexity, fine. If you don't, just do what's simple and obvious; don't waste someone else's time fixing your mistakes just because you thought it would be fun to be clever. --
        Ytrew

      I accept what you're saying here -- I wouldn't use even the most polished version of this in production code.

      The modules's intent was more to be a proof of concept than anything actually useful. I thought that, if people other than me liked the idea, maybe it could go into something more "official" in the future (should I devise a programming language myself, or I could even've suggested it for perl6 if everyone loved the idea *g*).

Re: numbers with chainable comparisons
by jdporter (Paladin) on Jan 19, 2006 at 14:57 UTC

    Note that this idea was proposed for Perl 6 in RFC 25 and accepted in Apocalypse 3.

    We're building the house of the future together.

      Doh!... On one hand, that's a "yay", 'cos it means that I'm not the only person who likes that idea. On t'other, I don't get to be the guy who thought of it first. Meh. =/

      ...on a more serious note, the RFC suggests some kind of weird list-associative-ish behaviour for comparison ops. My method strikes me as more elegant, although I do like their idea of chained comparisons short-circuiting.

Re: numbers with chainable comparisons
by Roy Johnson (Monsignor) on Jan 19, 2006 at 15:57 UTC
    Here's another approach to changing how expressions are parsed: start with strings and eval them after you've turned them into the Perl equivalent. The dechain sub turns a string into a series of and-ed expressions, which you could then eval.
    my $op = qr/[!=<>]+/; sub dechain { my @terms; while ($_[0] =~ /(.+?)($op)(?=((?:(?!$op).)+))/g) { push @terms, join '', $1, $2, $3; } join ' and ', @terms; } print dechain('3 < 4 < 5'), "\n"; print dechain('$x < $y <= $z'), "\n";

    Caution: Contents may have been coded under pressure.