kotlin-result

A multiplatform Result monad for modelling success or failure operations.

ISC License

Stars
1K

Bot releases are hidden (Show)

kotlin-result - Latest Release

Published by michaelbull 7 months ago

  • The Result type is now an inline value class for reduced runtime overhead (981fbe2812185f5083f44854409601bb42fcfb3c)
    • Before & After comparisons outlined below
    • Also see the Overhead design doc on the wiki
  • Previously deprecated behaviours have been removed (eecd1b7d9946f541af3f5d453905024ff97e7c26)

Migration Guide

Ok/Err as Types

The migration to an inline value class means that using Ok/Err as types is no longer valid.

Consumers that need to introspect the type of Result should instead use Result.isOk/Result.isErr booleans. This naming scheme matches Rust's is_ok & is_err functions.

Before:

public inline fun <V, E, U> Result<V, E>.mapOrElse(default: (E) -> U, transform: (V) -> U): U {
    return when (this) {
        is Ok -> transform(value)
        is Err -> default(error)
    }
}

After:

public inline fun <V, E, U> Result<V, E>.mapOrElse(default: (E) -> U, transform: (V) -> U): U {
    return when {
        isOk -> transform(value)
        else -> default(error)
    }
}

Type Casting

When changing the return type to another result, e.g. the map function which goes from Result<V, E> to Result<U, E>, consumers are encouraged to use the asOk/asErr extension functions in conjunction with the isOk/isErr guard.

The example below calls asErr which unsafely casts the Result<V, E to Result<Nothing, E>, which is acceptable given the isOk check, which satisfies the Result<U, E> return type.

The asOk/asOk functions should not be used outside of a manual type guard via isOk/isErr - the cast is unsafe.

public inline infix fun <V, E, U> Result<V, E>.map(transform: (V) -> U): Result<U, E> {
    return when {
        isOk -> Ok(transform(value))
        else -> this.asErr() // unsafely typecasts Result<V, E> to Result<Nothing, E>
    }
}

Removal of Deprecations

The following previously deprecated behaviours have been removed in v2.

  • binding & SuspendableResultBinding, use coroutineBinding instead
  • and without lambda argument, use andThen instead
  • ResultBinding, use BindingScope instead
  • getOr without lambda argument, use getOrElse instead
  • getErrorOr without lambda argument, use getErrorOrElse instead
  • getAll, use filterValues instead
  • getAllErrors, use filterErrors instead
  • or without lambda argument, use orElse instead
  • Result.of, use runCatching instead
  • expect with non-lazy evaluation of message
  • expectError with non-lazy evaluation of message

Inline Value Class - Before & After

The base Result class is now modelled as an inline value class. References to Ok<V>/Err<E> as types should be replaced with Result<V, Nothing> and Result<Nothing, E> respectively.

Calls to Ok and Err still function, but they no longer create a new instance of the Ok/Err objects - instead these are top-level functions that return a type of Result. This change achieves code that produces zero object allocations when on the "happy path", i.e. anything that returns an Ok(value). Previously, every successful operation wrapped its returned value in a new Ok(value) object.

The Err(error) function still allocates a new object each call by internally wrapping the provided error with a new instance of a Failure object. This Failure class is an internal implementation detail and not exposed to consumers. As a call to Err is usually a terminal state, occurring at the end of a chain, the allocation of a new object is unlikely to cause a lot of GC pressure unless a function that produces an Err is called in a tight loop.

Below is a comparison of the bytecode decompiled to Java produced before and after this change. The total number of possible object allocations is reduced from 4 to 1, with 0 occurring on the happy path and 1 occurring on the unhappy path.

public final class Before {
    @NotNull
    public static final Before INSTANCE = new Before();

    private Before() {
    }

    @NotNull
    public final Result<Integer, ErrorOne> one() {
        return (Result)(new Ok(50));
    }

    public final int two() {
        return 100;
    }

    @NotNull
    public final Result<Integer, ErrorThree> three(int var1) {
        return (Result)(new Ok(var1 + 25));
    }

