I was quite annoyed recently when the Business::CreditCard module didn't return values for cardtype() that I expected. Instead of returning 'Discover' or 'Visa', it returns 'Discover card' and 'VISA card', a fact which you will only know after a short look at the source code.

It wasn't long before I put some constants for the Business::CreditCard in a private module so I wouldn't have this problem again. However, I also think this would be a great place for a junction, if only we didn't have to load all of Quantum::Superpositions or wait for Perl6 to get it.

Instead of returning a single scalar string, we return a junction in a scalar which holds a number of different strings:

return any('Discover card', 'Discover'); # Or . . . return any('VISA card', 'VISA'); # Or maybe . . . return any('American Express card', 'American Express', 'AmEx');

When we check equivilence against the string, any of the values returned will cause the expression to be true. This has the advantage of maintaining backwards compatiblity while returning various values that the user might expect.

I'm sure this isn't limited to the credit card module. The point is that we don't have to limit junctions to just being stuck in a truth test--we can pass them around just like any other scalars.

Update: Fixed a typo.

----
I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
-- Schemer

Note: All code is untested, unless otherwise stated

Replies are listed 'Best First'.
Re: Returning a Junction
by diotalevi (Canon) on Sep 03, 2003 at 21:54 UTC

    That'd be improper. It is a perfectly sane thing for the module user to get back the list of useable cards and not for use in a boolean test. If you want to do a boolean test then the user should create the disjunction. Heck, do that right now in p5 if all you want is some equality tests: eqany().

    if ( eqany( 'foo', available_cards() ) ) { ... sub eqany { $_[0] eq $_ && return 1 for $_[1] .. $_[$#_]; 0 }
      Looks like a job for context to me. Return a list of cardnames in a list context and a junction otherwise.

      Your solution isn't transparent to the end user. With a junction, the module simply returns the junction and the end user doesn't have to care--they test equality against a string they expected the module to return and nothing more needs to be done. No current code needs to be changed.

      Anyway, I just thought it was a cool use of a feature that isn't practical to use yet . . .

      ----
      I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
      -- Schemer

      Note: All code is untested, unless otherwise stated

        Ah, see I thought that *your* solution wasn't transparent because it hides the list behind a junction. If the person wants to read the list then s/he would have to query the result's eigenvalues (to borrow from Q::S's function. I don't know the p6ism for that). I regard that as distinctly unfriendly. I'm mulling over whether PDCawley's context idea would be a good idea - in scalar return a junction, in list return the list. Under p5 I'd rather return an array reference but that may just be habit.

Re: Returning a Junction
by belg4mit (Prior) on Sep 03, 2003 at 21:19 UTC
    Another option might be to use Text::Abbrev on the returned value Discover card and see if the "expected value" Discover is in fact a unique "abbreviation".

    --
    I'm not belgian but I play one on TV.

Re: Returning a Junction
by fletcher_the_dog (Friar) on Sep 04, 2003 at 19:00 UTC
    You could make a junction class now using operator overloading:
    use strict; package junction; use overload 'eq'=>\&compare; sub new{ my $class = shift; my $self = {}; %{$self} = map{$_,1} @_; bless $self,$class; } sub compare{ my $self = shift; my $string = shift; return exists $self->{$string}; } package main; my $foo = junction->new("Visa Card","Visa","Visa Check Card"); foreach my $test ("Visa","Vasdfasd","Taco","Visa Check Card") { if ($test eq $foo) { print "$test is a Visa!\n"; } else { print "$test is NOT a Visa\n"; } } __OUTPUT__ Visa is a Visa! Vasdfasd is NOT a Visa Taco is NOT a Visa Visa Check Card is a Visa!

      ++fletcher_the_dog

      Only problem is that, while my example above used the any() style junction, there is also the all() type (i.e., all elements in the junction must match).

      Probably the best way around this is to split into two packages: junction_any and junction_all. The implementation for compare under junction_all would be something like this:

      sub compare { my $self = shift; my $string = shift; foreach my $key (keys %{ $self }) { return 0 if $string ne $key; } return 1; }

      For completeness, you'll want to provide equivilent overloading for the other comparison operators, too.

      ----
      I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
      -- Schemer

      Note: All code is untested, unless otherwise stated

        I don't know a lot about junctions, but it seems to me in order for a string to ever pass an all_junction compare there could only be one key in the junction. i.e. "Visa" only matches "Visa" in a string compare, if there is anything else the compare will fail. An all junction I would think would only work if you were comparing two junctions.