match now support `any` and multiple patterns
authorwidmogrod <widmogrod@gmail.com>
Wed, 13 Jun 2018 21:08:03 +0000 (23:08 +0200)
committerwidmogrod <widmogrod@gmail.com>
Wed, 13 Jun 2018 21:08:25 +0000 (23:08 +0200)
src/Useful/PatternNotMatchedError.php
src/Useful/match.php
test/Useful/MatchTest.php

index e95860f..fd81420 100644 (file)
@@ -30,4 +30,12 @@ class PatternNotMatchedError extends \Exception
 
         return new self($message);
     }
+
+    public static function tupleMismatch($patternCount, $valueCount)
+    {
+        $message = 'tupleMismatch(%d != %d)';
+        $message = sprintf($message, $patternCount, $valueCount);
+
+        return new self($message);
+    }
 }
index 8bc860c..0d3c918 100644 (file)
@@ -4,7 +4,18 @@ declare(strict_types=1);
 
 namespace Widmogrod\Useful;
 
+use Widmogrod\Monad\Maybe\Just;
+use Widmogrod\Primitive\Listt;
+use function Widmogrod\Functional\concatM;
 use function Widmogrod\Functional\curryN;
+use function Widmogrod\Functional\fromIterable;
+use function Widmogrod\Functional\fromNil;
+use function Widmogrod\Functional\fromValue;
+use function Widmogrod\Functional\zip;
+use function Widmogrod\Monad\Maybe\just;
+use function Widmogrod\Monad\Maybe\nothing;
+
+const any = 'Widmogrod\Useful\PatternAny';
 
 /**
  * match :: #{ Pattern -> (a -> b)} -> a -> b
@@ -23,13 +34,72 @@ function match(array $patterns, $value = null)
         }
 
         foreach ($patterns as $className => $fn) {
-            if ($value instanceof $className) {
-                return $value instanceof PatternMatcher
-                    ? $value->patternMatched($fn)
-                    : $fn($value);
+            $isTuplePattern = is_int($className) && is_array($fn);
+            if ($isTuplePattern) {
+                [$tuple, $fn] = $fn;
+                $result = matchTuple($tuple, $value);
+                if ($result instanceof Just) {
+                    return $fn(...$result->extract());
+                }
+            }
+
+            if (isMatch($value, $className)) {
+                return isAny($className)
+                    ? $fn($value)
+                    : matchApply($value, $fn);
             }
         }
 
         throw PatternNotMatchedError::cannotMatch($value, array_keys($patterns));
     })(...func_get_args());
 }
+
+function isMatch($value, $className): bool
+{
+    if (is_array($value)) {
+        return false;
+    }
+
+    if ($value instanceof $className) {
+        return true;
+    }
+
+    return isAny($className);
+}
+
+function isAny($className)
+{
+    return $className === any;
+}
+
+function matchTuple(array $tuplePattern, array $valueTuple)
+{
+    $patternCount = count($tuplePattern);
+    $valueCount = count($valueTuple);
+
+    if ($valueCount !== $patternCount) {
+        return nothing();
+    }
+
+    $collectArgs = function (): Listt {
+        return fromIterable(func_get_args());
+    };
+
+    $args = fromNil();
+    foreach (zip(fromIterable($tuplePattern), fromIterable($valueTuple)) as [$className, $value]) {
+        if (!isMatch($value, $className)) {
+            return nothing();
+        }
+
+        $args = concatM($args, isAny($className) ? fromValue($value) : matchApply($value, $collectArgs));
+    }
+
+    return just(iterator_to_array($args));
+}
+
+function matchApply($value, callable $fn)
+{
+    return $value instanceof PatternMatcher
+        ? $value->patternMatched($fn)
+        : $fn($value);
+}
index ca4cfa3..ab4bbb3 100644 (file)
@@ -6,8 +6,9 @@ namespace test\Useful;
 
 use Widmogrod\Useful\PatternMatcher;
 use Widmogrod\Useful\PatternNotMatchedError;
-use const Widmogrod\Functional\identity;
 use function Widmogrod\Useful\match;
+use const Widmogrod\Functional\identity;
+use const Widmogrod\Useful\any;
 
 class MatchTest extends \PHPUnit\Framework\TestCase
 {
@@ -41,6 +42,14 @@ class MatchTest extends \PHPUnit\Framework\TestCase
                 '$value' => random_int(-1000, 1000),
                 '$expectedMessage' => 'Cannot match "integer" type. Defined patterns are: "test\Useful\MatchTest", "RandomString"',
             ],
+            'Value not in tuple pattern list' => [
+                '$patterns' => [
+                    [[self::class, \stdClass::class], identity],
+                    [["RandomString"], identity],
+                ],
+                '$value' => [random_int(-1000, 1000)],
+                '$expectedMessage' => 'Cannot match "array" type. Defined patterns are: "0", "1"',
+            ],
         ];
     }
 
@@ -62,6 +71,8 @@ class MatchTest extends \PHPUnit\Framework\TestCase
     public function providePatterns()
     {
         $std = new \stdClass();
+        $e = new \Exception();
+        $m = new MyPatternMatcher(100, 123);
 
         return [
             'single pattern' => [
@@ -71,6 +82,14 @@ class MatchTest extends \PHPUnit\Framework\TestCase
                 '$value' => $std,
                 '$expected' => $std,
             ],
+            'single pattern fallback to any' => [
+                '$patterns' => [
+                    \stdClass::class => identity,
+                    any => identity,
+                ],
+                '$value' => $e,
+                '$expected' => $e,
+            ],
             'many patterns' => [
                 '$patterns' => [
                     \Exception::class => identity,
@@ -80,6 +99,27 @@ class MatchTest extends \PHPUnit\Framework\TestCase
                 '$value' => $std,
                 '$expected' => $std,
             ],
+            'tuple patterns' => [
+                '$patterns' => [
+                    [[\stdClass::class, \stdClass::class], function () {
+                        return func_get_args();
+                    }],
+                ],
+                '$value' => [$std, $std],
+                '$expected' => [$std, $std],
+            ],
+            'tuple fallback to any patterns' => [
+                '$patterns' => [
+                    [[\stdClass::class, \stdClass::class], function () {
+                        return func_get_args();
+                    }],
+                    [[any, any], function () {
+                        return ['any', func_get_args()];
+                    }],
+                ],
+                '$value' => [$std, $m],
+                '$expected' => ['any', [$std, $m]],
+            ],
             'value as a PatternMatcher patterns' => [
                 '$patterns' => [
                     \Exception::class => identity,