    public final void example() {
        Result $this$map$iv = this.one(); // object allocation (1)
        Result var10000;
        if ($this$map$iv instanceof Ok) {
            Integer var10 = INSTANCE.two();
            var10000 = (Result)(new Ok(var10)); // object allocation (2)
        } else {
            if (!($this$map$iv instanceof Err)) {
                throw new NoWhenBranchMatchedException();
            }

            var10000 = $this$map$iv;
        }

        Result $this$mapError$iv = var10000;
        if ($this$mapError$iv instanceof Ok) {
            var10000 = $this$mapError$iv;
        } else {
            if (!($this$mapError$iv instanceof Err)) {
                throw new NoWhenBranchMatchedException();
            }

            ErrorTwo var11 = ErrorTwo.INSTANCE;
            var10000 = (Result)(new Err(var11)); // object allocation (3)
        }

        Result $this$andThen$iv = var10000;
        if ($this$andThen$iv instanceof Ok) {
            int p0 = ((Number)((Ok)$this$andThen$iv).getValue()).intValue();
            var10000 = this.three(p0); // object allocation (4)
        } else {
            if (!($this$andThen$iv instanceof Err)) {
                throw new NoWhenBranchMatchedException();
            }

            var10000 = $this$andThen$iv;
        }

        String result = var10000.toString();
        System.out.println(result);
    }

    public static abstract class Result<V, E> {
        private Result() {
        }
    }

    public static final class Ok<V> extends Result {
        private final V value;

        public Ok(V value) {
            this.value = value;
        }

        public final V getValue() {
            return this.value;
        }

        public boolean equals(@Nullable Object other) {
            if (this == other) {
                return true;
            } else if (other != null && this.getClass() == other.getClass()) {
                Ok var10000 = (Ok)other;
                return Intrinsics.areEqual(this.value, ((Ok)other).value);
            } else {
                return false;
            }
        }

        public int hashCode() {
            Object var10000 = this.value;
            return var10000 != null ? var10000.hashCode() : 0;
        }

        @NotNull
        public String toString() {
            return "Ok(" + this.value + ')';
        }
    }
    
    public static final class Err<E> extends Result {
        private final E error;

        public Err(E error) {
            this.error = error;
        }

        public final E getError() {
            return this.error;
        }

        public boolean equals(@Nullable Object other) {
            if (this == other) {
                return true;
            } else if (other != null && this.getClass() == other.getClass()) {
                Before$Err var10000 = (Err)other;
                return Intrinsics.areEqual(this.error, ((Err)other).error);
            } else {
                return false;
            }
        }

        public int hashCode() {
            Object var10000 = this.error;
            return var10000 != null ? var10000.hashCode() : 0;
        }

        @NotNull
        public String toString() {
            return "Err(" + this.error + ')';
        }
    }
}
public final class After {
    @NotNull
    public static final After INSTANCE = new After();

    private After() {
    }

    @NotNull
    public final Object one() {
        return this.Ok(50);
    }

    public final int two() {
        return 100;
    }

    @NotNull
    public final Object three(int var1) {
        return this.Ok(var1 + 25);
    }

    public final void example() {
        Object $this$map_u2dj2AeeQ8$iv = this.one();
        Object var10000;
        if (Result.isOk_impl($this$map_u2dj2AeeQ8$iv)) {
            var10000 = this.Ok(INSTANCE.two());
        } else {
            var10000 = $this$map_u2dj2AeeQ8$iv;
        }

        Object $this$mapError_u2dj2AeeQ8$iv = var10000;
        if (Result.isErr_impl($this$mapError_u2dj2AeeQ8$iv)) {
            var10000 = this.Err(ErrorTwo.INSTANCE); // object allocation (1)
        } else {
            var10000 = $this$mapError_u2dj2AeeQ8$iv;
        }

        Object $this$andThen_u2dj2AeeQ8$iv = var10000;
        if (Result.isOk_impl($this$andThen_u2dj2AeeQ8$iv)) {
            int p0 = ((Number) Result.getValue_impl($this$andThen_u2dj2AeeQ8$iv)).intValue();
            var10000 = this.three(p0);
        } else {
            var10000 = $this$andThen_u2dj2AeeQ8$iv;
        }

        String result = Result.toString_impl(var10000);
        System.out.println(result);
    }

    @NotNull
    public final <V> Object Ok(V value) {
        return Result.constructor_impl(value);
    }

    @NotNull
    public final <E> Object Err(E error) {
        return Result.constructor_impl(new Failure(error));
    }

    public static final class Result<V, E> {
        @Nullable
        private final Object inlineValue;

        public static final V getValue_impl(Object arg0) {
            return arg0;
        }

        public static final E getError_impl(Object arg0) {
            Intrinsics.checkNotNull(arg0, "null cannot be cast to non-null type Failure<E of Result>");
            return ((Failure) arg0).getError();
        }

        public static final boolean isOk_impl(Object arg0) {
            return !(arg0 instanceof Failure);
        }

        public static final boolean isErr_impl(Object arg0) {
            return arg0 instanceof Failure;
        }

