Skip to content

Commit

Permalink
Use the new Result type instead of Choice for Errors
Browse files Browse the repository at this point in the history
Closes #52
  • Loading branch information
gusty committed Dec 18, 2017
1 parent bc31dc0 commit d23d847
Show file tree
Hide file tree
Showing 16 changed files with 170 additions and 147 deletions.
1 change: 1 addition & 0 deletions docsrc/content/abstraction-applicative.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ From .Net/F#
- ``IObservable<'T>``
- ``Lazy<'T>``
- ``Async<'T>``
- ``Result<'T,'U>``
- ``Choice<'T,'U>``
- ``KeyValuePair<'Key,'T>``
- ``Map<'Key,'T>``
Expand Down
1 change: 1 addition & 0 deletions docsrc/content/abstraction-bifunctor.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Concrete implementations
From .Net/F#
- ``'T * 'U``
- ``Result<'T,'U>``
- ``Choice<'T,'U>``
- ``KeyValuePair<'T,'U>``
Expand Down
1 change: 1 addition & 0 deletions docsrc/content/abstraction-functor.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ From .Net/F#
- ``IObservable<'T>``
- ``Lazy<'T>``
- ``Async<'T>``
- ``Result<'T,'U>``
- ``Choice<'T,'U>``
- ``KeyValuePair<'Key,'T>``
- ``Map<'Key,'T>``
Expand Down
1 change: 1 addition & 0 deletions docsrc/content/abstraction-monad.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ From .Net/F#
- ``option<'T>``
- ``Lazy<'T>``
- ``Async<'T>``
- ``Result<'T,'U>``
- ``Choice<'T,'U>``
- ``Map<'Key,'T>``
- ``'Monoid * 'T``
Expand Down
5 changes: 5 additions & 0 deletions src/FSharpPlus/Converter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ type ToString with static member inline ToString (x:_ option, _:ToString) = fun
| Some a -> "Some " + ToString.Invoke k a
| None -> "None"

type ToString with static member inline ToString (x:Result<_,_>, _:ToString) = fun (k:CultureInfo) ->
match x with
| Ok a -> "Ok " + ToString.Invoke k a
| Error b -> "Error " + ToString.Invoke k b

type ToString with static member inline ToString (x:Choice<_,_>, _:ToString) = fun (k:CultureInfo) ->
match x with
| Choice1Of2 a -> "Choice1Of2 " + ToString.Invoke k a
Expand Down
58 changes: 29 additions & 29 deletions src/FSharpPlus/Error.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,62 +4,62 @@ open System
open FsControl
open FSharpPlus

/// Additional operations on Error
/// Additional operations on Result
[<RequireQualifiedAccess>]
module Error =
let inline traverse f = function Choice1Of2 x -> Map.Invoke Choice1Of2 (f x) | Choice2Of2 x -> result (Choice2Of2 x)
module Result =
let inline traverse f = function Ok x -> Map.Invoke Ok (f x) | Error x -> result (Error x)

/// Choice<'TSuccess,'TFailure> specialized in 'TFailure = Exception
/// Result<'TSuccess,'TFailure> specialized in 'TFailure = Exception
[<Runtime.CompilerServices.Extension>]
module ResultOrException =
[<Runtime.CompilerServices.Extension>]
let IsResult :Choice<_,exn> -> _ = function Choice1Of2 _ -> true | _ -> false
let IsResult :Result<_,exn> -> _ = function Ok _ -> true | _ -> false

[<Runtime.CompilerServices.Extension>]
let IsException :Choice<_,exn> -> _ = function Choice2Of2 _ -> true | _ -> false
let IsException :Result<_,exn> -> _ = function Error _ -> true | _ -> false

[<Runtime.CompilerServices.Extension>]
let Result :Choice<_,exn> -> _ = function Choice1Of2 v -> v | Choice2Of2 e -> raise e
let Result :Result<_,exn> -> _ = function Ok v -> v | Error e -> raise e

