Skip to content

Commit 51cb00a

Browse files
committed
Add draft type class container idea
1 parent d9c7f00 commit 51cb00a

1 file changed

Lines changed: 90 additions & 0 deletions

File tree

src/Typeclass/Container.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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

Comments
 (0)