        @NotNull
        public static String toString_impl(Object arg0) {
            return isOk_impl(arg0) ? "Ok(" + getValue_impl(arg0) + ')' : "Err(" + getError_impl(arg0) + ')';
        }

        @NotNull
        public String toString() {
            return toString_impl(this.inlineValue);
        }

        public static int hashCode_impl(Object arg0) {
            return arg0 == null ? 0 : arg0.hashCode();
        }

        public int hashCode() {
            return hashCode_impl(this.inlineValue);
        }

        public static boolean equals_impl(Object arg0, Object other) {
            if (!(other instanceof Result)) {
                return false;
            } else {
                return Intrinsics.areEqual(arg0, ((Result) other).unbox_impl());
            }
        }

        public boolean equals(Object other) {
            return equals_impl(this.inlineValue, other);
        }

        private Result(Object inlineValue) {
            this.inlineValue = inlineValue;
        }

        @NotNull
        public static <V, E> Object constructor_impl(@Nullable Object inlineValue) {
            return inlineValue;
        }

        public static final Result box_impl(Object v) {
            return new Result(v);
        }

        public final Object unbox_impl() {
            return this.inlineValue;
        }

        public static final boolean equals_impl0(Object p1, Object p2) {
            return Intrinsics.areEqual(p1, p2);
        }
    }

    static final class Failure<E> {
        private final E error;

        public Failure(E error) {
            this.error = error;
        }

        public final E getError() {
            return this.error;
        }

        public boolean equals(@Nullable Object other) {
            return other instanceof Failure && Intrinsics.areEqual(this.error, ((Failure)other).error);
        }

        public int hashCode() {
            Object var10000 = this.error;
            return var10000 != null ? var10000.hashCode() : 0;
        }

        @NotNull
        public String toString() {
            return "Failure(" + this.error + ')';
        }
    }
}
kotlin-result -

Published by michaelbull 7 months ago

This release serves as a bridge towards v2 and the last major release of v1.

Old behaviours have been deprecated in a non-breaking manner to anticipate the breaking changes of v2.

Additions

  • Add flatMapEither, flatMapBoth (4e5cdeede7b5dbecc96ea8f561bbec4ae96e407b)
  • Add mapCatching (15fc1ff0139306b640a38f00922748b34e2e1d5b)
  • Add Iterable.allOk, Iterable.allErr, Iterable.anyOk, Iterable.anyErr, Iterable.countOk, Iterable.countErr (6e62d9f97df43c7d2e10fe407ddc110aeb85840d)
  • Add Iterable.filterValues, Iterable.filterValuesTo, Iterable.filterErrors, Iterable.filterErrorsTo (f091f507d97a0d3f565945ef9c97ae1eb28bfe59)
  • Add transpose (c46a2925b17a87b98081b7b10214e1468ffc483d)
  • Return List of errors for all variants of zipOrAccumulate by @YuitoSato (716109aa84ba3d5975b964351b6b0c5bec251ee5)
    • The four-arg and five-arg variants were returning Collection instead of List.

Deprecations

  • Deprecate getAll, getAllErrors in favour of filterValues & filterErrors (aca9ad92f8ec761d2fdad5b6ccbf900931eddf74)
  • Deprecate ResultBinding in favour of BindingScope (dd5c96f983b08fc5c44c44e888636f19c0e45b22)
  • Deprecate suspending variant of binding in favour of coroutineBinding (b19894a08c1ec56512ff301444669da2125f8de2)
    • This matches the internally-called function named coroutineScope, and helps consumers distinguish between the blocking variant that is otherwise only differing in package name.
    • This should also help convey to readers that structured concurrency will occur within the block.
  • Deprecate Ok/Err as return types (7ce7c16d7fb055e31711218ac4642a621c8cc515)
    • This is in preparation for the v2 release where these don't exist as types.
  • Deprecate getAll/getAllErrors in favour of valuesOf/errorsOf (522c821fdf45363c0f3d60b29b37bb958af90839)
kotlin-result -

Published by michaelbull 8 months ago

kotlin-result -

