|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace thgs\Functional\Typeclass; |
| 4 | + |
| 5 | +use thgs\Functional\Data\Just; |
| 6 | +use thgs\Functional\Data\Maybe; |
| 7 | +use thgs\Functional\Data\Nothing; |
| 8 | + |
| 9 | +use function thgs\Functional\c; |
| 10 | + |
| 11 | +/** |
| 12 | + * Contains type class implementations here |
| 13 | + */ |
| 14 | +class Container |
| 15 | +{ |
| 16 | + /** |
| 17 | + * @var array<string, array{predicate: \Closure, impl: \Closure}> |
| 18 | + */ |
| 19 | + private static array $definitions; |
| 20 | + |
| 21 | + /** |
| 22 | + * @param \Closure(mixed):bool $predicate |
| 23 | + * @param \Closure $implementation |
| 24 | + * |
| 25 | + * @todo Make the container a state monad? |
| 26 | + */ |
| 27 | + public static function register(string $identifier, \Closure $predicate, \Closure $implementation): void |
| 28 | + { |
| 29 | + self :: $definitions [$identifier] [] = ['predicate' => $predicate, 'impl' => $implementation]; |
| 30 | + } |
| 31 | + |
| 32 | + public function get(string $identifier, mixed $target): \Closure |
| 33 | + { |
| 34 | + if (!isset( self :: $definitions [$identifier] )) { |
| 35 | + throw new \Exception("Missing definition for $identifier"); |
| 36 | + } |
| 37 | + |
| 38 | + foreach (self :: $definitions [$identifier] as ['predicate' => $predicate, 'impl' => $impl]) { |
| 39 | + if ($predicate($target)) { |
| 40 | + return $impl; |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + throw new \Exception("No definition for $identifier on given target"); |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +/** |
| 49 | + * Maybe we can still use interfaces like FunctorInstance but can also |
| 50 | + * provide the ability to register more fancy things that otherwise |
| 51 | + * would end up in `Instance` namespace by using the callable |
| 52 | + * predicate? |
| 53 | + * |
| 54 | + * This will work best when we can compile/build, so it changes to |
| 55 | + * static dispatch. |
| 56 | + * |
| 57 | + * The main benefit is that the type declaration does not have |
| 58 | + * to define interfaces OR be wrapped around to be used. |
| 59 | + * |
| 60 | + * example: Register `fmap` for some new X object that some imported |
| 61 | + * lib defines and then call fmap. Otherwise we would have to use |
| 62 | + * the `FunctorAdapter` or the `Wrapper` or implement a new class to |
| 63 | + * basically wrap around the X object and provide `fmap`. |
| 64 | + * |
| 65 | + * @see https://terbium.io/2021/02/traits-typeclasses/ |
| 66 | + */ |
| 67 | +Container::register( |
| 68 | + 'fmap', |
| 69 | + fn ($x) => $x instanceof Maybe, |
| 70 | + /** |
| 71 | + * @template A1 |
| 72 | + * @template B1 |
| 73 | + * @param \Closure(A1):B1 $f |
| 74 | + * @param Maybe<A1> $fa |
| 75 | + * @return Maybe<B1> |
| 76 | + */ |
| 77 | + function (\Closure $f, Maybe $fa): Maybe { |
| 78 | + $value = $fa->getValue(); |
| 79 | + if ($value instanceof Nothing) { |
| 80 | + return $fa; |
| 81 | + } |
| 82 | + |
| 83 | + /** @var Composition<A1,B1> */ |
| 84 | + $c = c ($f); |
| 85 | + |
| 86 | + /** @phpstan-assert B1 $value->getValue() */ |
| 87 | + |
| 88 | + return new Maybe(new Just( $c ($value->getValue()) )); |
| 89 | + } |
| 90 | +); |
0 commit comments