[<Runtime.CompilerServices.Extension>]
let Exception :Choice<_,exn> -> _ = function Choice2Of2 e -> e | _ -> new Exception()
let Exception :Result<_,exn> -> _ = function Error e -> e | _ -> new Exception()


/// Monad Transformer for Choice<'T, 'E>
/// Monad Transformer for Result<'T, 'E>
type ErrorT<'``monad<'choice<'t,'e>>``> = ErrorT of '``monad<'choice<'t,'e>>``

/// Basic operations on ErrorT
[<RequireQualifiedAccess>]
module ErrorT =
let run (ErrorT x) = x : '``Monad<'Choice<'T,'E>>``
let inline bind (f:'T->ErrorT<'``Monad<'Choice<'U,'E>>``>) (ErrorT m:ErrorT<'``Monad<'Choice<'T,'E>>``>) = (ErrorT (m >>= (fun a -> match a with Choice2Of2 l -> result (Choice2Of2 l) | Choice1Of2 r -> run (f r))))
let inline apply (ErrorT f:ErrorT<'``Monad<'Choice<('T -> 'U),'E>>``>) (ErrorT x:ErrorT<'``Monad<'Choice<'T,'E>>``>) = ErrorT(map Error.apply f <*> x) : ErrorT<'``Monad<'Choice<'U,'E>>``>
let inline map (f:'T->'U) (ErrorT m:ErrorT<'``Monad<'Choice<'T,'E>>``>) = ErrorT (map (Error.map f) m) :ErrorT<'``Monad<'Choice<('T -> 'U),'E>>``>
let run (ErrorT x) = x : '``Monad<'Result<'T,'E>>``
let inline bind (f:'T->ErrorT<'``Monad<'Result<'U,'E>>``>) (ErrorT m:ErrorT<'``Monad<'Result<'T,'E>>``>) = (ErrorT (m >>= (fun a -> match a with Error l -> result (Error l) | Ok r -> run (f r))))
let inline apply (ErrorT f:ErrorT<'``Monad<'Result<('T -> 'U),'E>>``>) (ErrorT x:ErrorT<'``Monad<'Result<'T,'E>>``>) = ErrorT(map Result.apply f <*> x) : ErrorT<'``Monad<'Result<'U,'E>>``>
let inline map (f:'T->'U) (ErrorT m:ErrorT<'``Monad<'Result<'T,'E>>``>) = ErrorT (map (Result.map f) m) :ErrorT<'``Monad<'Result<('T -> 'U),'E>>``>

type ErrorT<'``monad<'choice<'t,'e>>``> with
static member inline Return (x : 'T) = ErrorT (result (Choice1Of2 x)) : ErrorT<'``Monad<'Choice<'T,'E>>``>
static member inline Map (x : ErrorT<'``Monad<'Choice<'T,'E>>``>, f : 'T->'U) = ErrorT.map f x : ErrorT<'``Monad<'Choice<'U,'E>>``>
static member inline (<*>) (f : ErrorT<'``Monad<'Choice<('T -> 'U),'E>>``>, x : ErrorT<'``Monad<'Choice<'T,'E>>``>) = ErrorT.apply f x : ErrorT<'``Monad<'Choice<'U,'E>>``>
static member inline Bind (x : ErrorT<'``Monad<'Choice<'T,'E>>``>, f : 'T->ErrorT<'``Monad<'Choice<'U,'E>>``>) = ErrorT.bind f x
static member inline Return (x : 'T) = ErrorT (result (Ok x)) : ErrorT<'``Monad<'Result<'T,'E>>``>
static member inline Map (x : ErrorT<'``Monad<'Result<'T,'E>>``>, f : 'T->'U) = ErrorT.map f x : ErrorT<'``Monad<'Result<'U,'E>>``>
static member inline (<*>) (f : ErrorT<'``Monad<'Result<('T -> 'U),'E>>``>, x : ErrorT<'``Monad<'Result<'T,'E>>``>) = ErrorT.apply f x : ErrorT<'``Monad<'Result<'U,'E>>``>
static member inline Bind (x : ErrorT<'``Monad<'Result<'T,'E>>``>, f : 'T->ErrorT<'``Monad<'Result<'U,'E>>``>) = ErrorT.bind f x

static member inline Lift (x:'``Monad<'T>``) = x |> liftM Choice1Of2 |> ErrorT : ErrorT<'``Monad<Choice<'T,'E>>``>
static member inline Lift (x:'``Monad<'T>``) = x |> liftM Ok |> ErrorT : ErrorT<'``Monad<Result<'T,'E>>``>

static member inline Throw (x:'E) = x |> Choice2Of2 |> result |> ErrorT : ErrorT<'``Monad<Choice<'T,'E>>``>
static member inline Catch (ErrorT x :ErrorT<'``MonadError<'E1,'T>``>, f: 'E1 -> _) = (ErrorT (x >>= (fun a -> match a with Choice2Of2 l -> ErrorT.run (f l) | Choice1Of2 r -> result (Choice1Of2 r)))) : ErrorT<'``Monad<Choice<'T,'E2>>``>
static member inline Throw (x:'E) = x |> Error |> result |> ErrorT : ErrorT<'``Monad<Result<'T,'E>>``>
static member inline Catch (ErrorT x :ErrorT<'``MonadError<'E1,'T>``>, f: 'E1 -> _) = (ErrorT (x >>= (fun a -> match a with Error l -> ErrorT.run (f l) | Ok r -> result (Ok r)))) : ErrorT<'``Monad<Result<'T,'E2>>``>

static member inline LiftAsync (x :Async<'T>) = lift (liftAsync x) : '``ErrorT<'MonadAsync<'T>>``

static member inline CallCC (f:('T -> ErrorT<'``MonadCont<'R,Choice<'U,'E>>``>) -> _) :ErrorT<'``MonadCont<'R, Choice<'T,'E>>``> = ErrorT(callCC <| fun c -> ErrorT.run(f (ErrorT << c << Choice1Of2)))
static member inline CallCC (f:('T -> ErrorT<'``MonadCont<'R,Result<'U,'E>>``>) -> _) :ErrorT<'``MonadCont<'R, Result<'T,'E>>``> = ErrorT(callCC <| fun c -> ErrorT.run(f (ErrorT << c << Ok)))

static member inline get_Ask() = (ErrorT << (map Choice1Of2)) ask : ErrorT<'``MonadReader<'R,Choice<'R,'E>>``>
static member inline Local (ErrorT m : ErrorT<'``MonadReader<'R2,Choice<'R2,'E>>``>, f:'R1->'R2) = ErrorT (local f m)
static member inline get_Ask() = (ErrorT << (map Ok)) ask : ErrorT<'``MonadReader<'R,Result<'R,'E>>``>
static member inline Local (ErrorT m : ErrorT<'``MonadReader<'R2,Result<'R2,'E>>``>, f:'R1->'R2) = ErrorT (local f m)

static member inline Tell (w:'Monoid) = w |> tell |> lift : '``ErrorT<Writer<'Monoid,Choice<unit,'E>>>``
static member inline Listen m : ErrorT<'``MonadWriter<'Monoid,Choice<'T*'Monoid,'E>>``> =
let liftError (m, w) = Error.map (fun x -> (x, w)) m
static member inline Tell (w:'Monoid) = w |> tell |> lift : '``ErrorT<Writer<'Monoid,Result<unit,'E>>>``
static member inline Listen m : ErrorT<'``MonadWriter<'Monoid,Result<'T*'Monoid,'E>>``> =
let liftError (m, w) = Result.map (fun x -> (x, w)) m
ErrorT (listen (ErrorT.run m) >>= (result << liftError))

static member inline Pass m = ErrorT (ErrorT.run m >>= either (result << Choice2Of2) (map Choice1Of2 << pass << result)) : ErrorT<'``MonadWriter<'Monoid,Choice<'T,'E>>``>
static member inline Pass m = ErrorT (ErrorT.run m >>= either (result << Error) (map Ok << pass << result)) : ErrorT<'``MonadWriter<'Monoid,Result<'T,'E>>``>

static member inline get_Get() = lift get : '``ErrorT<'MonadState<'S,Choice<_,'E>>>``
static member inline Put (x:'S) = x |> put |> lift : '``ErrorT<'MonadState<'S,Choice<_,'E>>>``
static member inline get_Get() = lift get : '``ErrorT<'MonadState<'S,Result<_,'E>>>``
static member inline Put (x:'S) = x |> put |> lift : '``ErrorT<'MonadState<'S,Result<_,'E>>>``
23 changes: 20 additions & 3 deletions src/FSharpPlus/Extensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,25 @@ module Option =
| _ -> None


/// Additional operations on Error
/// Additional operations on Result<'Ok,'Error>
[<RequireQualifiedAccess>]
module Error =
module Result =
let map f = function Ok x -> Ok(f x) | Error x -> Error x
let apply f x =
match (f, x) with
| (Ok a, Ok b) -> Ok (a b)
| (Error a, _) -> Error a
| (_, Error b) -> Error b: Result<'b,'e>
let result x = Ok x
let throw x = Error x
let bind (f:'t -> Result<'v,'e>) = function Ok v -> f v | Error e -> Error e
let inline catch (f:'t -> Result<'v,'e>) = function Ok v -> Ok v | Error e -> f e
let inline either f g = function Ok x -> f x | Error y -> g y


/// Additional operations on Choice
[<RequireQualifiedAccess>]
module Choice =
let map f = function Choice1Of2 x -> Choice1Of2(f x) | Choice2Of2 x -> Choice2Of2 x
let apply f x =
match (f,x) with
Expand All @@ -22,7 +38,8 @@ module Error =
let result x = Choice1Of2 x
let throw x = Choice2Of2 x
let bind (f:'t -> Choice<'v,'e>) = function Choice1Of2 v -> f v | Choice2Of2 e -> Choice2Of2 e
let inline catch (f:'t -> Choice<'v,'e>) = function Choice1Of2 v -> Choice1Of2 v | Choice2Of2 e -> f e
let inline catch (f:'t -> Choice<'v,'e>) = function Choice1Of2 v -> Choice1Of2 v | Choice2Of2 e -> f e
let inline either f g = function Choice2Of2 x -> f x | Choice1Of2 y -> g y


/// Additional operations on Seq
Expand Down

2 comments on commit d23d847

@gusty
Copy link
Member Author

@gusty gusty commented on d23d847 Dec 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change, but since we're still behind the 1.0.0 release changes like this should be expected.

The main idea is that Result<'T,'U> is preferred over Choice<'T,'U> to model effects which represents computations that might fail. But Choice will still be preferred to model computations where the second parameter doesn't necessarily represents an error.

Summary:

  • The module with Extensions previously called Error is now renamed to Choice and there is a new module called Result. They both contains exactly the same functions. These names are less ambiguous and also force you to decide which model to follow when migrating old code.

  • The generic function either now operates over Result<'T,'U>. The old version is still available as Choice.either.

  • Choice Functors, Applicatives and Monads will continue to work on Choice, Result was added as another instance.

  • ArrowChoice will keep using Choice because AFAIK they're not necessarily modelling errors.

  • The dummy type Either is no longer required, we can use Choice to ambiguate in overload resolution. This is more an internal change.

  • The monad transformer ErrorT operates with Result.

  • Choice prisms were removed in favor of _Ok and _Error. They can be added again in the future but right now will force you to migrate.

@wallymathieu
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Please sign in to comment.