Editors Note, February 14th 2022: This project is now ABANDONED and no longer supported or updated.

I’m proud to announce that a new abstract data type has been added to asherwunk/phabstractic, the Range.

The Range Class

When you use an if statement you oftentimes poll whether a given value is equal to, or lesser/greater than a given value. When I published my PHP Restrictions Predicate object (Primus2/Falcraft) I was interested in being able to abstract away the nature of an if clause. I thought, what if the idea of something coming out as true or false was encapsulated in an object as a ‘first-class citizen’? This was interesting to me because I could then set up if-clauses that checked for certain parameters (filters as I called them in my post) and then use them as swappable and configurable components in an algorithm’s logic.

Think about it in terms of a sorting function. A sorting function takes two items given to it by an internal sorting function and returns which one is first and which one is second. You can plug a sorting function into a sorting algorithm and produce custom list-processing functionality purely based on that sorting function. I transformed the notion of a ‘sorting function’ into an abstract one of a ‘filter’. Plug any filter into anywhere that an algorithm may have a variable if-clause comparing pre-determine data types. Heck, you could build a multi-data type predicate function that you could plug into a generic algorithm which would then allow you to add additional data types without having to rewrite the algorithm. If-clause polymorphism baby!

But I digress. There was a particular set of if-clause functionality that I hoped to reproduce in a more succinct and less abstract sense, and that was inequalities. In mathematics, you get equality statements with less-than, greater-than, equal-to, greater-than-or-equal-to, etc.

What if you could define a range of values, say, the domain of a function perhaps, and then test if a value was in that range? This could prove useful for pre-emptive debugging of function bounds.

Thus, I bring to you the Range class (also on github):

<?php

namespace Phabstractic\Data\Types {

	// ...

	$includes = array( '/Data/Types/Exception/InvalidArgumentException.php' );

	// ...

	use Phabstractic\Data\Types\Exception;

	class Range implements \IteratorAggregate {
		private $max = 0;

		private $min = 0;

		// \IteratorAggregate functions

		/**
		 * Instead of \Iterator we use \IteratorAggregate instead
		 *
		 * This gets the iterator for the range
		 *
		 * @return \ArrayIterator The iterator for the range
		 *
		 */
		public function getIterator() {
			return \ArrayIterator( range( $this->min, $this->max ) );
		}

		public function __construct( $min = 0, $max = 0 ) {
			if ( ! is_int( $max ) || ! is_int( $min ) ) {
				throw new Exception\InvalidArgumentException(
					'Phabstractic\\Data\\Types\\Range->__construct: constructor arguments not integers' );
			}

			if ( $max < $min ) {
				throw new Exception\InvalidArgumentException(
					'Phabstractic\\Data\\Types\\Range->__construct: max and minimum reversed' );
			}

			$this->max = $max;
			$this->min = $min;
		}

		public function setMaximum( $max ) {
			if ( $max < $this->min ) {
				throw new Exception\InvalidArgumentException(
					'Phabstractic\\Data\\Types\\Range->setMaximum: maximum less than determined minimum.' );
			}

			$this->max = $max;
		}

		public function setMinimum( $min ) {
			if ( $min > $this->max ) {
				throw new Exception\InvalidArgumentException(
					'Phabstractic\\Data\\Types\\Range->setMinimum: minimum greater than determined maximum.' );
			}

			$this->min = $min;
		}

		public function getMaximum() {
			return $this->max;
		}

		public function getMinimum() {
			return $this->min;
		}

		public function isInRange( $x, array $include = array() ) {
			$include       = array_change_key_case( $include );
			$leftOperator  = '<';
			$rightOperator = '>';
			if ( $include ) {
				if ( isset( $include['minimum'] ) && $include['minimum'] ) {
					$leftOperator .= '=';
				}

				if ( isset( $include['maximum'] ) && $include['maximum'] ) {
					$rightOperator .= '=';
				}
			}

			return eval( "return ((\$this->min $leftOperator \$x) && (\$this->max $rightOperator \$x));" );
		}

	}

}

Yes, it does use the dreaded eval function, so be very cautious about what kind of information you send it. However, I thought it was more elegant to use the built-in comparison functions, and I think it’s pretty secure given that the only thing it references outside of itself is min and max.

It was either this or write all the comparison logic on top of comparison logic, which I thought was kind of ugly.

Now, you can test if a number is in a given range by simply calling $myRange->isInRange($number, array('maximum'=>true,)) [This example includes the maximum (less than and equal to maximum)]

Conclusion

Predicate objects are an interesting concept, which could have many uses in functional programming, in mathematical polymorphism, in input validation, etc. Ranges are a specific version of a predicate object that tests if any given value is within the maximum and minimum range defined in the object.

This is part of the Phabstractic Library.

photo credit: New Product – Fibonacci Clock Kit via photopin (license)

Asher Wolfstein

Metaverse Resident

About the Author

A metaverse resident, you can find me on Second Life (kadar.talbot) and other online platforms. I write about my digital life, my musings, and my projects as a programmer, webmaster, artist, and game designer. (exist (be wunk) (use rational imagination) (import artist coder maker furry) (conditional (if (eq you asshole) (me (block you))))

View Articles