├── .gitignore ├── .travis.yml ├── elm.json ├── CHANGELOG.md ├── README.md ├── LICENSE ├── tests └── Tests.elm └── src └── Result └── Extra.elm /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore build or dist files 2 | /elm-stuff 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | language: elm 4 | node_js: '12' 5 | elm-format: 0.8.3 6 | -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "elm-community/result-extra", 4 | "summary": "Convenience functions for working with Result", 5 | "license": "MIT", 6 | "version": "2.4.0", 7 | "exposed-modules": [ 8 | "Result.Extra" 9 | ], 10 | "elm-version": "0.19.0 <= v < 0.20.0", 11 | "dependencies": { 12 | "elm/core": "1.0.0 <= v < 2.0.0" 13 | }, 14 | "test-dependencies": { 15 | "elm-explorations/test": "1.0.0 <= v < 2.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v2.4.0 4 | 5 | * Add additional `combine...` functions for mapping & pulling `Result` values 6 | out of lists and tuples. 7 | * Add a `join` function for merging nested `Result` values. 8 | * Add a `filter` function for validating an `Ok` value or returning an `Err`. 9 | 10 | 11 | ## v2.3.0 12 | 13 | * Improve test coverage. 14 | * Add toTask function to convert Results into Tasks. 15 | * Add error function to convert Err values into Just values. 16 | * Add partition function to split a list of Results into lists of Ok values & 17 | Err values. 18 | * Add CHANGELOG file. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Convenience functions for working with Result 2 | 3 | [![result-extra Build Status](https://travis-ci.org/elm-community/result-extra.svg?branch=master)](https://travis-ci.org/elm-community/result-extra) 4 | 5 | 6 | This package contains convenience functions for working with the `Result` type. 7 | 8 | You may want to import this module directly into the `Result` namespace: 9 | 10 | ```elm 11 | import Result.Extra as Result 12 | ``` 13 | 14 | Then you can use the included functions similarly to the `Result` module in 15 | `elm/core`. E.g., `Result.singleton 5` or `Result.isOk someResultValue`. 16 | 17 | 18 | Feedback and contributions are very welcome. 19 | 20 | 21 | ## Contributing 22 | 23 | Pull requests are welcome. If you want to talk to us, join us on the 24 | `#elm-community` channel on the [Elm Slack](https://elmlang.slack.com). You can 25 | reach the maintainer at @Lysergia on slack, or @prikhi on github. 26 | 27 | 28 | ## License 29 | 30 | The source code for this package is released under the terms of the MIT 31 | license. See the `LICENSE` file. 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2019 CircuitHub Inc., Elm Community members 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/Tests.elm: -------------------------------------------------------------------------------- 1 | module Tests exposing (all) 2 | 3 | import Expect 4 | import Fuzz 5 | import Result.Extra 6 | exposing 7 | ( andMap 8 | , combine 9 | , combineBoth 10 | , combineFirst 11 | , combineMap 12 | , combineMapBoth 13 | , combineMapFirst 14 | , combineMapSecond 15 | , combineSecond 16 | , error 17 | , extract 18 | , filter 19 | , isErr 20 | , isOk 21 | , join 22 | , mapBoth 23 | , merge 24 | , or 25 | , orElse 26 | , orElseLazy 27 | , orLazy 28 | , partition 29 | , singleton 30 | , toTask 31 | , unwrap 32 | ) 33 | import Task 34 | import Test exposing (Test, describe, fuzz, fuzz2, test) 35 | 36 | 37 | all : Test 38 | all = 39 | describe "Result.Extra" 40 | [ commonHelperTests 41 | , combiningTests 42 | , applyingTests 43 | , alternativesTests 44 | , toTaskTests 45 | ] 46 | 47 | 48 | commonHelperTests : Test 49 | commonHelperTests = 50 | describe "Common Helpers" 51 | [ test "isOk - Ok" <| 52 | \_ -> 53 | Expect.true "Expected Ok value to return True" (isOk <| Ok 2) 54 | , test "isOk - Err" <| 55 | \_ -> 56 | Expect.false "Expected Err value to return False" (isOk <| Err 42) 57 | , test "isErr - Ok" <| 58 | \_ -> 59 | Expect.false "Expected Ok value to return False" (isErr <| Ok 2) 60 | , test "isErr - Err" <| 61 | \_ -> 62 | Expect.true "Expected Err value to return True" (isErr <| Err 42) 63 | , test "extract - Ok" <| 64 | \_ -> 65 | Expect.equal (extract (always 42) <| Ok 2) 2 66 | , test "extract - Err" <| 67 | \_ -> 68 | Expect.equal (extract (\e -> e ++ " world") <| Err "hello") "hello world" 69 | , test "unwrap - Ok" <| 70 | \_ -> 71 | Expect.equal (unwrap "error" String.fromInt <| Ok 2) "2" 72 | , test "unwrap - Err" <| 73 | \_ -> 74 | Expect.equal (unwrap "error" String.fromInt <| Err 42) "error" 75 | , test "error - Ok" <| 76 | \_ -> 77 | Expect.equal (error <| Ok 2) Nothing 78 | , test "error - Err" <| 79 | \_ -> 80 | Expect.equal (error <| Err 42) <| Just 42 81 | , test "mapBoth - Ok" <| 82 | \_ -> 83 | Expect.equal (mapBoth String.fromFloat String.fromInt <| Ok 2) 84 | (Ok "2") 85 | , test "mapBoth - Err" <| 86 | \_ -> 87 | Expect.equal (mapBoth String.fromFloat String.fromInt <| Err 4.2) 88 | (Err "4.2") 89 | , test "merge - Ok" <| 90 | \_ -> 91 | Expect.equal (merge <| Ok 42) 42 92 | , test "merge - Err" <| 93 | \_ -> 94 | Expect.equal (merge <| Err 42) 42 95 | , test "join - Ok Ok" <| 96 | \_ -> 97 | Expect.equal (join <| Ok <| Ok 42) (Ok 42) 98 | , test "join - Ok Err" <| 99 | \_ -> 100 | Expect.equal (join <| Ok <| Err 42) (Err 42) 101 | , test "join - Err" <| 102 | \_ -> 103 | Expect.equal (join <| Err 42) (Err 42) 104 | , partitionTests 105 | , filterTests 106 | ] 107 | 108 | 109 | partitionTests : Test 110 | partitionTests = 111 | describe "partition" 112 | [ test "empty list" <| 113 | \_ -> 114 | Expect.equal (partition []) ( [], [] ) 115 | , test "only Ok" <| 116 | \_ -> 117 | Expect.equal (partition [ Ok 99 ]) ( [ 99 ], [] ) 118 | , test "only Err" <| 119 | \_ -> 120 | Expect.equal (partition [ Err 99 ]) ( [], [ 99 ] ) 121 | , test "mixed list" <| 122 | \_ -> 123 | Expect.equal (partition [ Ok 99, Err "Nope", Ok -5 ]) ( [ 99, -5 ], [ "Nope" ] ) 124 | ] 125 | 126 | 127 | filterTests : Test 128 | filterTests = 129 | describe "filter" 130 | [ test "err is ignored" <| 131 | \_ -> 132 | Err "previous error" 133 | |> filter "is not 1" ((==) 1) 134 | |> Expect.equal (Err "previous error") 135 | , test "ok passes filter" <| 136 | \_ -> 137 | Ok 1 138 | |> filter "is not 1" ((==) 1) 139 | |> Expect.equal (Ok 1) 140 | , test "ok filtered out" <| 141 | \() -> 142 | Ok 2 143 | |> filter "is not 1" ((==) 1) 144 | |> Expect.equal (Err "is not 1") 145 | ] 146 | 147 | 148 | combiningTests : Test 149 | combiningTests = 150 | describe "Combining" 151 | [ combineTests 152 | , combineMapTests 153 | , test "combineFirst - Ok" <| 154 | \_ -> Expect.equal (combineFirst ( Ok 42, 9001 )) <| Ok ( 42, 9001 ) 155 | , test "combineFirst - Err" <| 156 | \_ -> Expect.equal (combineFirst ( Err 42, 9001 )) <| Err 42 157 | , test "combineSecond - Ok" <| 158 | \_ -> Expect.equal (combineSecond ( 9001, Ok 42 )) <| Ok ( 9001, 42 ) 159 | , test "combineSecond - Err" <| 160 | \_ -> Expect.equal (combineSecond ( 9001, Err 42 )) <| Err 42 161 | , test "combineBoth - Ok Ok" <| 162 | \_ -> Expect.equal (combineBoth ( Ok 42, Ok 42 )) <| Ok ( 42, 42 ) 163 | , test "combineBoth - Ok Err" <| 164 | \_ -> Expect.equal (combineBoth ( Ok 42, Err 9001 )) <| Err 9001 165 | , test "combineBoth - Err Ok" <| 166 | \_ -> Expect.equal (combineBoth ( Err 9001, Ok 42 )) <| Err 9001 167 | , test "combineBoth - Err Err" <| 168 | \_ -> Expect.equal (combineBoth ( Err 9001, Err 42 )) <| Err 9001 169 | , fuzz (Fuzz.result Fuzz.string Fuzz.int) "combineMapFirst identity == combineFirst" <| 170 | \r -> Expect.equal (combineFirst ( r, "42" )) (combineMapFirst identity ( r, "42" )) 171 | , fuzz (Fuzz.result Fuzz.string Fuzz.int) "combineMapSecond identity == combineSecond" <| 172 | \r -> Expect.equal (combineSecond ( "42", r )) (combineMapSecond identity ( "42", r )) 173 | , fuzz2 (Fuzz.result Fuzz.string Fuzz.int) 174 | (Fuzz.result Fuzz.string Fuzz.int) 175 | "combineMapBoth identity identity == combineBoth" 176 | <| 177 | \r1 r2 -> 178 | Expect.equal (combineMapBoth identity identity ( r1, r2 )) <| combineBoth ( r1, r2 ) 179 | ] 180 | 181 | 182 | combineTests : Test 183 | combineTests = 184 | describe "combine" 185 | [ test "empty list" <| 186 | \_ -> 187 | Expect.equal (combine []) <| Ok [] 188 | , test "all Ok" <| 189 | \_ -> 190 | Expect.equal (combine [ Ok 42, Ok 9001 ]) <| Ok [ 42, 9001 ] 191 | , test "all Err" <| 192 | \_ -> 193 | Expect.equal (combine [ Err 42, Err 9001 ]) <| Err 42 194 | , test "Err first" <| 195 | \_ -> 196 | Expect.equal (combine [ Err 42, Ok "hi" ]) <| Err 42 197 | , test "Err last" <| 198 | \_ -> 199 | Expect.equal (combine [ Ok "tada", Err 9001 ]) <| Err 9001 200 | , test "Err middle" <| 201 | \_ -> 202 | Expect.equal (combine [ Ok "hello", Err 42, Ok "world" ]) <| Err 42 203 | ] 204 | 205 | 206 | combineMapTests : Test 207 | combineMapTests = 208 | describe "combineMap" 209 | [ test "empty list" <| 210 | \_ -> 211 | Expect.equal (combineMap identity []) (Ok []) 212 | , test "all Ok" <| 213 | \_ -> 214 | Expect.equal (combineMap (always <| Ok 42) [ 1, 2, 3, 4 ]) <| Ok [ 42, 42, 42, 42 ] 215 | , test "all Err" <| 216 | \_ -> 217 | Expect.equal (combineMap (always <| Err 42) [ 1, 2, 3, 4 ]) <| Err 42 218 | , test "mixed" <| 219 | \_ -> 220 | Expect.equal (combineMap identity [ Ok 9001, Err 42 ]) <| Err 42 221 | , fuzz2 (Fuzz.result Fuzz.string Fuzz.int) 222 | (Fuzz.result Fuzz.string Fuzz.int) 223 | "combineMap identity == combine" 224 | <| 225 | \r1 r2 -> 226 | Expect.equal (combineMap identity [ r1, r2 ]) (combine [ r1, r2 ]) 227 | ] 228 | 229 | 230 | applyingTests : Test 231 | applyingTests = 232 | describe "Applying" 233 | [ test "singleton" <| 234 | \_ -> 235 | Expect.equal (singleton 42) (Ok 42) 236 | , andMapTests 237 | ] 238 | 239 | 240 | andMapTests : Test 241 | andMapTests = 242 | describe "andMap" 243 | [ test "Err -> Err -> Err" <| 244 | \_ -> 245 | Expect.equal (Err "Oh" |> andMap (Err "No!")) (Err "Oh") 246 | , test "Err -> Ok -> Err" <| 247 | \_ -> 248 | Expect.equal (Err "Oh" |> andMap (Ok 2)) (Err "Oh") 249 | , test "Ok -> Err -> Err" <| 250 | \_ -> 251 | Expect.equal (Ok ((+) 1) |> andMap (Err "No!")) (Err "No!") 252 | , test "Ok -> Ok -> Ok" <| 253 | \_ -> 254 | Expect.equal (Ok ((+) 1) |> andMap (Ok 2)) (Ok 3) 255 | ] 256 | 257 | 258 | alternativesTests : Test 259 | alternativesTests = 260 | describe "Alternatives" 261 | [ orTests 262 | , orLazyTests 263 | , orElseTests 264 | , orElseLazyTests 265 | ] 266 | 267 | 268 | orTests : Test 269 | orTests = 270 | describe "or" 271 | [ test "Ok1 -> Ok2 -> Ok1" <| 272 | \_ -> 273 | Expect.equal (or (Ok 2) (Ok 3)) <| Ok 2 274 | , test "Ok -> Err -> Ok" <| 275 | \_ -> 276 | Expect.equal (or (Ok 2) (Err "42")) <| Ok 2 277 | , test "Err -> Ok -> Ok" <| 278 | \_ -> 279 | Expect.equal (or (Err "42") (Ok 2)) <| Ok 2 280 | , test "Err1 -> Err2 -> Err2" <| 281 | \_ -> 282 | Expect.equal (or (Err "1") (Err "2")) <| Err "2" 283 | ] 284 | 285 | 286 | orLazyTests : Test 287 | orLazyTests = 288 | describe "orLazy" 289 | [ test "Ok1 -> Ok2 -> Ok1" <| 290 | \_ -> 291 | Expect.equal (orLazy (Ok 2) (always <| Ok 3)) <| Ok 2 292 | , test "Ok -> Err -> Ok" <| 293 | \_ -> 294 | Expect.equal (orLazy (Ok 2) (always <| Err "42")) <| Ok 2 295 | , test "Err -> Ok -> Ok" <| 296 | \_ -> 297 | Expect.equal (orLazy (Err "42") (always <| Ok 2)) <| Ok 2 298 | , test "Err1 -> Err2 -> Err2" <| 299 | \_ -> 300 | Expect.equal (orLazy (Err "1") (always <| Err "2")) <| Err "2" 301 | ] 302 | 303 | 304 | orElseTests : Test 305 | orElseTests = 306 | describe "orElse" 307 | [ test "Ok1 -> Ok2 -> Ok2" <| 308 | \_ -> 309 | Expect.equal (orElse (Ok 2) (Ok 3)) <| Ok 3 310 | , test "Ok -> Err -> Ok" <| 311 | \_ -> 312 | Expect.equal (orElse (Ok 2) (Err "42")) <| Ok 2 313 | , test "Err -> Ok -> Ok" <| 314 | \_ -> 315 | Expect.equal (orElse (Err "42") (Ok 2)) <| Ok 2 316 | , test "Err1 -> Err2 -> Err1" <| 317 | \_ -> 318 | Expect.equal (orElse (Err "1") (Err "2")) <| Err "1" 319 | ] 320 | 321 | 322 | orElseLazyTests : Test 323 | orElseLazyTests = 324 | describe "orElseLazy" 325 | [ test "Ok1 -> Ok2 -> Ok2" <| 326 | \_ -> 327 | Expect.equal (orElseLazy (always <| Ok 3) (Ok 2)) <| Ok 2 328 | , test "Ok -> Err -> Ok" <| 329 | \_ -> 330 | Expect.equal (orElseLazy (always <| Err "42") (Ok 2)) <| Ok 2 331 | , test "Err -> Ok -> Ok" <| 332 | \_ -> 333 | Expect.equal (orElseLazy (always <| Ok 2) (Err "42")) <| Ok 2 334 | , test "Err1 -> Err2 -> Err1" <| 335 | \_ -> 336 | Expect.equal (orElseLazy (always <| Err "2") (Err "1")) <| Err "2" 337 | ] 338 | 339 | 340 | toTaskTests : Test 341 | toTaskTests = 342 | describe "toTask" 343 | [ test "Ok" <| 344 | \_ -> 345 | Expect.equal (toTask (Ok 4)) (Task.succeed 4) 346 | , test "Err" <| 347 | \_ -> 348 | Expect.equal (toTask (Err "Oh")) (Task.fail "Oh") 349 | ] 350 | -------------------------------------------------------------------------------- /src/Result/Extra.elm: -------------------------------------------------------------------------------- 1 | module Result.Extra exposing 2 | ( isOk, isErr, extract, unwrap, unpack, error, mapBoth, merge, join, partition, filter 3 | , combine, combineMap, combineFirst, combineSecond, combineBoth, combineMapFirst, combineMapSecond, combineMapBoth 4 | , singleton, andMap 5 | , or, orLazy, orElseLazy, orElse 6 | , toTask 7 | ) 8 | 9 | {-| Convenience functions for working with `Result`. 10 | 11 | 12 | # Common Helpers 13 | 14 | @docs isOk, isErr, extract, unwrap, unpack, error, mapBoth, merge, join, partition, filter 15 | 16 | 17 | # Combining 18 | 19 | @docs combine, combineMap, combineFirst, combineSecond, combineBoth, combineMapFirst, combineMapSecond, combineMapBoth 20 | 21 | 22 | # Applying 23 | 24 | @docs singleton, andMap 25 | 26 | 27 | # Alternatives 28 | 29 | @docs or, orLazy, orElseLazy, orElse 30 | 31 | 32 | # Conversions 33 | 34 | @docs toTask 35 | 36 | -} 37 | 38 | import Task exposing (Task) 39 | 40 | 41 | {-| Check whether the result is `Ok` without unwrapping it. 42 | -} 43 | isOk : Result e a -> Bool 44 | isOk x = 45 | case x of 46 | Ok _ -> 47 | True 48 | 49 | Err _ -> 50 | False 51 | 52 | 53 | {-| Check whether the result is `Err` without unwrapping it. 54 | -} 55 | isErr : Result e a -> Bool 56 | isErr x = 57 | case x of 58 | Ok _ -> 59 | False 60 | 61 | Err _ -> 62 | True 63 | 64 | 65 | {-| Turn a `Result e a` to an `a`, by applying the conversion 66 | function specified to the `e`. 67 | -} 68 | extract : (e -> a) -> Result e a -> a 69 | extract f x = 70 | case x of 71 | Ok a -> 72 | a 73 | 74 | Err e -> 75 | f e 76 | 77 | 78 | {-| Convert a `Result e a` to a `b` by applying a function if 79 | the `Result` is `Ok` or using the provided default value if it 80 | is an `Err`. 81 | -} 82 | unwrap : b -> (a -> b) -> Result e a -> b 83 | unwrap defaultValue okFunc result = 84 | case result of 85 | Ok ok -> 86 | okFunc ok 87 | 88 | Err _ -> 89 | defaultValue 90 | 91 | 92 | {-| Convert a `Result e a` to a `b` by applying either the first 93 | function if the `Result` is an `Err` or the second function if the 94 | `Result` is `Ok`. Both of these functions must return the same type. 95 | -} 96 | unpack : (e -> b) -> (a -> b) -> Result e a -> b 97 | unpack errFunc okFunc result = 98 | case result of 99 | Ok ok -> 100 | okFunc ok 101 | 102 | Err err -> 103 | errFunc err 104 | 105 | 106 | {-| Convert to a Maybe containing the error, if there is one. 107 | 108 | parseInt : String -> Result ParseError Int 109 | 110 | maybeParseError : String -> Maybe ParseError 111 | maybeParseError string = 112 | error (parseInt string) 113 | 114 | -} 115 | error : Result e a -> Maybe e 116 | error result = 117 | case result of 118 | Ok _ -> 119 | Nothing 120 | 121 | Err err -> 122 | Just err 123 | 124 | 125 | {-| Apply the first argument function to an `Err` and the second 126 | argument function to an `Ok` of a `Result`. 127 | -} 128 | mapBoth : (e -> f) -> (a -> b) -> Result e a -> Result f b 129 | mapBoth errFunc okFunc result = 130 | case result of 131 | Ok ok -> 132 | Ok <| okFunc ok 133 | 134 | Err err -> 135 | Err <| errFunc err 136 | 137 | 138 | 139 | -- Combining 140 | 141 | 142 | {-| Combine a list of results into a single result (holding a list). 143 | Also known as `sequence` on lists. 144 | -} 145 | combine : List (Result x a) -> Result x (List a) 146 | combine = 147 | List.foldr (Result.map2 (::)) (Ok []) 148 | 149 | 150 | {-| Map a function producing results on a list 151 | and combine those into a single result (holding a list). 152 | Also known as `traverse` on lists. 153 | 154 | combineMap f xs == combine (List.map f xs) 155 | 156 | -} 157 | combineMap : (a -> Result x b) -> List a -> Result x (List b) 158 | combineMap f = 159 | combine << List.map f 160 | 161 | 162 | {-| Pull a result out of the _first_ element of a tuple 163 | and combine it into a result holding the tuple's values. 164 | -} 165 | combineFirst : ( Result x a, c ) -> Result x ( a, c ) 166 | combineFirst ( rx, y ) = 167 | Result.map (\x -> Tuple.pair x y) rx 168 | 169 | 170 | {-| Pull a result out of the _second_ element of a tuple 171 | and combine it into a result holding the tuple's values. 172 | Also known as `sequence` on tuples. 173 | -} 174 | combineSecond : ( c, Result x a ) -> Result x ( c, a ) 175 | combineSecond ( x, ry ) = 176 | Result.map (Tuple.pair x) ry 177 | 178 | 179 | {-| Combine all results in a tuple 180 | into a single result holding the tuple's values. 181 | Also know as `bisequence` on tuples. 182 | -} 183 | combineBoth : ( Result x a, Result x b ) -> Result x ( a, b ) 184 | combineBoth ( rx, ry ) = 185 | Result.map2 Tuple.pair rx ry 186 | 187 | 188 | {-| Map a function producing results on the _first_ element of a tuple 189 | and then pull it out using `combineFirst`. 190 | Also know as `sequence` on tuples. 191 | 192 | combineMapFirst f ( x, y ) 193 | == combineFirst (Tuple.mapFirst f ( x, y )) 194 | == Result.map (flip Tuple.pair y) (f x) 195 | 196 | -} 197 | combineMapFirst : (a -> Result x b) -> ( a, c ) -> Result x ( b, c ) 198 | combineMapFirst f = 199 | combineFirst << Tuple.mapFirst f 200 | 201 | 202 | {-| Map a function producing results on the _second_ element of a tuple 203 | and then pull it out using `combineSecond`. 204 | Also know as `traverse` on tuples. 205 | 206 | combineMapSecond f ( x, y ) 207 | == combineSecond (Tuple.mapSecond f ( x, y )) 208 | == Result.map (Tuple.pair x) (f y) 209 | 210 | -} 211 | combineMapSecond : (a -> Result x b) -> ( c, a ) -> Result x ( c, b ) 212 | combineMapSecond f = 213 | combineSecond << Tuple.mapSecond f 214 | 215 | 216 | {-| Map a function producing results on the _both_ elements of a tuple 217 | and then pull them out using `combineBoth`. 218 | Also know as `bitraverse` on tuples. 219 | 220 | combineMapBoth f g ( x, y ) 221 | == combineBoth (Tuple.mapBoth f g ( x, y )) 222 | == Result.map2 Tuple.pair (f x) (g y) 223 | 224 | -} 225 | combineMapBoth : (a -> Result x c) -> (b -> Result x d) -> ( a, b ) -> Result x ( c, d ) 226 | combineMapBoth f g = 227 | combineBoth << Tuple.mapBoth f g 228 | 229 | 230 | 231 | -- Applying 232 | 233 | 234 | {-| Create a `singleton` from a value to an `Result` with a `Ok` 235 | of the same type. Also known as `pure`. You can use the `Err` 236 | constructor for a singleton of the `Err` variety. 237 | 238 | singleton 2 == Ok 2 239 | 240 | -} 241 | singleton : a -> Result e a 242 | singleton = 243 | Ok 244 | 245 | 246 | {-| Apply the function that is inside `Result` to a value that is inside 247 | `Result`. Return the result inside `Result`. If one of the `Result` 248 | arguments is `Err e`, return `Err e`. Also known as `apply`. 249 | 250 | Err "Oh" |> andMap (Err "No!") == Err "Oh" 251 | 252 | Err "Oh" |> andMap (Ok 2) == Err "Oh" 253 | 254 | Ok ((+) 1) |> andMap (Err "No!") == Err "No!" 255 | 256 | Ok ((+) 1) |> andMap (Ok 2) == Ok 3 257 | 258 | -} 259 | andMap : Result e a -> Result e (a -> b) -> Result e b 260 | andMap ra rb = 261 | case ( ra, rb ) of 262 | ( _, Err x ) -> 263 | Err x 264 | 265 | ( o, Ok fn ) -> 266 | Result.map fn o 267 | 268 | 269 | 270 | -- Alternatives 271 | 272 | 273 | {-| Like the Boolean `||` this will return the first value that is 274 | positive (`Ok`). However, unlike with `||`, both values will be 275 | computed anyway (there is no short-circuiting). 276 | 277 | or (Ok 4) (Ok 5) == Ok 4 278 | 279 | or (Err "Oh!") (Ok 5) == Ok 5 280 | 281 | or (Ok 4) (Err "No!") == Ok 4 282 | 283 | or (Err "Oh!") (Err "No!") == Err "No!" 284 | 285 | As the last example line shows, the second error is returned if both 286 | results are erroneous. 287 | 288 | -} 289 | or : Result e a -> Result e a -> Result e a 290 | or ra rb = 291 | case ra of 292 | Err _ -> 293 | rb 294 | 295 | Ok _ -> 296 | ra 297 | 298 | 299 | {-| Non-strict version of `or`. The second argument will only be 300 | evaluated if the first argument is an `Err`. 301 | -} 302 | orLazy : Result e a -> (() -> Result e a) -> Result e a 303 | orLazy ra frb = 304 | case ra of 305 | Err _ -> 306 | frb () 307 | 308 | Ok _ -> 309 | ra 310 | 311 | 312 | {-| Piping-friendly version of `orLazy`. The first argument will only 313 | be evaluated if the second argument is an `Err`. Example use: 314 | 315 | String.toInt "Hello" 316 | |> orElseLazy (\() -> String.toInt "42") 317 | 318 | -} 319 | orElseLazy : (() -> Result e a) -> Result e a -> Result e a 320 | orElseLazy fra rb = 321 | orLazy rb fra 322 | 323 | 324 | {-| Strict version of `orElseLazy` (and at the same time, 325 | piping-friendly version of `or`). 326 | 327 | orElse (Ok 4) (Ok 5) == Ok 5 -- crucial difference from `or` 328 | 329 | orElse (Err "Oh!") (Ok 5) == Ok 5 330 | 331 | orElse (Ok 4) (Err "No!") == Ok 4 332 | 333 | orElse (Err "Oh!") (Err "No!") == Err "Oh!" -- also different from `or` 334 | 335 | Also: 336 | 337 | String.toInt "Hello" 338 | |> orElse (String.toInt "42") 339 | 340 | -} 341 | orElse : Result e a -> Result e a -> Result e a 342 | orElse ra rb = 343 | or rb ra 344 | 345 | 346 | 347 | -- OTHER -- 348 | 349 | 350 | {-| Eliminate Result when error and success have been mapped to the same 351 | type, such as a message type. 352 | 353 | merge (Ok 4) == 4 354 | 355 | merge (Err -1) == -1 356 | 357 | More pragmatically: 358 | 359 | type Msg 360 | = UserTypedInt Int 361 | | UserInputError String 362 | 363 | msgFromInput : String -> Msg 364 | msgFromInput = 365 | String.toInt 366 | >> Result.mapError UserInputError 367 | >> Result.map UserTypedInt 368 | >> Result.Extra.merge 369 | 370 | -} 371 | merge : Result a a -> a 372 | merge r = 373 | case r of 374 | Ok rr -> 375 | rr 376 | 377 | Err rr -> 378 | rr 379 | 380 | 381 | {-| Join contained results with the same error into one result. 382 | 383 | Usefull if you have a "result in a result": 384 | 385 | join <| Ok (Ok 4) == Ok 4 386 | 387 | join <| Ok (Err "message") == Err "message" 388 | 389 | -} 390 | join : Result x (Result x a) -> Result x a 391 | join r = 392 | case r of 393 | Err x -> 394 | Err x 395 | 396 | Ok (Err x) -> 397 | Err x 398 | 399 | Ok (Ok a) -> 400 | Ok a 401 | 402 | 403 | {-| Partition a list of Results into two lists of values (successes 404 | and failures), much as List.partition takes a predicate and splits 405 | a list based on whether the predicate indicates success or failure. 406 | 407 | partition ( Ok 4, Err "no", Err "hi" ) == ( [ 4 ], [ "no", "hi" ] ) 408 | 409 | partition ( Err 7.1, Ok 'k', Err 9.0, Ok 'p' ) == ( [ 'k', 'p' ], [ 7.1, 9.0 ] ) 410 | 411 | -} 412 | partition : List (Result e a) -> ( List a, List e ) 413 | partition rs = 414 | List.foldr 415 | (\r ( succ, err ) -> 416 | case r of 417 | Ok v -> 418 | ( v :: succ, err ) 419 | 420 | Err v -> 421 | ( succ, v :: err ) 422 | ) 423 | ( [], [] ) 424 | rs 425 | 426 | 427 | {-| Take a `Result` and a predicate function and return a `Result` with the 428 | original value when a predicate matches. 429 | 430 | filter "is not 1" (\v -> v == 1) (Ok 1) == Ok 1 431 | 432 | filter "is not 2" (\v -> v == 2) (Ok 1) == Err "is not 2" 433 | 434 | -} 435 | filter : e -> (a -> Bool) -> Result e a -> Result e a 436 | filter err predicate result = 437 | case Result.map predicate result of 438 | Ok True -> 439 | result 440 | 441 | Ok False -> 442 | Err err 443 | 444 | Err _ -> 445 | result 446 | 447 | 448 | 449 | -- Conversions 450 | 451 | 452 | {-| Convert a `Result` to a `Task` that will fail or succeed immediately. 453 | 454 | toTask (Ok 4) == Task.succeed 4 455 | 456 | toTask (Err "msg") == Task.fail "msg" 457 | 458 | This can be helpful when the value of a succeeding Task needs to be decoded, but 459 | a failure to decode should result in a failing `Task`, not a succeeding Task 460 | containing a `Result.Err`: 461 | 462 | andThenDecode : (a -> Result x b) -> Task x a -> Task x b 463 | andThenDecode decode = 464 | Task.andThen (decode >> Result.Extra.toTask) 465 | 466 | -} 467 | toTask : Result x a -> Task x a 468 | toTask result = 469 | case result of 470 | Ok a -> 471 | Task.succeed a 472 | 473 | Err x -> 474 | Task.fail x 475 | --------------------------------------------------------------------------------