{"id":22896684,"url":"https://github.com/pawaclawczyk/scalp","last_synced_at":"2026-03-17T15:45:36.307Z","repository":{"id":57048022,"uuid":"102777846","full_name":"pawaclawczyk/scalp","owner":"pawaclawczyk","description":"Some Scala useful classes ported to PHP.","archived":false,"fork":false,"pushed_at":"2017-11-09T17:03:14.000Z","size":85,"stargazers_count":18,"open_issues_count":8,"forks_count":1,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-04-17T11:18:12.231Z","etag":null,"topics":["immutable","maybe","option","pattern-matching","php7","try","try-catch","types"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pawaclawczyk.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-09-07T19:33:57.000Z","updated_at":"2022-02-12T06:53:45.000Z","dependencies_parsed_at":"2022-08-23T17:50:24.799Z","dependency_job_id":null,"html_url":"https://github.com/pawaclawczyk/scalp","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pawaclawczyk%2Fscalp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pawaclawczyk%2Fscalp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pawaclawczyk%2Fscalp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pawaclawczyk%2Fscalp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pawaclawczyk","download_url":"https://codeload.github.com/pawaclawczyk/scalp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229629207,"owners_count":18101264,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["immutable","maybe","option","pattern-matching","php7","try","try-catch","types"],"created_at":"2024-12-13T23:38:06.668Z","updated_at":"2026-03-17T15:45:31.248Z","avatar_url":"https://github.com/pawaclawczyk.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/pawaclawczyk/scalp.svg?branch=master)](https://travis-ci.org/pawaclawczyk/scalp)\n[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/pawaclawczyk/scalp/badges/quality-score.png)](https://scrutinizer-ci.com/g/pawaclawczyk/scalp/)\n\nScalp\n======\n\n## Scalp\n### Option\nThe `Option` type represents optional value. It can be either `Some` value or `None`.\n\n```php\nfunction divide(int $x, int $y): Option\n{\n    return $y === 0 ? None() : Some(intdiv($x, $y));\n}\n\nprintln(divide(42, 6));\nprintln(divide(42, 0));\n```\n\n```bash\nSome[int](7)\nNone\n```\n\n`Option` can be used as collection with `map`, `flatMap` or `filter`.\n\n ```php\n$option = Option(42);\n\n$square = function (int $x): int {\n    return $x ** 2;\n};\n\nprintln($option-\u003emap($square));\n\n$isOdd = function (int $x): bool {\n    return $x % 2 === 1;\n};\n\nprintln($option-\u003efilter($isOdd));\n\n$squareRoot = function (int $x): Option {\n    return $x \u003e= 0 ? Some(sqrt($x)) : None();\n};\n\nprintln($option-\u003eflatMap($squareRoot));\n ```\n\n```bash\nSome[integer](1764)\nNone\nSome[double](6.4807406984079)\n```\n\nComputation performed on `Some` can also be performed on `None` without any side effect. The only difference is that\nthe result is always `None`.\n\n```php\nprintln(None()-\u003emap($square));\nprintln(None()-\u003efilter($isOdd));\nprintln(None()-\u003eflatMap($squareRoot));\n```\n\n```bash\nNone\nNone\nNone\n```\n\n### Function Currying\nFunction currying allows to use function as a function of lower arity by providing arguments in separated steps.\n\n```php\nuse function Scalp\\curry;\nuse function Scalp\\println;\n\n$match = curry('preg_match');\n\n$containsFoo = $match('/foo/');\n$containsBar = $match('/bar/');\n\nprintln($containsFoo('foobar'));   // 1\nprintln($containsFoo('foofoo'));   // 1\nprintln($containsFoo('barbar'));   // 0\n\nprintln($containsBar('foobar'));   // 1\nprintln($containsBar('foofoo'));   // 0\nprintln($containsBar('barbar'));   // 1\n```\n\n### Partial Function Application\nPartial function application lets to apply some of function arguments immediately, while rest of them can be applied later.\n\n```php\n$isEven = function (int $x): bool {\n    return $x % 2 === 0;\n};\n\n$filterEven = papply(array_filter, __, $isEven);\n\nprintln(AnyToString(\n    $filterEven([-2, -1, 0, 1, 2])\n));\n\nprintln(AnyToString(\n    $filterEven([11, 13, 17, 19])\n));\n```\n\n```bash\nArray(-2, 0, 2)\nArray()\n```\n\n### Tuple\n\n`Tuple` is a data structure that holds elements of different types.\n`Pair` is factory function for `Tuple` with two elements.\n\n```php\n$singleton = Tuple(42);\n\n$pair      = Pair('Life', 42);\n\n$triple    = Tuple('text', 27, false);\n```\n\n`Tuple` exposes its elements by properties with names `_1`, `_2` to `_N`.\n\n_Elements of Tuple cannot be set._\n\n## Scalp\\Conversion\n### AnyToString\nConverts any type to string. In case of value type looks for implicit conversion function and if not found casts to value string. \nIn case of object type at first looks if object implements `toString` or `__toString` method, then looks for implicit conversion\nand if none of then has been found return object hash id.\n\n```php\necho AnyToString(null) . \"\\n\";\necho AnyToString(false) . \"\\n\";\necho AnyToString(36.6) . \"\\n\";\necho AnyToString(printAny(new class { function toString(): string { return 'Hello World!'; }});) . \"\\n\";\n```\n\n```bash\nnull\nfalse\n36.6\nHello World!\n```\n\n### Implicit conversion\nImplicit conversion is a function that convert value of one type into another.\n\n*Current version does not provide support for implicit conversion. Very simplified version is used by `AnyToString`.\nImplicit conversion should have name following convention `[TypeA]To[TypeB]`.\nIn example in case of conversions able to convert value of some type to string, `AnyToString` will look for functions \nwith name [Type]ToString.*\n\n## Scalp\\PatternMatching\nPattern matching is a mechanism for checking value against a pattern. You can think of it as of advanced switch statement.\nIn opposition to `switch` and `if` statements, pattern matching is an expression (it returns value, like ternary operator `?:`).\nGeneral use of pattern matching expression:\n\n```php\n$result = match($subject)\n    -\u003ecase($pattern1, $callableToRunForPattern1)\n    -\u003ecase($pattern2, $callableToRunForPattern2)\n    ...\n    -\u003ecase($patternN, $callableToRunForPatternN)\n    -\u003edone();\n```\n\nCase patterns are checked in order of declaration. When more general pattern is declared before specific one,\nit will always fall into the general case.\n\n### Case classes and type deconstruction\n`CaseClass` is an interface that ensures existing of `deconstruct` method. `Deconstruct` method should return arguments\nused to construct instance of given type. You can use trait `Deconstruction` to provide the ability to deconstruct.\nIt enables pattern matching to compare immutable complex type values.\n\nIn example the `Option` type is implemented as case class.\n\n```php\nabstract class Option implements CaseClass\n{\n    use Deconstruction;\n\n    ...\n}\n\nfinal class Some extends Option\n{\n    private $value;\n\n    public function __construct($value)\n    {\n        $this-\u003econstruct($value);\n\n        $this-\u003evalue = $value;\n    }\n\n    ...\n}\n\n```\n\n### Patterns\nThe most basic pattern is `Any`, it will match anything.\n```php\n$res0 = match(42)\n    -\u003ecase(Any(), function (): string { return 'Anything'; })\n    -\u003edone();\n\n// $res0 === 'Anything'\n\n$res1 = match(Some(42))\n    -\u003ecase(Any(), function (): string { return 'Anything'; })\n    -\u003edone();\n\n// $res1 === 'Anything'\n```\n\n`Value` pattern does regular comparison with `===` for primitive types or `==` for objects.\nWhen loose comparison is used, objects properties are also compared with `==` (see 3rd example).\n\n```php\n$res2 = match(42)\n    -\u003ecase(Value(13), function (): string { return 'Number 13'; })\n    -\u003ecase(Value('42'), function (): string { return 'String \"42\"'; })\n    -\u003ecase(Value(42), function (): string { return 'Number 42'; })\n    -\u003ecase(Any(), function (): string { return 'Fallback'; })\n    -\u003edone();\n\n// $res2 === 'Number 42'\n\n$res3 = match(Some(42))\n    -\u003ecase(Value(Some(13)), function (): string { return 'Some 13'; })\n    -\u003ecase(Value(Some(42)), function (): string { return 'Some 42'; })\n    -\u003ecase(Any(), function (): string { return 'Fallback'; })\n    -\u003edone();\n\n// $res3 === 'Some 42'\n\n$res4 = match(Some(42))\n    -\u003ecase(Value(Some('42')), function (): string { return 'Some 42'; })\n    -\u003ecase(Any(), function (): string { return 'Fallback'; })\n    -\u003edone();\n\n// $res4 === 'Some 42'\n```\n\nThe `Type` can be used as simple pattern that checks value type.\n```php\n$res5 = match(42)\n    -\u003ecase(Type('string'), function (): string { return 'String'; })\n    -\u003ecase(Type('integer'), function (): string { return 'Integer'; })\n    -\u003ecase(Any(), function (): string { return 'Not integer'; })\n    -\u003edone();\n\n// $res5 === 'integer'\n\n$res6 = match(Some(42))\n    -\u003ecase(Type(None::class), function (): string { return 'None'; })\n    -\u003ecase(Type(Some::class), function (): string { return 'Some'; })\n    -\u003ecase(Any(), function (): string { return 'Neither'; })\n    -\u003edone();\n\n// $res6 === 'Some'\n```\n\n`Type` pattern works with `CaseClass` deconstruction. It gives tha ability to look inside type construction\nand pattern match its arguments.\n\n```php\n$res7 = match(Some(42))\n    -\u003ecase(Type(Some::class, Value('42')), returnString('Inner value is string'))\n    -\u003ecase(Type(Some::class, Value(42)), returnString('Inner value is integer'))\n    -\u003ecase(Any(), returnString('Fallback'))\n    -\u003edone();\n\n// $res7 === 'Inner value is integer'\n```\n\n### Value binding\n\nEvery value matched by a pattern can be bound and used as handler argument.\n\n```php\n$res8 = match(new Tuple('2 * 7 = ', 14))\n    -\u003ecase(\n        Type(Tuple::class, Any()-\u003ebind(), Any()-\u003ebind()),\n        function (string $question, int $answer): string { return concat('Solution: ', $question, AnyToString($answer)); }\n    )\n    -\u003ecase(Any(), returnString('Fallback'))\n    -\u003edone();\n\n// $res8 === 'Solution: 2 * 7 = 14'\n```\n\n### Example\n\n```php\nabstract class Notification implements CaseClass {};\n\nfinal class Email extends Notification\n{\n    public function __construct(string $sender, string $title, string $body) { ... }\n}\n\nfinal class SMS extends Notification\n{\n    public function __construct(string $caller, string $message) { ... }\n}\n\nfinal class VoiceRecording extends Notification\n{\n    public function __construct(string $contactName, string $link) { ... }\n}\n\nfunction showNotification(Notification $notification): string\n{\n    return match($notification)\n        -\u003ecase(\n            Type(Email::class, Type('string')-\u003ebind(), Type('string')-\u003ebind(), Any()),\n            papply(concat, 'You got an email from ', __, 'with title: ', __)\n        )\n        -\u003ecase(\n            Type(SMS::class, Type('string')-\u003ebind(), Type('string')-\u003ebind()),\n            papply(concat, 'You got an SMS from ', __, '! Message: ', __)\n        )\n        -\u003ecase(\n            Type(VoiceRecording::class, Type('string')-\u003ebind(), Type('string')-\u003ebind()),\n            papply(concat, 'You received a Voice Recording from ', __, '! Click the link to hear it: ', __)\n        )\n        -\u003edone();\n}\n\n$someSms = new SMS('12345', 'Are you there?');\n$someVoiceRecording = new VoiceRecording('Tom', 'voicerecording.org/id/123');\n\nprintln(showNotification($someSms));\nprintln(showNotification($someVoiceRecording));\n```\n\n```\nYou got an SMS from 12345! Message: Are you there?\nYou received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123\n```\n\n## Scalp\\Utils\n### Delayed\nThe `Delayed` type represents a postponed computation. It is created from a callable -- the computation and\nits run arguments. The `Delayed` type is callable type, when called it executes the postponed computation.\nIn order to create delayed computation use factory method `dalay(callable $f, ...$args)`.\n\n```php\nuse function Scalp\\Utils\\delay;\n\n$delayed = delay(function (int $x): int { return $x * $x; }, 2);\n\necho $delayed();\n```\n\n```bash\n4\n```\n\n### TryCatch\nThe `TryCatch` type represents computation that may either result in an exception, or return successful value.\n\n```php\nuse function Scalp\\Utils\\delay;\nuse function Scalp\\Utils\\TryCatch;\n\n$computation = function (int $divisor): int {\n    return intdiv(42, $divisor);\n};\n\n$success = TryCatch(delay($computation, 7));\n$failure = TryCatch(delay($computation, 0));\n\necho \"Success: $success\\n\";\necho \"Failure: $failure\\n\";\n```\n\n```bash\nSuccess: Success[integer](6)\nFailure: Failure[DivisionByZeroError](\"Division by zero\")\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpawaclawczyk%2Fscalp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpawaclawczyk%2Fscalp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpawaclawczyk%2Fscalp/lists"}