scalp/scalp

一些Scala有用的类移植到PHP。

v0.2.0 2017-09-16 15:12 UTC

This package is not auto-updated.

Last update: 2024-09-14 02:20:57 UTC


README

Build Status Scrutinizer Code Quality

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 可以作为集合使用,可以进行 mapflatMapfilter 操作。

$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 语句。与 switchif 语句不同,模式匹配是一个表达式(它返回值,就像三元运算符 ?:)。

$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")