scalp / scalp
一些Scala有用的类移植到PHP。
Requires
- php: ~7.1
Requires (Dev)
- phpunit/phpunit: ~6.2.4
- symfony/var-dumper: ^3.3
This package is not auto-updated.
Last update: 2024-09-14 02:20:57 UTC
README
Scalp
Scalp
选项
Option
类型表示可选值。它可以是一个 Some
值或者一个 None
。
function divide(int $x, int $y): Option { return $y === 0 ? None() : Some(intdiv($x, $y)); } println(divide(42, 6)); println(divide(42, 0));
Some[int](7) None
Option
可以作为集合使用,可以进行 map
、flatMap
或 filter
操作。
$option = Option(42); $square = function (int $x): int { return $x ** 2; }; println($option->map($square)); $isOdd = function (int $x): bool { return $x % 2 === 1; }; println($option->filter($isOdd)); $squareRoot = function (int $x): Option { return $x >= 0 ? Some(sqrt($x)) : None(); }; println($option->flatMap($squareRoot));
Some[integer](1764) None Some[double](6.4807406984079)
对 Some
执行的计算也可以对 None
执行,而不产生任何副作用。唯一的区别是结果是始终为 None
。
println(None()->map($square)); println(None()->filter($isOdd)); println(None()->flatMap($squareRoot));
None None None
函数柯里化
函数柯里化允许通过分步骤提供参数来将函数用作低阶函数。
use function Scalp\curry; use function Scalp\println; $match = curry('preg_match'); $containsFoo = $match('/foo/'); $containsBar = $match('/bar/'); println($containsFoo('foobar')); // 1 println($containsFoo('foofoo')); // 1 println($containsFoo('barbar')); // 0 println($containsBar('foobar')); // 1 println($containsBar('foofoo')); // 0 println($containsBar('barbar')); // 1
部分函数应用
部分函数应用允许立即应用一些函数参数,而其余参数可以稍后应用。
$isEven = function (int $x): bool { return $x % 2 === 0; }; $filterEven = papply(array_filter, __, $isEven); println(AnyToString( $filterEven([-2, -1, 0, 1, 2]) )); println(AnyToString( $filterEven([11, 13, 17, 19]) ));
Array(-2, 0, 2)
Array()
元组
Tuple
是一个包含不同类型元素的集合。 Pair
是用于创建包含两个元素的 Tuple
的工厂函数。
$singleton = Tuple(42); $pair = Pair('Life', 42); $triple = Tuple('text', 27, false);
Tuple
通过具有名称 _1
、_2
到 _N
的属性来公开其元素。
元组的元素不能被设置。
Scalp\Conversion
AnyToString
将任何类型转换为字符串。在值类型的情况下,查找隐式转换函数,如果未找到,则将其转换为值字符串。在对象类型的情况下,首先检查对象是否实现了 toString
或 __toString
方法,然后查找隐式转换,如果两者都未找到,则返回对象哈希ID。
echo AnyToString(null) . "\n"; echo AnyToString(false) . "\n"; echo AnyToString(36.6) . "\n"; echo AnyToString(printAny(new class { function toString(): string { return 'Hello World!'; }});) . "\n";
null false 36.6 Hello World!
隐式转换
隐式转换是一个将一个类型的值转换为另一个类型的函数。
当前版本不提供对隐式转换的支持。当前版本使用的是 AnyToString
的非常简化的版本。隐式转换应遵循 [TypeA]To[TypeB]
的命名约定。例如,在能够将某些类型的值转换为字符串的转换中,AnyToString
将查找具有名称 [Type]ToString 的函数。
Scalp\PatternMatching
模式匹配是一种检查值与模式相匹配的机制。您可以将其视为高级的 switch 语句。与 switch
和 if
语句不同,模式匹配是一个表达式(它返回值,就像三元运算符 ?:
)。
$result = match($subject) ->case($pattern1, $callableToRunForPattern1) ->case($pattern2, $callableToRunForPattern2) ... ->case($patternN, $callableToRunForPatternN) ->done();
情况模式按声明的顺序进行检查。当更一般的模式在特定模式之前声明时,它总是会落入一般情况。
情况类和类型分解
CaseClass
是一个确保存在 deconstruct
方法的接口。Deconstruct
方法应返回用于构造给定类型实例的参数。您可以使用特质 Deconstruction
来提供解构的能力。它使模式匹配能够比较不可变复杂类型值。
例如,Option
类型作为情况类实现。
abstract class Option implements CaseClass { use Deconstruction; ... } final class Some extends Option { private $value; public function __construct($value) { $this->construct($value); $this->value = $value; } ... }
模式
最基本的模式是 Any
,它可以匹配任何内容。
$res0 = match(42) ->case(Any(), function (): string { return 'Anything'; }) ->done(); // $res0 === 'Anything' $res1 = match(Some(42)) ->case(Any(), function (): string { return 'Anything'; }) ->done(); // $res1 === 'Anything'
Value
模式使用 ===
对原始类型进行常规比较,或使用 ==
对对象进行比较。当使用宽松比较时,对象属性也会使用 ==
进行比较(见第3个示例)。
$res2 = match(42) ->case(Value(13), function (): string { return 'Number 13'; }) ->case(Value('42'), function (): string { return 'String "42"'; }) ->case(Value(42), function (): string { return 'Number 42'; }) ->case(Any(), function (): string { return 'Fallback'; }) ->done(); // $res2 === 'Number 42' $res3 = match(Some(42)) ->case(Value(Some(13)), function (): string { return 'Some 13'; }) ->case(Value(Some(42)), function (): string { return 'Some 42'; }) ->case(Any(), function (): string { return 'Fallback'; }) ->done(); // $res3 === 'Some 42' $res4 = match(Some(42)) ->case(Value(Some('42')), function (): string { return 'Some 42'; }) ->case(Any(), function (): string { return 'Fallback'; }) ->done(); // $res4 === 'Some 42'
Type
可以用作检查值类型的简单模式。
$res5 = match(42) ->case(Type('string'), function (): string { return 'String'; }) ->case(Type('integer'), function (): string { return 'Integer'; }) ->case(Any(), function (): string { return 'Not integer'; }) ->done(); // $res5 === 'integer' $res6 = match(Some(42)) ->case(Type(None::class), function (): string { return 'None'; }) ->case(Type(Some::class), function (): string { return 'Some'; }) ->case(Any(), function (): string { return 'Neither'; }) ->done(); // $res6 === 'Some'
Type
模式与 CaseClass
解构一起使用。它提供了检查类型构造和对其参数进行模式匹配的能力。
$res7 = match(Some(42)) ->case(Type(Some::class, Value('42')), returnString('Inner value is string')) ->case(Type(Some::class, Value(42)), returnString('Inner value is integer')) ->case(Any(), returnString('Fallback')) ->done(); // $res7 === 'Inner value is integer'
值绑定
每个与模式匹配的值都可以绑定并用作处理器参数。
$res8 = match(new Tuple('2 * 7 = ', 14)) ->case( Type(Tuple::class, Any()->bind(), Any()->bind()), function (string $question, int $answer): string { return concat('Solution: ', $question, AnyToString($answer)); } ) ->case(Any(), returnString('Fallback')) ->done(); // $res8 === 'Solution: 2 * 7 = 14'
示例
abstract class Notification implements CaseClass {}; final class Email extends Notification { public function __construct(string $sender, string $title, string $body) { ... } } final class SMS extends Notification { public function __construct(string $caller, string $message) { ... } } final class VoiceRecording extends Notification { public function __construct(string $contactName, string $link) { ... } } function showNotification(Notification $notification): string { return match($notification) ->case( Type(Email::class, Type('string')->bind(), Type('string')->bind(), Any()), papply(concat, 'You got an email from ', __, 'with title: ', __) ) ->case( Type(SMS::class, Type('string')->bind(), Type('string')->bind()), papply(concat, 'You got an SMS from ', __, '! Message: ', __) ) ->case( Type(VoiceRecording::class, Type('string')->bind(), Type('string')->bind()), papply(concat, 'You received a Voice Recording from ', __, '! Click the link to hear it: ', __) ) ->done(); } $someSms = new SMS('12345', 'Are you there?'); $someVoiceRecording = new VoiceRecording('Tom', 'voicerecording.org/id/123'); println(showNotification($someSms)); println(showNotification($someVoiceRecording));
You got an SMS from 12345! Message: Are you there?
You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
Scalp\Utils
Delayed
《Delayed》类型表示延迟计算。它由可调用对象——计算及其运行参数创建。当调用时,Delayed
类型会执行延迟的计算。为了创建延迟计算,可以使用工厂方法delay(callable $f, ...$args)
。
use function Scalp\Utils\delay; $delayed = delay(function (int $x): int { return $x * $x; }, 2); echo $delayed();
4
TryCatch
《TryCatch》类型表示可能抛出异常或返回成功值的计算。
use function Scalp\Utils\delay; use function Scalp\Utils\TryCatch; $computation = function (int $divisor): int { return intdiv(42, $divisor); }; $success = TryCatch(delay($computation, 7)); $failure = TryCatch(delay($computation, 0)); echo "Success: $success\n"; echo "Failure: $failure\n";
Success: Success[integer](6)
Failure: Failure[DivisionByZeroError]("Division by zero")