{"id":21953385,"url":"https://github.com/kfly8/result-simple","last_synced_at":"2025-08-19T21:23:13.566Z","repository":{"id":263387662,"uuid":"889728284","full_name":"kfly8/Result-Simple","owner":"kfly8","description":"A dead simple perl-ish Result like F#, Rust, Go, etc.","archived":false,"fork":false,"pushed_at":"2025-04-18T14:29:13.000Z","size":63,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-18T21:38:32.113Z","etag":null,"topics":["perl","result-type","types"],"latest_commit_sha":null,"homepage":"","language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kfly8.png","metadata":{"files":{"readme":"README.md","changelog":"Changes","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-11-17T04:14:17.000Z","updated_at":"2025-04-18T14:29:16.000Z","dependencies_parsed_at":"2025-01-19T19:36:42.254Z","dependency_job_id":"bbf77b1d-8f0d-4859-a8ac-8374a45d4bff","html_url":"https://github.com/kfly8/Result-Simple","commit_stats":null,"previous_names":["kfly8/result-simple"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/kfly8/Result-Simple","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfly8%2FResult-Simple","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfly8%2FResult-Simple/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfly8%2FResult-Simple/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfly8%2FResult-Simple/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kfly8","download_url":"https://codeload.github.com/kfly8/Result-Simple/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kfly8%2FResult-Simple/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267652497,"owners_count":24122093,"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","status":"online","status_checked_at":"2025-07-29T02:00:12.549Z","response_time":2574,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["perl","result-type","types"],"created_at":"2024-11-29T07:08:22.282Z","updated_at":"2025-07-29T08:04:50.457Z","avatar_url":"https://github.com/kfly8.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Actions Status](https://github.com/kfly8/Result-Simple/actions/workflows/test.yml/badge.svg)](https://github.com/kfly8/Result-Simple/actions) [![Coverage Status](https://img.shields.io/coveralls/kfly8/Result-Simple/main.svg?style=flat)](https://coveralls.io/r/kfly8/Result-Simple?branch=main) [![MetaCPAN Release](https://badge.fury.io/pl/Result-Simple.svg)](https://metacpan.org/release/Result-Simple)\n# NAME\n\nResult::Simple - A dead simple perl-ish Result like F#, Rust, Go, etc.\n\n# SYNOPSIS\n\n```perl\nuse Result::Simple qw( ok err result_for chain pipeline);\nuse Types::Standard -types;\n\nuse kura Error   =\u003e Dict[message =\u003e Str];\nuse kura Request =\u003e Dict[name =\u003e Str, age =\u003e Int];\n\nresult_for validate_name =\u003e Request, Error;\n\nsub validate_name {\n    my $req = shift;\n    my $name = $req-\u003e{name};\n    return err({ message =\u003e 'No name'}) unless defined $name;\n    return err({ message =\u003e 'Empty name'}) unless length $name;\n    return err({ message =\u003e 'Reserved name'}) if $name eq 'root';\n    return ok($req);\n}\n\nresult_for validate_age =\u003e Request, Error;\n\nsub validate_age {\n    my $req = shift;\n    my $age = $req-\u003e{age};\n    return err({ message =\u003e 'No age'}) unless defined $age;\n    return err({ message =\u003e 'Invalid age'}) unless $age =~ /\\A\\d+\\z/;\n    return err({ message =\u003e 'Too young age'}) if $age \u003c 18;\n    return ok($req);\n}\n\nresult_for validate_req =\u003e Request, Error;\n\nsub validate_req {\n    my $req = shift;\n    my $err;\n\n    ($req, $err) = validate_name($req);\n    return err($err) if $err;\n\n    ($req, $err) = validate_age($req);\n    return err($err) if $err;\n\n    return ok($req);\n}\n\n# my $req = validate_req({ name =\u003e 'taro', age =\u003e 42 });\n# =\u003e Throw an exception, because `validate_req` requires calling in a list context to handle an error.\n\nmy ($req1, $err1) = validate_req({ name =\u003e 'taro', age =\u003e 42 });\n$req1 # =\u003e { name =\u003e 'taro', age =\u003e 42 };\n$err1 # =\u003e undef;\n\nmy ($req2, $err2) = validate_req({ name =\u003e 'root', age =\u003e 20 });\n$req2 # =\u003e undef;\n$err2 # =\u003e { message =\u003e 'Reserved name' };\n\n# Following are the same as above but using `chain` and `pipeline` helper functions.\n\nsub validate_req_with_chain {\n    my $req = shift;\n\n    my @r = ok($req);\n    @r = chain(validate_name =\u003e @r);\n    @r = chain(validate_age =\u003e @r);\n    return @r;\n}\n\nsub validate_req_with_pipeline {\n    my $req = shift;\n\n    state $code = pipeline qw( validate_name validate_age );\n    $code-\u003e(ok($req));\n}\n```\n\n# DESCRIPTION\n\nResult::Simple is a dead simple Perl-ish Result.\n\nResult represents a function's return value as success or failure, enabling safer error handling and more effective control flow management.\nThis pattern is used in other languages such as F#, Rust, and Go.\n\nIn Perl, this pattern is also useful, and this module provides a simple way to use it.\nThis module does not wrap a return value in an object. Just return a tuple like `($data, undef)` or `(undef, $err)`.\n\n## FUNCTIONS\n\n### ok($value)\n\nReturn a tuple of a given value and undef. When the function succeeds, it should return this.\n\n```perl\nsub add($a, $b) {\n    ok($a + $b); # =\u003e ($a + $b, undef)\n}\n```\n\n### err($error)\n\nReturn a tuple of undef and a given error. When the function fails, it should return this.\n\n```perl\nsub div($a, $b) {\n    return err('Division by zero') if $b == 0; # =\u003e (undef, 'Division by zero')\n    ok($a / $b);\n}\n```\n\nNote that the error value must be a truthy value, otherwise it will throw an exception.\n\n### result\\_for $function\\_name =\u003e $T, $E\n\nYou can use the `result_for` to define a function that returns a success or failure and asserts the return value types. Here is an example:\n\n```perl\nresult_for half =\u003e Int, ErrorMessage;\n\nsub half ($n) {\n    if ($n % 2) {\n        return err('Odd number');\n    } else {\n        return ok($n / 2);\n    }\n}\n```\n\n- T (success type)\n\n    When the function succeeds, then returns `($data, undef)`, and `$data` should satisfy this type.\n\n- E (error type)\n\n    When the function fails, then returns `(undef, $err)`, and `$err` should satisfy this type.\n    Additionally, type E must be truthy value to distinguish between success and failure.\n\n    ```perl\n    result_for foo =\u003e Int, Str;\n\n    sub foo ($input) { }\n    # =\u003e throw exception: Result E should not allow falsy values: [\"0\"] because Str allows \"0\"\n    ```\n\n    When a function never returns an error, you can set type E to `undef`:\n\n    ```perl\n    result_for bar =\u003e Int, undef;\n    sub double ($n) { ok($n * 2) }\n    ```\n\n### chain($function, $data, $err)\n\n`chain` is a helper function for passing result type `(T, E)` to the next function.\n\nIf an error has already occurred (when `$err` is defined), the new function won't be called and the same error will be returned as is.\nIf there's no error, the given function will be applied to `$data`, and its result `(T, E)` will be returned.\n\nThis is mainly suitable for use cases where functions need to be applied serially, such as in validation processing.\n\nExample:\n\n```perl\nmy @result = ok($req);\n@result = chain(validate_name =\u003e @result);\n@result = chain(validate_age  =\u003e @result);\nreturn @result;\n```\n\nIn this way, if a failure occurs along the way, the process stops at that point and the failure result is returned.\n\n### pipeline(@functions)\n\n`pipeline` is a helper function that generates a pipeline function that applies multiple functions in series.\n\nIt returns a new function that applies the given list of functions in order. This generated function takes an argument in the form of `(T, E)`,\nand if an error occurs during the process, it immediately halts processing as a failure. If processing succeeds all the way through, it returns `ok($value)`.\n\nExample:\n\n```perl\nstate $code = pipeline qw( validate_name validate_age );\nmy ($req, $err) = $code-\u003e($input);\n```\n\nThis allows you to describe multiple processes concisely as a single flow.\nEach function in the pipeline needs to return `(T, E)`.\n\n### combine(@results)\n\n`combine` takes a list of Result like `((T1,E1), (T2,E2), (T3,E3))` and returns a new Result like `([T1,T2,T3], E)`.\n\nIf all Result values are successful, it returns a new Result with all success values collected into an array reference. If any Result has an error, the function short-circuits and returns the first error encountered.\n\nThis is useful when you need to collect the results of multiple operations that all need to succeed, similar to how `Promise.all` works in JavaScript. For example, when fetching data from multiple sources or validating multiple aspects of input data.\n\nExample:\n\n```perl\nsub fetch_user { ... }       # Returns Result\u003cUser, Error\u003e\nsub fetch_orders { ... }     # Returns Result\u003cOrder[], Error\u003e\nsub fetch_settings { ... }   # Returns Result\u003cSettings, Error\u003e\n\nmy ($data, $err) = combine(\n    fetch_user($user_id),\n    fetch_orders($user_id),\n    fetch_settings($user_id)\n);\n\nif ($err) {\n    # Handle error\n} else {\n    my ($user, $orders, $settings) = @$data;\n    # Process all successful results\n}\n```\n\n### combine\\_with\\_all\\_errors(@results)\n\n`combine_with_all_errors` takes a list of Result like `((T1,E1), (T2,E2), (T3,E3))` and returns a new Result.\n\nUnlike `combine` which stops at the first error, this function collects all errors from the input Results. If all Results are successful, it returns `([T1,T2,T3], undef)`. If any Results have errors, it returns `(undef, [E1,E2,E3])` with an array reference containing all encountered errors.\n\nThis is particularly useful for validation scenarios where you want to report all validation errors at once rather than one at a time. For example, when validating a form, you might want to show the user all fields that have errors rather than making them fix one error at a time.\n\nExample:\n\n```perl\nsub validate_name { ... }    # Returns Result\u003cName, Error\u003e\nsub validate_email { ... }   # Returns Result\u003cEmail, Error\u003e\nsub validate_age { ... }     # Returns Result\u003cAge, Error\u003e\n\nmy ($data, $errors) = combine_with_all_errors(\n    validate_name($form-\u003e{name}),\n    validate_email($form-\u003e{email}),\n    validate_age($form-\u003e{age})\n);\n\nif ($errors) {\n    # Show all validation errors to the user\n    for my $error (@$errors) {\n        print \"Error: $error-\u003e{message}\\n\";\n    }\n} else {\n    my ($name, $email, $age) = @$data;\n    # Process valid form data\n}\n```\n\n### flatten(@results)\n\n`flatten` takes a list of array references that contain Result tuples and flattens them into a single list of Result tuples.\n\nFor example, it converts `([T1,E1], [T2,E2], [T3,E3])` to `(T1,E1, T2,E2, T3,E3)`.\n\nThis is useful when you have multiple arrays of Results that you need to combine or process together.\n\nExample:\n\n```perl\nmy @result1 = ok(1);\nmy @result2 = ok(2);\nmy @result3 = ok(3);\n\nmy @all_results = flatten([\\@result1], [\\@result2], [\\@result3]);\n# @all_results is now (1,undef, 2,undef, 3,undef)\n\n# You can use it with combine:\nmy ($values, $error) = combine(flatten([\\@result1], [\\@result2], [\\@result3]));\n# $values is [1, 2, 3], $error is undef\n```\n\n### match($on\\_success, $on\\_failure)\n\n`match` provides a way to handle both success and failure cases of a Result in a functional style, similar to pattern matching in other languages.\n\nIt takes two callbacks:\n\\- `$on_success`: a function that receives the success value\n\\- `$on_failure`: a function that receives the error value\n\n`match` returns a new function that will call the appropriate callback depending on whether the Result passed to it represents success or failure.\n\nExample:\n\n```perl\nmy $handler = match(\n    sub { my $value = shift; \"Success: The value is $value\" },\n    sub { my $error = shift; \"Error: $error occurred\" }\n);\n\n$handler-\u003e(ok(42));               # =\u003e Success: The value is 42\n$handler-\u003e(err(\"Invalid input\")); # =\u003e Error: Invalid input occurred\n```\n\n### unsafe\\_unwrap($data, $err)\n\n`unsafe_unwrap` takes a Result\u003cT, E\u003e and returns a T when the result is an Ok, otherwise it throws exception.\nIt should be used in tests or debugging code.\n\n```perl\nsub div($a, $b) {\n    return err('Division by zero') if $b == 0;\n    return ok($a / $b);\n}\n\nunsafe_unwrap(div(4, 2)); # =\u003e 2\nunsafe_unwrap(div(4, 0)); # =\u003e throw an exception: Error called in `unsafe_unwrap`: \"Division by zero\"\n```\n\n### unsafe\\_unwrap\\_err($data, $err)\n\n`unsafe_unwrap_err` takes a Result\u003cT, E\u003e and returns an E when the result is an Err, otherwise it throws exception.\nIt should be used in tests or debugging code.\n\n```perl\nsub div($a, $b) {\n    return err('Division by zero') if $b == 0;\n    return ok($a / $b);\n}\nunsafe_unwrap_err(div(4, 2)); # =\u003e throw an exception: No error called in `unsafe_unwrap_err`: 2\nunsafe_unwrap_err(div(4, 0)); # =\u003e \"Division by zero\"\n```\n\n## ENVIRONMENTS\n\n### `$ENV{RESULT_SIMPLE_CHECK_ENABLED}`\n\nIf the `ENV{RESULT_SIMPLE_CHECK_ENABLED}` environment is truthy before loading this module, it works as an assertion.\nOtherwise, if it is falsy, `result_for` attribute does nothing. The default is true.\nThis option is useful for development and testing mode, and it recommended to set it to false for production.\n\n```perl\nresult_for foo =\u003e Int, undef;\nsub foo { ok(\"hello\") }\n\nmy ($data, $err) = foo();\n# =\u003e throw exception when check enabled\n```\n\n# NOTE\n\n## Type constraint requires `check` method\n\nPerl has many type constraint modules, but this module requires the type constraint module that provides `check` method.\nSo you can use [Type::Tiny](https://metacpan.org/pod/Type%3A%3ATiny), [Moose](https://metacpan.org/pod/Moose), [Mouse](https://metacpan.org/pod/Mouse) or [Data::Checks](https://metacpan.org/pod/Data%3A%3AChecks) etc.\n\n## Use different function name\n\nSometimes, you may want to use a different name for `ok`, `err`, or some other functions of `Result::Simple`.\nFor example, `Test2::V0` has `ok` functions, so it conflicts with `ok` function of `Result::Simple`.\nThis module provides a way to set a different function name using the `-as` option.\n\n```perl\nuse Result::Simple\n    ok =\u003e { -as =\u003e 'left' },   # `left` is equivalent to `ok`\n    err =\u003e { -as =\u003e 'right' }; # `right` is equivalent to `err`\n```\n\n## Check unhandled error\n\n[Perl::Critic::Policy::Variables::ProhibitUnusedVarsStricter](https://metacpan.org/pod/Perl%3A%3ACritic%3A%3APolicy%3A%3AVariables%3A%3AProhibitUnusedVarsStricter) is useful to check unhandled error at compile time.\n\n```perl\nuse Result::Simple;\nmy ($v, $e) = ok(2); # =\u003e Critic: $e is declared but not used (Variables::ProhibitUnusedVarsStricter, Severity: 3)\nprint $v;\n```\n\n## Using Result::Simple with Promises for asynchronous operations\n\nResult::Simple can be combined with Promise-based asynchronous operations to create clean, functional error handling in asynchronous code. Here's an example using Mojo::Promise:\n\n```perl\nuse Mojo::Promise;\nuse Mojo::UserAgent;\nuse Result::Simple qw(ok err combine flatten match);\n\nmy $ua = Mojo::UserAgent-\u003enew;\n\n# Convert HTTP responses to Result tuples\nsub fetch_result {\n    my $uri = shift;\n    $ua-\u003eget_p($uri)-\u003ethen(\n        sub {\n            my $tx = shift;\n            my $res = $tx-\u003eresult;\n            if ($res-\u003eis_success) {\n                return ok($res-\u003ejson);  # Success case with parsed JSON\n            } else {\n                return err({            # Error case with details\n                    uri =\u003e $uri,\n                    code =\u003e $res-\u003ecode,\n                });\n            }\n        }\n    )-\u003ecatch(\n        sub {\n            my $err = shift;\n            return err($err);           # Connection/network errors\n        }\n    );\n}\n\n# Fetch a specific todo item\nsub fetch_todo {\n    my $id = shift;\n    my $uri = \"https://jsonplaceholder.typicode.com/todos/${id}\";\n    fetch_result($uri);\n}\n\n# Fetch multiple todos in parallel\nMojo::Promise-\u003eall(\n    fetch_todo(1),\n    fetch_todo(2),\n)-\u003ethen(\n    sub {\n        # Combine the results of multiple promises\n        my ($todos, $err) = combine(flatten(@_));\n\n        # Create a matcher to handle the combined result\n        state $handler = match(\n            sub {\n                my $todos = shift;\n                say \"Successfully fetched all todos:\";\n                for my $todo (@$todos) {\n                    say \"- Todo #$todo-\u003e{id}: $todo-\u003e{title}\";\n                    say \"  Completed: \" . ($todo-\u003e{completed} ? \"Yes\" : \"No\");\n                }\n            },\n            sub {\n                my $error = shift;\n                say \"Error fetching todos:\";\n                if (ref $error eq 'HASH' \u0026\u0026 exists $error-\u003e{code}) {\n                    say \"HTTP $error-\u003e{code} error for $error-\u003e{uri}\";\n                } else {\n                    say \"Connection error: $error\";\n                }\n            }\n        );\n\n        # Process the result\n        $handler-\u003e($todos, $err);\n    }\n)-\u003ewait;\n```\n\nThis pattern provides several benefits:\n\n- Clear separation between success and error cases\n- Consistent error handling across both synchronous and asynchronous code\n- Ability to combine multiple asynchronous operations and handle their results uniformly\n- More expressive and maintainable code through functional composition\n\nThe combination of `flatten`, `combine`, and `match` makes it easy to work with multiple promises while maintaining clean error handling.\n\n## Avoiding Ambiguity in Result Handling\n\nForgetting to call `ok` or `err` function is a common mistake. Consider the following example:\n\n```perl\nresult_for validate_name =\u003e Str, ErrorMessage;\n\nsub validate_name ($name) {\n    return \"Empty name\" unless $name; # Oops! Forgot to call `err` function.\n    return ok($name);\n}\n\nmy ($name, $err) = validate_name('');\n# =\u003e throw exception: Invalid result tuple (T, E)\n```\n\nIn this case, the function throws an exception because the return value is not a valid result tuple `($data, undef)` or `(undef, $err)`.\nThis is fortunate, as the mistake is detected immediately. The following case is not detected:\n\n```perl\nresult_for foo =\u003e Str, ErrorMessage;\n\nsub foo {\n    return (undef, 'apple'); # No use of `ok` or `err` function.\n}\n\nmy ($data, $err) = foo;\n# =\u003e $err is 'apple'\n```\n\nHere, the function returns a valid failure tuple `(undef, $err)`. However, it is unclear whether this was intentional or a mistake.\nThe lack of `ok` or `err` makes the intent ambiguous.\n\nConclusively, be sure to use `ok` or `err` functions to make it clear whether the success or failure is intentional.\n\n# LICENSE\n\nCopyright (C) kobaken.\n\nThis library is free software; you can redistribute it and/or modify\nit under the same terms as Perl itself.\n\n# AUTHOR\n\nkobaken \u003ckentafly88@gmail.com\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkfly8%2Fresult-simple","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkfly8%2Fresult-simple","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkfly8%2Fresult-simple/lists"}