Published by michaelbull 8 months ago

  • Document the order of output lists in Iterable.kt by @peter-cunderlik-kmed (e81f581436ec2fb5c45296465ac84176807a33d2)
  • Add zipOrAccumulate by @YuitoSato (27f0a63847a0522686a67bc3672b4c4b73f4c449)
  • Update Kotlin to 1.9.20 (05a1e91298296c39f008bd233b179b107d2cf61c)
    • "In Kotlin 1.9.20, we've also removed a number of previously deprecated targets, namely:"
      • iosArm32
      • watchosX86
      • wasm32
      • mingwX86
      • linuxMips32
      • linuxMipsel32
    • See: https://kotl.in/native-targets-tiers
  • Add andThen{Recover,RecoverIf,RecoverUnless} by @Jhabkin (d4414b1a086cb219795d649fddf79bbd4c9a4c63)
  • Facilitate mapping to arbitrary types in and/or functions (05d50b7fec517de0ed7ab76cf7f2085f53fedbf9)
    • See #95
  • Extract shared build logic to convention plugins (88e31cd1ac0f5c65f72435cc39b12a57983efb08)
kotlin-result -

Published by michaelbull over 1 year ago

  • Improve type constraint on toErrorIfNull by @kirillzh (d7dbf35bcf305ea3dcfbe4f22e02b2fa5d6ee43d)
kotlin-result -

Published by michaelbull over 1 year ago

  • Add recoverCatching by @berikv (a6eb86da71e0db1386f2b013eba31f12c53cc6b5)
  • Add more multiplatform build targets by @05nelsonm (6f86d20d53adaa814ca68cbb91946abcf7be2c2a)
  • Migrate to IR-based JavaScript compiler (cc3b3cea05ee7f93c61c9b431090a2e122ae74ca)
  • Disable compatibility with non-hierarchical multiplatform projects (c1c50369917f45b1d8b136992a6d6b7cbd4b4ea8)
  • Migrate to Gradle version catalog by @bitPogo (41fff9eb9c2f2aba3d526d531a868a52976d4c06)
  • Update Gradle to 8.0.2 (8229a29f6217497874ecdeb6e8e5e5f8ab6719cb)
  • Update dependencies (6e1c4dd5f1b28332472727420a5fec306af10b3b)
  • Add toErrorIfNull & toErrorUnlessNull (fd2160c7a66f33c0c29011e59af1a1a514f3ba2a)
    • See #84
kotlin-result -

Published by michaelbull over 2 years ago

kotlin-result - 1.1.15

Published by michaelbull over 2 years ago

  • Add iosSimulatorArm64 and macosArm64 targets by @dimsuz (fe30193d7c03c97f6a471adf251f71deb12152fb)
  • Update dependencies (96a84b227b1cbbe91a67bd1f0b2616da7e1a030a)
  • Update Gradle to 7.4.2 (ead48285598af63749ceb1056658fe85b4cc5ff9)
  • Include LICENSE file in META-INF directories of jar files (07ad45929f74dc207f7acf33eab6074c2bbaadb0)
kotlin-result -

Published by michaelbull almost 3 years ago

  • Add getOrThrow by @Nimelrian (d07bd589edd24e55d18ec03036ac554e18fae025)
    • See #68
  • Migrate example project to Ktor 2 (6a5523c9983fb7852373b427e4f1c6a209cd9a64)
  • Update Gradle to 7.3.3 (7e89f1b6a6d2b09a5417893d352f8e557b05fc85)
  • Update dependencies (4b9ca158fc129c207a8f742b5fa7375879895f70)
  • Migrate to new kotlinx-coroutines-test API (72df4c0ff60979a3782ea8806ad853ee39c962b0)
    • See #69
kotlin-result - 1.1.13

Published by michaelbull almost 3 years ago

  • Update Kotlin to 1.5.31 by @pablisco (b8d4109eee92655745fd7750660ceea4def2cd50)
  • Replace usages of useExperimentalAnnotation by @grodin (4e1bb9d8de06ad30c3f3ca84bc6c00810587a72d)
  • Update Gradle to 7.2 (98c8eaead34fab3f1ae06cb357ebdc9761a555bc)
  • Add Result#orElseThrow (f236e2674bf98f12f50fa740e1f70aa65b5464d5)
  • Add Result#{throwIf,throwUnless} by @grodin (3b87373b238df613605cee0a82ee1e0def879507)
  • Add runSuspendCatching and T#runSuspendCatching by @grodin (2667273015dd18350d23f7a8de965cb49c184a8d)
    • See #64
kotlin-result -

Published by michaelbull over 3 years ago

kotlin-result -

Published by michaelbull over 3 years ago

kotlin-result -

Published by michaelbull over 3 years ago

  • Releases are now automated via GitHub actions. Every push to master will produce a SNAPSHOT build available at:
  • Fixed links in README (3d40c7070837359a1e9884c56c1c6a7ec4dccb65) by @gregoryinouye
  • Eagerly cancel async bindings (c8372a052218a6d76a31b256968586093d0c03fb) by @Munzey
  • Add Result.recoverIf and Result.recoverUnless (0fdd0f2c2bccf3fdc6eb0615a145da4b6ee12ed5) by @oddsund
  • Publish JS multiplatform artifacts (0f90bb8b90c4fa224207ac40858856bce02b94fe) by @DerYeger
