Git Product home page Git Product logo

maybe's Introduction

Maybe: Option & Result for PHP

Help yourself by not returning null, false, -1 or throwing exception for everything by using Option & Result instead.

Using those makes it harder to make mistake, make it easy to do common operations on unknown returned values and can help static analysis tool detect incorrect logic in PHP code.

doctest looks for php examples in your functions, methods classes & interfaces comments and execute them to ensure they are correct.

Installation

composer req --dev texthtml/maybe

Usage

Options

Type Option represents an optional value: every Option is either Option\Some and contains a value, or Option\None, and does not. Option types have a number of uses:

  • Initial values
  • Return values for functions that are not defined over their entire input range (partial functions)
  • Return value for otherwise reporting simple errors, where None is returned on error
  • Optional class properties
  • Swapping things out of difficult situations

Options are commonly paired with instanceof Option\{Some|None} to query the presence of a value and take action, always accounting for the None case.

/**
 * @param Option<float>
 */
function divide(float $numerator, float $denominator) -> Option {
    return match ($denomintor) {
        0.0 => Option\none(),
        _ => Option\some($numerator / $denominator)
    };
}

// The return value of the function is an option
$result = divide(2.0, 3.0);

// Pattern match to retrieve the value
if ($result instanceof Option\Some) {
    // The division was valid
    echo "Result: {$option->unwrap()}");
} else {
    // The division was invalid
    echo "Cannot divide by 0";
}

Results

Result<T, E> is the type used for returning and propagating errors. It two variants, Ok(T), representing success and containing a value, and Err(E), representing error and containing an error value.

Functions return Result whenever errors are expected and recoverable.

A simple function returning Result might be defined and used like so:

/**
 * @param Result<Version,string>
 */
function parse_version(string $header) -> Result {
    return match $header[0] ?? null {
        null => Result\err("invalid header length"),
        1 => Result\ok(Version::Version1),
        2 => Result\ok(Version::Version2),
        _ => Result\err("invalid version"),
    };
}

$version = parse_version("1234");
if ($version instanceof Result\Ok) {
    echo "working with version: {$version->unwrap()}";
    echo "error parsing header: {$version->unwrapErr()}";
}

Results must be used

A common problem with using return values to indicate errors is that it is easy to ignore the return value, thus failing to handle the error. Unused Results are tracked and will trigger an exception when a Result value is ignored and goes out of scope. This makes Result especially useful with functions that may encounter errors but don’t otherwise return a useful value.

/**
 * Write $data in $filepath
 * @return Result<int,string> The number of bytes that were written to the file if Ok, an error message otherwise
 */
function writeInFile(string $filepath, string $data): Result {
    $res = file_put_contents($filepath, $data);

    if ($res === false) {
        return Result\err("failed to write in $path");
    }

    return Result\ok($res);
}

$result = writeInFile("/path/to/file", "Hi!");

// …

// @throws Result\UnusedResultException if $result has not been used before

Using a Result can be done by calling any method on it, except inspect() & inspectErr().

Note: some methods return another Result that must also be used.

Documentation

Option & Result each have plenty of methods helper, explore their source code comments to learn about them and how they can be used.

TODO

  • Document Result methods
  • Generate documentation from comments in file
  • To prevent other implementations of Option & Result, try another implementation with:
    • a class with a final private constructor and final unserialize, etc. methods (eg: class Option)
    • 1 interface for each variant of the class (eg: interface Option\Some & interface Option\None)
    • 1 static public constructors for each variant of the class (eg: Option::Some($value) & Option::None()
      • return a dynamic class extending the reference class and implementing the corresponding interface variant
      • question: will it be possible to serialize / unserialize those objects?

maybe's People

Contributors

mathroc avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.