Skip to content

Commit

Permalink
Introduce the comparison component (#428)
Browse files Browse the repository at this point in the history
  • Loading branch information
veewee authored Nov 22, 2023
1 parent 5a444c5 commit f106d9e
Show file tree
Hide file tree
Showing 30 changed files with 649 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- [Psl\Channel](./component/channel.md)
- [Psl\Class](./component/class.md)
- [Psl\Collection](./component/collection.md)
- [Psl\Comparison](./component/comparison.md)
- [Psl\DataStructure](./component/data-structure.md)
- [Psl\Dict](./component/dict.md)
- [Psl\Encoding\Base64](./component/encoding-base64.md)
Expand Down
33 changes: 33 additions & 0 deletions docs/component/comparison.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!--
This markdown file was generated using `docs/documenter.php`.
Any edits to it will likely be lost.
-->

[*index](./../README.md)

---

### `Psl\Comparison` Component

#### `Functions`

- [compare](./../../src/Psl/Comparison/compare.php#L19)
- [equal](./../../src/Psl/Comparison/equal.php#L13)
- [greater](./../../src/Psl/Comparison/greater.php#L13)
- [greater_or_equal](./../../src/Psl/Comparison/greater_or_equal.php#L13)
- [less](./../../src/Psl/Comparison/less.php#L13)
- [less_or_equal](./../../src/Psl/Comparison/less_or_equal.php#L13)
- [not_equal](./../../src/Psl/Comparison/not_equal.php#L13)
- [sort](./../../src/Psl/Comparison/sort.php#L17)

#### `Interfaces`

- [Comparable](./../../src/Psl/Comparison/Comparable.php#L12)
- [Equable](./../../src/Psl/Comparison/Equable.php#L10)

#### `Enums`

- [Order](./../../src/Psl/Comparison/Order.php#L7)


2 changes: 1 addition & 1 deletion docs/component/option.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@

#### `Classes`

- [Option](./../../src/Psl/Option/Option.php#L12)
- [Option](./../../src/Psl/Option/Option.php#L16)


1 change: 1 addition & 0 deletions docs/documenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ function get_all_components(): array
'Psl\\Channel',
'Psl\\Class',
'Psl\\Collection',
'Psl\\Comparison',
'Psl\\DataStructure',
'Psl\\Dict',
'Psl\\Encoding\\Base64',
Expand Down
20 changes: 20 additions & 0 deletions src/Psl/Comparison/Comparable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Psl\Comparison;

use Psl\Comparison\Exception\IncomparableException;

/**
* @template T
*/
interface Comparable
{
/**
* @param T $other
*
* @optionallyThrows IncomparableException - In case you want to bail out on specific comparisons.
*/
public function compare(mixed $other): Order;
}
16 changes: 16 additions & 0 deletions src/Psl/Comparison/Equable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Psl\Comparison;

/**
* @template T
*/
interface Equable
{
/**
* @param T $other
*/
public function equals(mixed $other): bool;
}
26 changes: 26 additions & 0 deletions src/Psl/Comparison/Exception/IncomparableException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Psl\Comparison\Exception;

use InvalidArgumentException as InvalidArgumentRootException;
use Psl\Exception\ExceptionInterface;
use Psl\Str;

use function get_debug_type;

class IncomparableException extends InvalidArgumentRootException implements ExceptionInterface
{
public static function fromValues(mixed $a, mixed $b, string $additionalInfo = ''): self
{
return new self(
Str\format(
'Unable to compare "%s" with "%s"%s',
get_debug_type($a),
get_debug_type($b),
$additionalInfo ? ': ' . $additionalInfo : '.',
)
);
}
}
12 changes: 12 additions & 0 deletions src/Psl/Comparison/Order.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Psl\Comparison;

enum Order : int
{
case Less = -1;
case Equal = 0;
case Greater = 1;
}
26 changes: 26 additions & 0 deletions src/Psl/Comparison/compare.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Psl\Comparison;

/**
* @template T
*
* @param T $a
* @param T $b
*
* This function can compare 2 values of a similar type.
* When the type happens to be mixed or never, it will fall back to PHP's internal comparison rules:
*
* @link https://www.php.net/manual/en/language.operators.comparison.php
* @link https://www.php.net/manual/en/types.comparisons.php
*/
function compare(mixed $a, mixed $b): Order
{
if ($a instanceof Comparable) {
return $a->compare($b);
}

return Order::from($a <=> $b);
}
16 changes: 16 additions & 0 deletions src/Psl/Comparison/equal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Psl\Comparison;

/**
* @template T
*
* @param T $a
* @param T $b
*/
function equal(mixed $a, mixed $b): bool
{
return compare($a, $b) === Order::Equal;
}
16 changes: 16 additions & 0 deletions src/Psl/Comparison/greater.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Psl\Comparison;

/**
* @template T
*
* @param T $a
* @param T $b
*/
function greater(mixed $a, mixed $b): bool
{
return compare($a, $b) === Order::Greater;
}
18 changes: 18 additions & 0 deletions src/Psl/Comparison/greater_or_equal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Psl\Comparison;

/**
* @template T
*
* @param T $a
* @param T $b
*/
function greater_or_equal(mixed $a, mixed $b): bool
{
$order = compare($a, $b);

return $order === Order::Equal || $order === Order::Greater;
}
16 changes: 16 additions & 0 deletions src/Psl/Comparison/less.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Psl\Comparison;

/**
* @template T
*
* @param T $a
* @param T $b
*/
function less(mixed $a, mixed $b): bool
{
return compare($a, $b) === Order::Less;
}
18 changes: 18 additions & 0 deletions src/Psl/Comparison/less_or_equal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace Psl\Comparison;

/**
* @template T
*
* @param T $a
* @param T $b
*/
function less_or_equal(mixed $a, mixed $b): bool
{
$order = compare($a, $b);

return $order === Order::Equal || $order === Order::Less;
}
16 changes: 16 additions & 0 deletions src/Psl/Comparison/not_equal.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Psl\Comparison;

/**
* @template T
*
* @param T $a
* @param T $b
*/
function not_equal(mixed $a, mixed $b): bool
{
return compare($a, $b) !== Order::Equal;
}
20 changes: 20 additions & 0 deletions src/Psl/Comparison/sort.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace Psl\Comparison;

/**
* @template T
*
* @param T $a
* @param T $b
*
* This method can be used as a sorter callback function for Comparable items.
*
* Vec\sort($list, Comparable\sort(...))
*/
function sort($a, $b): int
{
return compare($a, $b)->value;
}
12 changes: 12 additions & 0 deletions src/Psl/Internal/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ final class Loader
];

public const FUNCTIONS = [
'Psl\\Comparison\\compare' => 'Psl/Comparison/compare.php',
'Psl\\Comparison\\equal' => 'Psl/Comparison/equal.php',
'Psl\\Comparison\\greater' => 'Psl/Comparison/greater.php',
'Psl\\Comparison\\greater_or_equal' => 'Psl/Comparison/greater_or_equal.php',
'Psl\\Comparison\\less' => 'Psl/Comparison/less.php',
'Psl\\Comparison\\less_or_equal' => 'Psl/Comparison/less_or_equal.php',
'Psl\\Comparison\\not_equal' => 'Psl/Comparison/not_equal.php',
'Psl\\Comparison\\sort' => 'Psl/Comparison/sort.php',
'Psl\\Dict\\associate' => 'Psl/Dict/associate.php',
'Psl\\Dict\\count_values' => 'Psl/Dict/count_values.php',
'Psl\\Dict\\drop' => 'Psl/Dict/drop.php',
Expand Down Expand Up @@ -514,6 +522,8 @@ final class Loader
];

public const INTERFACES = [
'Psl\\Comparison\\Comparable' => 'Psl/Comparison/Comparable.php',
'Psl\\Comparison\\Equable' => 'Psl/Comparison/Equable.php',
'Psl\\DataStructure\\PriorityQueueInterface' => 'Psl/DataStructure/PriorityQueueInterface.php',
'Psl\\DataStructure\\QueueInterface' => 'Psl/DataStructure/QueueInterface.php',
'Psl\\DataStructure\\StackInterface' => 'Psl/DataStructure/StackInterface.php',
Expand Down Expand Up @@ -611,6 +621,7 @@ final class Loader

public const CLASSES = [
'Psl\\Ref' => 'Psl/Ref.php',
'Psl\\Comparison\\Exception\\IncomparableException' => 'Psl/Comparison/Exception/IncomparableException.php',
'Psl\\DataStructure\\PriorityQueue' => 'Psl/DataStructure/PriorityQueue.php',
'Psl\\DataStructure\\Queue' => 'Psl/DataStructure/Queue.php',
'Psl\\DataStructure\\Stack' => 'Psl/DataStructure/Stack.php',
Expand Down Expand Up @@ -805,6 +816,7 @@ final class Loader
];

public const ENUMS = [
'Psl\\Comparison\\Order' => 'Psl/Comparison/Order.php',
'Psl\\Encoding\\Base64\\Variant' => 'Psl/Encoding/Base64/Variant.php',
'Psl\\File\\LockType' => 'Psl/File/LockType.php',
'Psl\\File\\WriteMode' => 'Psl/File/WriteMode.php',
Expand Down
28 changes: 27 additions & 1 deletion src/Psl/Option/Option.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
namespace Psl\Option;

use Closure;
use Psl\Comparison;

/**
* @template T
*
* @implements Comparison\Comparable<Option<T>>
* @implements Comparison\Equable<Option<T>>
*/
final class Option
final class Option implements Comparison\Comparable, Comparison\Equable
{
/**
* @param ?array{T} $option
Expand Down Expand Up @@ -279,4 +283,26 @@ public function mapOrElse(Closure $closure, Closure $default): Option

return some($default());
}

/**
* @param Option<T> $other
*/
public function compare(mixed $other): Comparison\Order
{
$aIsNone = $this->isNone();
$bIsNone = $other->isNone();

return match (true) {
$aIsNone || $bIsNone => Comparison\compare($bIsNone, $aIsNone),
default => Comparison\compare($this->unwrap(), $other->unwrap())
};
}

/**
* @param Option<T> $other
*/
public function equals(mixed $other): bool
{
return Comparison\equal($this, $other);
}
}
Loading

0 comments on commit f106d9e

Please sign in to comment.