kotlin-result -

Published by michaelbull about 4 years ago

  • Coroutine binding support moved to kotlin-result-coroutines module (b16fb559a14dcd77139fb907db213184581e6bd6) by @Munzey
  • Add Scala's merge (09d341ae6dc5222a15430e10adae5bcebd64f436)
    • Calling .merge() on a Result<List<Int>, Set<Int>> will return a Collection<Int> (their most common supertype).
  • Remove deprecation of eager-evaluating functions (a76768fa42ba0f0cc8ba07265d739007d4cd2370)
    • Rust includes these eager evaluating variants with a simple warning in their comments with regards to using the lazy variants for heavy lifting.
  • Update Gradle to 6.6.1 (30d2778d006c59c6b0a3faaee9cab5c4ab7fb3f1)
  • Update Kotlin to 1.4.0 (a662ebc0a7c838688e5b3e2d01707a29cde70456)
  • Use Kotlin 1.4's Explicit API mode (a9a0c384f45c57ed59feddb43914c7cb2e375a2d) by @Munzey
kotlin-result -

Published by michaelbull over 4 years ago

  • Add suspend variant of binding function (bd7e1244b31e59e7d26479d7673d24716f70be0d) by @Munzey
    • Allows calls to binding to be passed a block that suspends
    • Addresses #24
kotlin-result -

Published by michaelbull over 4 years ago

  • Add Result#toErrorIf (cf9582075db6b2e86e7c8bf09f18d255fe314443) by @Globegitter
    • Facilitates transforming an Ok to an Err if the value satisfies a given predicate.
  • Add Result#toErrorUnless (1000c588c01a6f1fa9133329c11681ecda112a95)
    • Facilitates transforming an Ok to an Err unless the value satisfies a given predicate.
  • Add monad comprehensions via binding block (9bcaa974ca03b9f518a1e84717f79272f4d090c2) by @Munzey
    • See the Binding section on the README for an introduction to this concept
  • Add benchmarking framework (0910e9ffe477a1daafbe1dd3fede452f2ae60bca) by @Munzey
  • Update Gradle to 6.5 (b4b2224ed24e63d9b26c4ed14dbbf2e751f91371)
  • Add unit tests for zip functions (31965ceb3d22ade6c7f45e63c289354cfaf16138) by @jvanderwee
  • Convert to multi-platform project structure (bca344daafadbc7af42089b88c712ac1061298dd) by @Munzey
kotlin-result -

Published by michaelbull over 4 years ago

  • Update gradle to 6.2-rc-2 (e4da8cf75f7568d2cc4469a241d89e8f8aca8448)
  • Replace bintray with maven central (68cabd7a1e7967168211823679161bc7be1d7242)
kotlin-result -

Published by michaelbull over 4 years ago

  • Add kotlin.code.style=official to gradle.properties (6651b18905feebdbc445741168ee7128e18c5b5e)
  • Fix typo in Result#unwrap exception message (434b8aa7fbca71a0c234bb87e94f5d8e376fc36c)
    • Fixes #8
  • Add Rust's mapOr & mapOrElse (43ebd5753a2fb3810408c573fa09505e16930f4b)
  • Replace code blocks in comments with variable references (4ed42cc40793eb485e1c61f7a082b6cea2a99c27)
kotlin-result -

Published by michaelbull almost 5 years ago

  • Support destructuring declarations (1bf21253276c1715682e4c34be6284ec66a28158)
    • val (value: String?, error: Throwable?) = runCatching(yourFunction)
  • Update dependencies (782fac0cedddb102357888e6769ad61dd3d61fdb)
  • Replace travis with github actions (b3dbc36b7623afe5dea18d2090444bcf926bfd83)
kotlin-result - 1.1.3

Published by michaelbull about 5 years ago

  • Deprecate Result.of in favour of runCatching factory function (586b260683007d3a949d746c616ebc1abcdac449)
    • Matches Kotlin's stdlib and becomes top-level
  • Update Gradle to 5.6 (db00e6154211cfa111b19ef9b2b1c718fdb5d259)
  • Update dependencies (31808eb99cd0290da7f3850952c7ef1df7a92b9c)
  • Update "Creating Results" section in README (ed430c4eca1388da70d3f9c79c5ee0a3d7c46ff9)