Result.java

package com.guinetik.hexafun.fun;

import java.util.Objects;
import java.util.function.Function;

public sealed interface Result<T> permits Result.Success, Result.Failure {

    static <T> Result<T> ok(T value) {
        return new Success<>(value);
    }

    static <T> Result<T> fail(String message) {
        return new Failure<>(message);
    }

    boolean isSuccess();
    boolean isFailure();

    T get(); // orElseThrow?
    String error();

    <U> Result<U> map(Function<T, U> mapper);
    <U> Result<U> flatMap(Function<T, Result<U>> mapper);

    <U> U fold(Function<String, U> onFailure, Function<T, U> onSuccess);

    final class Success<T> implements Result<T> {
        private final T value;

        public Success(T value) {
            this.value = value;
        }

        public boolean isSuccess() { return true; }
        public boolean isFailure() { return false; }
        public T get() { return value; }
        public String error() { throw new IllegalStateException("No error"); }

        public <U> Result<U> map(Function<T, U> mapper) {
            return Result.ok(mapper.apply(value));
        }

        public <U> Result<U> flatMap(Function<T, Result<U>> mapper) {
            return mapper.apply(value);
        }

        public <U> U fold(Function<String, U> onFailure, Function<T, U> onSuccess) {
            return onSuccess.apply(value);
        }
    }

    final class Failure<T> implements Result<T> {
        private final String error;

        public Failure(String error) {
            this.error = Objects.requireNonNull(error);
        }

        public boolean isSuccess() { return false; }
        public boolean isFailure() { return true; }
        public T get() { throw new IllegalStateException(error); }
        public String error() { return error; }

        public <U> Result<U> map(Function<T, U> mapper) {
            return Result.fail(error);
        }

        public <U> Result<U> flatMap(Function<T, Result<U>> mapper) {
            return Result.fail(error);
        }

        public <U> U fold(Function<String, U> onFailure, Function<T, U> onSuccess) {
            return onFailure.apply(error);
        }
    }
}