[RFC FS-1004 Discussion] Result type #49

Closed
enricosada opened this Issue Feb 14, 2016 · 154 comments

Comments

Projects
None yet
@enricosada
Contributor

enricosada commented Feb 14, 2016

This issue is used to track discussions of F# RFC FS-1004 - "Result type". Please discuss in thread below (if necessary)

@enricosada enricosada referenced this issue in Microsoft/visualfsharp Feb 14, 2016

Merged

[RFC FS-1004] FSharpResult type with a few tests #964

@forki

This comment has been minimized.

Show comment
Hide comment
@forki

forki Feb 14, 2016

Member

In Microsoft/visualfsharp#964 @wallymathieu is proposing to add:

/// <summary>Helper type for error handling without exceptions.</summary>
[<StructuralEquality; StructuralComparison>]
[<CompiledName("FSharpResult`2")>]
type Result<'T1,'T2> = 
| Success of 'T1 
| Error of 'T2

Is that really the type we want for Results? Chessie is using:

/// Represents the result of a computation.
type Result<'TSuccess, 'TMessage> = 
/// Represents the result of a successful computation.
| Ok of 'TSuccess * 'TMessage list
/// Represents the result of a failed computation.
| Bad of 'TMessage list

If we add a specialized type to FSharp.Core then we should really think about it's usage. the proposed type is maybe too generic and basically only an alias for Choice.

Member

forki commented Feb 14, 2016

In Microsoft/visualfsharp#964 @wallymathieu is proposing to add:

/// <summary>Helper type for error handling without exceptions.</summary>
[<StructuralEquality; StructuralComparison>]
[<CompiledName("FSharpResult`2")>]
type Result<'T1,'T2> = 
| Success of 'T1 
| Error of 'T2

Is that really the type we want for Results? Chessie is using:

/// Represents the result of a computation.
type Result<'TSuccess, 'TMessage> = 
/// Represents the result of a successful computation.
| Ok of 'TSuccess * 'TMessage list
/// Represents the result of a failed computation.
| Bad of 'TMessage list

If we add a specialized type to FSharp.Core then we should really think about it's usage. the proposed type is maybe too generic and basically only an alias for Choice.

@enricosada

This comment has been minimized.

Show comment
Hide comment
@enricosada

enricosada Feb 14, 2016

Contributor

I think it's good to have a shared (in whole f# ecosystem) Success/Failure type.
But I think is wrong to add more functions in FSharp.Core.
External libraries like Chessie can add additional functions using the Result type.
Maybe the only function i'd like to add is Result.ofChoice: Choice1Of2 -> Result but maybe it's not needed

@swlaschin do you have a feedback to add? because of Railway Oriented Programming

Contributor

enricosada commented Feb 14, 2016

I think it's good to have a shared (in whole f# ecosystem) Success/Failure type.
But I think is wrong to add more functions in FSharp.Core.
External libraries like Chessie can add additional functions using the Result type.
Maybe the only function i'd like to add is Result.ofChoice: Choice1Of2 -> Result but maybe it's not needed

@swlaschin do you have a feedback to add? because of Railway Oriented Programming

@wallymathieu

This comment has been minimized.

Show comment
Hide comment
@wallymathieu

wallymathieu Feb 14, 2016

@forki Compared to Chessie you could either create a specialised type or an alias : RopResult<'TSuccess, 'TMessage> = Result<'TSuccess*'TMessage list,'TMessage list>
The difference with Choice is that this type tells you that one of the states is a failure and the other is a success. Choice1Of2 does not read like Success.

@forki Compared to Chessie you could either create a specialised type or an alias : RopResult<'TSuccess, 'TMessage> = Result<'TSuccess*'TMessage list,'TMessage list>
The difference with Choice is that this type tells you that one of the states is a failure and the other is a success. Choice1Of2 does not read like Success.

@forki

This comment has been minimized.

Show comment
Hide comment
@forki

forki Feb 14, 2016

Member

Yes, but IMHO this alone is not useful enough to justify this type in the FSharp.Core

Member

forki commented Feb 14, 2016

Yes, but IMHO this alone is not useful enough to justify this type in the FSharp.Core

@wallymathieu

This comment has been minimized.

Show comment
Hide comment
@wallymathieu

wallymathieu Feb 14, 2016

@swlaschin has provided an example of railway oriented programming, if you use an alias for the type in Railway-Oriented-Programming-Example you need to add a few more type signatures.

@swlaschin has provided an example of railway oriented programming, if you use an alias for the type in Railway-Oriented-Programming-Example you need to add a few more type signatures.

@haf

This comment has been minimized.

Show comment
Hide comment
@haf

haf Feb 14, 2016

The success case should not contain a list of messages.

haf commented Feb 14, 2016

The success case should not contain a list of messages.

@forki

This comment has been minimized.

Show comment
Hide comment
@forki

forki Feb 14, 2016

Member

Yes that is something we discussed in chessie and I'm not convinced that it is a good design. It came from the idea to trace the origin of a calculation (which is useful in some cases - and yes there are probably better ways to do that). But this shows that it is not that easy to find the right type.

Member

forki commented Feb 14, 2016

Yes that is something we discussed in chessie and I'm not convinced that it is a good design. It came from the idea to trace the origin of a calculation (which is useful in some cases - and yes there are probably better ways to do that). But this shows that it is not that easy to find the right type.

@mexx

This comment has been minimized.

Show comment
Hide comment
@mexx

mexx Feb 14, 2016

Contributor

IMHO technically Choice is as good as Result would be besides the mentioned addition of the semantic. However this semantic can be added by providing an alias with some supporting functions and active patterns. For now I would vote against this addition.

Contributor

mexx commented Feb 14, 2016

IMHO technically Choice is as good as Result would be besides the mentioned addition of the semantic. However this semantic can be added by providing an alias with some supporting functions and active patterns. For now I would vote against this addition.

@haf

This comment has been minimized.

Show comment
Hide comment
@haf

haf Feb 14, 2016

It can't be in an outside library, because that library is an extra dependency. Choice is quite bad as a name, so I agree with the suggestion in general. Users of Suave get confused when we return Choice<string, string>.

haf commented Feb 14, 2016

It can't be in an outside library, because that library is an extra dependency. Choice is quite bad as a name, so I agree with the suggestion in general. Users of Suave get confused when we return Choice<string, string>.

@wallymathieu

This comment has been minimized.

Show comment
Hide comment
@wallymathieu

wallymathieu Feb 14, 2016

Semantics are important. As a developer reading Choice<string, string> I don't see anything indicating that there is an error (in the same way option string * option string does not tell you that you expect exactly one to have a value). However, using Result<string, string> the type system tells me that the second type parameter is the type of the potential error.

Semantics are important. As a developer reading Choice<string, string> I don't see anything indicating that there is an error (in the same way option string * option string does not tell you that you expect exactly one to have a value). However, using Result<string, string> the type system tells me that the second type parameter is the type of the potential error.

@forki

This comment has been minimized.

Show comment
Hide comment
@forki

forki Feb 14, 2016

Member

Yes we all agree on this, but it doesn't mean that this one way to encapsulate results is the best. "the best" is probably not needed, but for Fsharp.core we should be really careful to select one that is useful in many scenarios.

If you look at rust then you see a community that embraced the Result in the whole standard crates, but somehow some people are now discussing if this was a good idea.

I personally still think that Result is a good idea. But just adding the base type is not that useful for me. I always need methods to wrap and unwrap. FSharpx and EntCore have these for Choice and Chessie has these for its own Result type.
If we really add result type, then we should also look which standard methods we can add to return proper results instead of exceptions.

Member

forki commented Feb 14, 2016

Yes we all agree on this, but it doesn't mean that this one way to encapsulate results is the best. "the best" is probably not needed, but for Fsharp.core we should be really careful to select one that is useful in many scenarios.

If you look at rust then you see a community that embraced the Result in the whole standard crates, but somehow some people are now discussing if this was a good idea.

I personally still think that Result is a good idea. But just adding the base type is not that useful for me. I always need methods to wrap and unwrap. FSharpx and EntCore have these for Choice and Chessie has these for its own Result type.
If we really add result type, then we should also look which standard methods we can add to return proper results instead of exceptions.

@forki

This comment has been minimized.

Show comment
Hide comment
@forki

forki Feb 14, 2016

Member

(Sorry if I sound too negative. I'm not against this, but working on Chessie made it very clear to me that it's not that easy to make that idea work well in practice.)

Member

forki commented Feb 14, 2016

(Sorry if I sound too negative. I'm not against this, but working on Chessie made it very clear to me that it's not that easy to make that idea work well in practice.)

@wallymathieu

This comment has been minimized.

Show comment
Hide comment
@wallymathieu

wallymathieu Feb 14, 2016

What kind of problems did you have in Chessie?

What kind of problems did you have in Chessie?

@forki

This comment has been minimized.

Show comment
Hide comment
@forki

forki Feb 14, 2016

Member

Chessie was extracted for the Rop usage in Paket and then refactored a couple of times in the hope to make it useful in more situations. So we changed designs a couple of times, but I think we are still not happy. So what i want to say is: I have no solution, but we should not rush it. Can you come up with a list of pros and cons that you see with the current design?

Member

forki commented Feb 14, 2016

Chessie was extracted for the Rop usage in Paket and then refactored a couple of times in the hope to make it useful in more situations. So we changed designs a couple of times, but I think we are still not happy. So what i want to say is: I have no solution, but we should not rush it. Can you come up with a list of pros and cons that you see with the current design?

@wallymathieu

This comment has been minimized.

Show comment
Hide comment
@wallymathieu

wallymathieu Feb 14, 2016

Pro and cons of the different designs.

Lets start by analysing the Railroad oriented programming approach

When we look at the type defined in both Chessie and in the example by @swlaschin we see that it's an approach that mixes Log and Result.

How do we infer this?

If we look at 'TMessage list as Log<'TMessage>:

type RopResult<'TSuccess, 'TMessage> = 
| Ok of 'TSuccess * Log<'TMessage>
| Bad of Log<'TMessage>

So in reality this is equivalent to:
Log<'TMessage> * ('TSuccess option)
Since you don't know if a "message" is from a previous successful computation or not (just by looking at the type).

From a railway oriented programming-perspective this makes sense. The downside of this is that you don't have any specific message-type associated with failure. The nice thing about this approach is that you can combine messages and recover from failure and then fail again (and avoid losing any message along the way).

What if we analyse the Choice-like approach

type Result<'TSuccess, 'TError> = 
| Success of 'TSuccess 
| Failure of 'TError

In the case that when we have a failure we will get some enumeration that describes the failure, then a simple Choice<CustomerState, CustomerValidationFailure list> is quite sufficient.

What about the case when we potentially have a customer as the result of our operation, or we have gotten string (say from a web service) that explains why things have failed, then we might want to model it as Result<Customer, string>.

For instance in Suave we have the case that a value is found or a failure description is returned. This we would like to model as Result<string, string>. Since the failure does not have any type information other than string, wrapping it in Result helps explain what we are returning.

A mix of ROP and Choice-like approach

It would be interesting to look at Chessie but with the following type declared:
RopResult<'TMessage,'TSuccess,'TError> = Log<'TMessage> * Result<'TSuccess, 'TError>

Pro and cons of the different designs.

Lets start by analysing the Railroad oriented programming approach

When we look at the type defined in both Chessie and in the example by @swlaschin we see that it's an approach that mixes Log and Result.

How do we infer this?

If we look at 'TMessage list as Log<'TMessage>:

type RopResult<'TSuccess, 'TMessage> = 
| Ok of 'TSuccess * Log<'TMessage>
| Bad of Log<'TMessage>

So in reality this is equivalent to:
Log<'TMessage> * ('TSuccess option)
Since you don't know if a "message" is from a previous successful computation or not (just by looking at the type).

From a railway oriented programming-perspective this makes sense. The downside of this is that you don't have any specific message-type associated with failure. The nice thing about this approach is that you can combine messages and recover from failure and then fail again (and avoid losing any message along the way).

What if we analyse the Choice-like approach

type Result<'TSuccess, 'TError> = 
| Success of 'TSuccess 
| Failure of 'TError

In the case that when we have a failure we will get some enumeration that describes the failure, then a simple Choice<CustomerState, CustomerValidationFailure list> is quite sufficient.

What about the case when we potentially have a customer as the result of our operation, or we have gotten string (say from a web service) that explains why things have failed, then we might want to model it as Result<Customer, string>.

For instance in Suave we have the case that a value is found or a failure description is returned. This we would like to model as Result<string, string>. Since the failure does not have any type information other than string, wrapping it in Result helps explain what we are returning.

A mix of ROP and Choice-like approach

It would be interesting to look at Chessie but with the following type declared:
RopResult<'TMessage,'TSuccess,'TError> = Log<'TMessage> * Result<'TSuccess, 'TError>

@mexx

This comment has been minimized.

Show comment
Hide comment
@mexx

mexx Feb 15, 2016

Contributor

My thoughts on Chessie way of ROP

As @forki mentioned Chessie got multiple refactoring trying to tide the design of Result. And as also mentioned not all are happy and so do I. In my understandings the concept of Trace should be made visible somehow. @wallymathieu also mentioned this flaw in his analysis. I think the mix of ROP and Choise-like approach would fit my conceptual model at best. In it the trace message can be of different type as the error one. I can even imagine to have a separate library to handle the tracing aspect, as it's orthogonal to the result and can be seen as Trace<'Message, 'Result> = Log<'Message> * 'Result. By this definition even a non Result typed function can be traced.

Extra dependency

It can't be in an outside library, because that library is an extra dependency.

@haf can you elaborate why an extra dependency is bad, we always have to depend on some library. Why is to depend on one more is bad? A separate library has advantages especially with the current release cycle of FSharp.Core.
IMHO the only advantage to have it defined in FSharp.Core would be when it would be eagerly used as the result of functions provided by FSharp.Core.

Contributor

mexx commented Feb 15, 2016

My thoughts on Chessie way of ROP

As @forki mentioned Chessie got multiple refactoring trying to tide the design of Result. And as also mentioned not all are happy and so do I. In my understandings the concept of Trace should be made visible somehow. @wallymathieu also mentioned this flaw in his analysis. I think the mix of ROP and Choise-like approach would fit my conceptual model at best. In it the trace message can be of different type as the error one. I can even imagine to have a separate library to handle the tracing aspect, as it's orthogonal to the result and can be seen as Trace<'Message, 'Result> = Log<'Message> * 'Result. By this definition even a non Result typed function can be traced.

Extra dependency

It can't be in an outside library, because that library is an extra dependency.

@haf can you elaborate why an extra dependency is bad, we always have to depend on some library. Why is to depend on one more is bad? A separate library has advantages especially with the current release cycle of FSharp.Core.
IMHO the only advantage to have it defined in FSharp.Core would be when it would be eagerly used as the result of functions provided by FSharp.Core.

@wallymathieu

This comment has been minimized.

Show comment
Hide comment
@wallymathieu

wallymathieu Feb 15, 2016

Extra dependency: usually the problem is that using a library with many dependencies in .net is still kind of flaky. Having a library like suave without other dependencies makes things easier for the consumers of that library.

Extra dependency: usually the problem is that using a library with many dependencies in .net is still kind of flaky. Having a library like suave without other dependencies makes things easier for the consumers of that library.

@eiriktsarpalis

This comment has been minimized.

Show comment
Hide comment
@eiriktsarpalis

eiriktsarpalis Feb 15, 2016

My feeling on this is that there are as many possible result types as there are monad implementations with some support for error handling baked in. The problem with any result type beyond choice is that it is necessarily ad-hoc and it really depends on the type of effect we wish manage on the specific monad implementation.

My feeling on this is that there are as many possible result types as there are monad implementations with some support for error handling baked in. The problem with any result type beyond choice is that it is necessarily ad-hoc and it really depends on the type of effect we wish manage on the specific monad implementation.

@radekm

This comment has been minimized.

Show comment
Hide comment
@radekm

radekm Feb 17, 2016

    | Bad of 'TMessage list

IMO list is not suitable since it has slow concatenation.

radekm commented Feb 17, 2016

    | Bad of 'TMessage list

IMO list is not suitable since it has slow concatenation.

@neoeinstein

This comment has been minimized.

Show comment
Hide comment
@neoeinstein

neoeinstein Feb 17, 2016

Contributor

How is this any better than declaring type Result<'a,'b> = Choice<'a,'b> and providing active patterns that match "Success" or "Failure" to Choice1Of2 and Choice2Of2?

One issue that I have with the Chessie types is that I have to re-implement many of my Choice utility functions to also work with Result. In some cases, I would have some functions that were using the Chessie types and some dependencies that used the more portable Choice model. Then I would have to convert between Choice and Result back and forth. It got quite irritating, and so I switched to a type alias: type Result<'a,'b> = Choice<'a,'b list> which has worked much better for me.

@haf I think that returning a Choice<string, 'TFailure> with an appropriate supporting failure type would be better. Couldn't Suave also just do a similar type alias?

@radekm How big are you expecting your error listing to be? And how often are you expecting that the "Bad" path chains multiple failures together? I haven't seen any cases for myself where performance on the failure path has been a problem.

Contributor

neoeinstein commented Feb 17, 2016

How is this any better than declaring type Result<'a,'b> = Choice<'a,'b> and providing active patterns that match "Success" or "Failure" to Choice1Of2 and Choice2Of2?

One issue that I have with the Chessie types is that I have to re-implement many of my Choice utility functions to also work with Result. In some cases, I would have some functions that were using the Chessie types and some dependencies that used the more portable Choice model. Then I would have to convert between Choice and Result back and forth. It got quite irritating, and so I switched to a type alias: type Result<'a,'b> = Choice<'a,'b list> which has worked much better for me.

@haf I think that returning a Choice<string, 'TFailure> with an appropriate supporting failure type would be better. Couldn't Suave also just do a similar type alias?

@radekm How big are you expecting your error listing to be? And how often are you expecting that the "Bad" path chains multiple failures together? I haven't seen any cases for myself where performance on the failure path has been a problem.

@smoothdeveloper

This comment has been minimized.

Show comment
Hide comment
@smoothdeveloper

smoothdeveloper Feb 18, 2016

Contributor

Choice<'a, 'b> doesn't convey so well which case is the result (apparent with the type I guess but Choice1Of2 doesn't read like Result to me) so the fact it is isomorphic with proposed type isn't much a concern.

https://hackage.haskell.org/package/base-4.8.2.0/docs/Data-Either.html

I don't think Result should embed a list of messages (can be either added by wrapping the Result type, or as type argument if only relevant to one of the cases).

Which function of FSharp.Core relies on Choice?

Contributor

smoothdeveloper commented Feb 18, 2016

Choice<'a, 'b> doesn't convey so well which case is the result (apparent with the type I guess but Choice1Of2 doesn't read like Result to me) so the fact it is isomorphic with proposed type isn't much a concern.

https://hackage.haskell.org/package/base-4.8.2.0/docs/Data-Either.html

I don't think Result should embed a list of messages (can be either added by wrapping the Result type, or as type argument if only relevant to one of the cases).

Which function of FSharp.Core relies on Choice?

@radekm

This comment has been minimized.

Show comment
Hide comment
@radekm

radekm Feb 19, 2016

How big are you expecting your error listing to be?

You can write functions which process millions of records, so the Result type should be able to handle similar number of errors.

And how often are you expecting that the "Bad" path chains multiple failures together?

I use applicative functor for validation quite often - it's more user friendly to gather as many errors as possible instead of stopping after the first error.


An interesting article related to this topic is How to fail – introducing Or_error.t.

radekm commented Feb 19, 2016

How big are you expecting your error listing to be?

You can write functions which process millions of records, so the Result type should be able to handle similar number of errors.

And how often are you expecting that the "Bad" path chains multiple failures together?

I use applicative functor for validation quite often - it's more user friendly to gather as many errors as possible instead of stopping after the first error.


An interesting article related to this topic is How to fail – introducing Or_error.t.

@radekm

This comment has been minimized.

Show comment
Hide comment
@radekm

radekm Feb 19, 2016

Choice<'a, 'b> doesn't convey so well which case is the result (apparent with the type I guess but Choice1Of2 doesn't read like Result to me) so the fact it is isomorphic with proposed type isn't much a concern.

@smoothdeveloper Good point, I agree.

radekm commented Feb 19, 2016

Choice<'a, 'b> doesn't convey so well which case is the result (apparent with the type I guess but Choice1Of2 doesn't read like Result to me) so the fact it is isomorphic with proposed type isn't much a concern.

@smoothdeveloper Good point, I agree.

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Feb 22, 2016

Contributor

Meta point - I'm very glad to see the lengthy discussion (also based on real-world experience) - just what I was hoping for from our first forays into using RFCs!

Contributor

dsyme commented Feb 22, 2016

Meta point - I'm very glad to see the lengthy discussion (also based on real-world experience) - just what I was hoping for from our first forays into using RFCs!

@enricosada

This comment has been minimized.

Show comment
Hide comment
@enricosada

enricosada Mar 2, 2016

Contributor

Our cousin, ocaml 4.03 added a result type

type ('a,'b) result = Ok of 'a | Error of 'b

I think Ok instead of Success is better because it's short. It's something we are going to write a lot.

Otherwise i think Success/Failure is better pair than Success/Error , because Failure is more an antonym than Error. And Failure doesnt mean an Error, only the bad choice happened

So :+1 Ok/Error because it's already used (ocaml/rust/elixir) so it's better to use a standard if possibile.
Another good option is Ok/Failure.

I'll write a table with proposed naming, existing implementations with links to lang design discussions (if possibile).

Another possibility is like rust, Ok/Err. Bonus point Err has less problems if a type Error is already defined in user code (compatibility).

About type, i think it must be generic on both sides, so Ok of 'a | Error of 'b .
Because only the application/library know what is the info it need for Error path (it's easier to map the error info if needed)

Contributor

enricosada commented Mar 2, 2016

Our cousin, ocaml 4.03 added a result type

type ('a,'b) result = Ok of 'a | Error of 'b

I think Ok instead of Success is better because it's short. It's something we are going to write a lot.

Otherwise i think Success/Failure is better pair than Success/Error , because Failure is more an antonym than Error. And Failure doesnt mean an Error, only the bad choice happened

So :+1 Ok/Error because it's already used (ocaml/rust/elixir) so it's better to use a standard if possibile.
Another good option is Ok/Failure.

I'll write a table with proposed naming, existing implementations with links to lang design discussions (if possibile).

Another possibility is like rust, Ok/Err. Bonus point Err has less problems if a type Error is already defined in user code (compatibility).

About type, i think it must be generic on both sides, so Ok of 'a | Error of 'b .
Because only the application/library know what is the info it need for Error path (it's easier to map the error info if needed)

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Mar 2, 2016

Contributor

BTW the ExtCore Choice<'T,'Error> functions are definitely relevant here: https://github.com/jack-pappas/ExtCore/blob/180138b6ccf77b5e1d4e411b6c1de2c428839a1f/ExtCore/Pervasive.fs#L1015

Contributor

dsyme commented Mar 2, 2016

BTW the ExtCore Choice<'T,'Error> functions are definitely relevant here: https://github.com/jack-pappas/ExtCore/blob/180138b6ccf77b5e1d4e411b6c1de2c428839a1f/ExtCore/Pervasive.fs#L1015

@huwman

This comment has been minimized.

Show comment
Hide comment
@huwman

huwman Mar 4, 2016

Hi all,
Excited about the possibility of having the Result type in F# core.
I have been using the following result type and useful functions inspired by elm for a while:

type Result<'Err, 'Value> = 
    | Error of 'Err
    | Success of 'Value

See the snippet with the related functions.

It is important to me that the Result type be usable not only from F#, in my opinion compiling the type as FSharpResult makes it less usable and feel foreign in C#. I also feel that for it to be worth including in the core the related functions for wrapping, unwrapping and chaining be included also.

huwman commented Mar 4, 2016

Hi all,
Excited about the possibility of having the Result type in F# core.
I have been using the following result type and useful functions inspired by elm for a while:

type Result<'Err, 'Value> = 
    | Error of 'Err
    | Success of 'Value

See the snippet with the related functions.

It is important to me that the Result type be usable not only from F#, in my opinion compiling the type as FSharpResult makes it less usable and feel foreign in C#. I also feel that for it to be worth including in the core the related functions for wrapping, unwrapping and chaining be included also.

@haf

This comment has been minimized.

Show comment
Hide comment
@haf

haf Mar 4, 2016

+1 for Ok, since it's short and sweet.

haf commented Mar 4, 2016

+1 for Ok, since it's short and sweet.

@vasily-kirichenko

This comment has been minimized.

Show comment
Hide comment
@vasily-kirichenko

vasily-kirichenko Mar 5, 2016

  1. type Result<'a, 'e> = Choice<'a, 'e>

  2. Use https://github.com/jack-pappas/ExtCore/blob/180138b6ccf77b5e1d4e411b6c1de2c428839a1f/ExtCore/Pervasive.fs#L1015 functions over it, as Don said.

  3. Use the following computation expressions:

    • Choice
    • ReaderChoice
    • ProtectedState
    • ReaderProtectedState
    • StatefulChoice
    • AsyncChoice
    • AsyncReaderChoice
    • AsyncProtectedState
    • AsyncStatefulChoice

    see here https://github.com/jack-pappas/ExtCore/blob/180138b6ccf77b5e1d4e411b6c1de2c428839a1f/ExtCore/Control.fs#L786

It's possible to define Result as a new type, then port all this above stuff into FSharpCore, but we will end up with two incompatible types. I understand that F# community is still don't use Either monad widely, but our team, for example, have used Choice + ExtCore for about 3 years now and it would be quite painful to move to Result (and it would be possible only if all the CEs and functions above were ported).

  1. type Result<'a, 'e> = Choice<'a, 'e>

  2. Use https://github.com/jack-pappas/ExtCore/blob/180138b6ccf77b5e1d4e411b6c1de2c428839a1f/ExtCore/Pervasive.fs#L1015 functions over it, as Don said.

  3. Use the following computation expressions:

    • Choice
    • ReaderChoice
    • ProtectedState
    • ReaderProtectedState
    • StatefulChoice
    • AsyncChoice
    • AsyncReaderChoice
    • AsyncProtectedState
    • AsyncStatefulChoice

    see here https://github.com/jack-pappas/ExtCore/blob/180138b6ccf77b5e1d4e411b6c1de2c428839a1f/ExtCore/Control.fs#L786

It's possible to define Result as a new type, then port all this above stuff into FSharpCore, but we will end up with two incompatible types. I understand that F# community is still don't use Either monad widely, but our team, for example, have used Choice + ExtCore for about 3 years now and it would be quite painful to move to Result (and it would be possible only if all the CEs and functions above were ported).

@smoothdeveloper

This comment has been minimized.

Show comment
Hide comment
@smoothdeveloper

smoothdeveloper Mar 5, 2016

Contributor

Can type alias be defined for the DU cases themselves?

I really think the naming of choice types cases was the main obstacle from their more widespread adoption.

With the isomorphism standpoint, byte is isomorphic to char, I think what the naming conveys (and functions consuming/exporting the type) is also important.

Speaking of migrating code bases relying on Choice to another type, why migrate it, maybe new code can rely on new one and have a functions to convert from/to in the code at boundary? It is not elegant but does the job until changes get propagated little by little.

Nice to share your experience BTW!

Contributor

smoothdeveloper commented Mar 5, 2016

Can type alias be defined for the DU cases themselves?

I really think the naming of choice types cases was the main obstacle from their more widespread adoption.

With the isomorphism standpoint, byte is isomorphic to char, I think what the naming conveys (and functions consuming/exporting the type) is also important.

Speaking of migrating code bases relying on Choice to another type, why migrate it, maybe new code can rely on new one and have a functions to convert from/to in the code at boundary? It is not elegant but does the job until changes get propagated little by little.

Nice to share your experience BTW!

@vasily-kirichenko

This comment has been minimized.

Show comment
Hide comment
@vasily-kirichenko

vasily-kirichenko Mar 5, 2016

@smoothdeveloper You cannot define aliases for DU cases, but you can define an active pattern and type aliases which make new type illusion:

let inline (|Ok|Err|) x =
    match x with
    | Choice1Of2 x -> Ok x
    | Choice2Of2 e -> Err e

let inline Ok x = Choice1Of2 x
let inline Err e = Choice2Of2 e
let inline (>>=) m f = match m with Ok x -> f x | Err e -> Err e

type ResultBuilder() =
    member inline __.Bind(m, f) = m >>= f
    member inline __.Return x = Ok x

let result = ResultBuilder()

let r =
    result {
        let! a = 
            match Ok 1 with
            | Ok x -> Ok (x + 1)
            | Err _ -> Ok 0

        let! b = Ok 2
        let! c = Err 3
        return a + b       
    }

open ExtCore.Control

let r' =
    // works with ExtCore CEs as well!
    choice {
        let! a = 
            match Ok 1 with
            | Ok x -> Ok (x + 1)
            | Err _ -> Ok 0

        let! b = Ok 2
        return a + b        
    }

@smoothdeveloper You cannot define aliases for DU cases, but you can define an active pattern and type aliases which make new type illusion:

let inline (|Ok|Err|) x =
    match x with
    | Choice1Of2 x -> Ok x
    | Choice2Of2 e -> Err e

let inline Ok x = Choice1Of2 x
let inline Err e = Choice2Of2 e
let inline (>>=) m f = match m with Ok x -> f x | Err e -> Err e

type ResultBuilder() =
    member inline __.Bind(m, f) = m >>= f
    member inline __.Return x = Ok x

let result = ResultBuilder()

let r =
    result {
        let! a = 
            match Ok 1 with
            | Ok x -> Ok (x + 1)
            | Err _ -> Ok 0

        let! b = Ok 2
        let! c = Err 3
        return a + b       
    }

open ExtCore.Control

let r' =
    // works with ExtCore CEs as well!
    choice {
        let! a = 
            match Ok 1 with
            | Ok x -> Ok (x + 1)
            | Err _ -> Ok 0

        let! b = Ok 2
        return a + b        
    }
@mexx

This comment has been minimized.

Show comment
Hide comment
@mexx

mexx Mar 5, 2016

Contributor

I can see only one thing from the above discussion, we need an easy way to provide aliases for DU cases.

Sidenote: @smoothdeveloper byte is not isomorphic to char. Character has a much wider range than just 256 available byte values.

Contributor

mexx commented Mar 5, 2016

I can see only one thing from the above discussion, we need an easy way to provide aliases for DU cases.

Sidenote: @smoothdeveloper byte is not isomorphic to char. Character has a much wider range than just 256 available byte values.

@smoothdeveloper

This comment has been minimized.

Show comment
Hide comment
@smoothdeveloper

smoothdeveloper Mar 6, 2016

Contributor

@mexx strawman busted! although I believe in some languages char = 1 byte (python 2?).

Yes alias on DU cases, but as @vasily-kirichenko mention, active pattern + functions kind of hides it, although the type signatures are going to be very confusing.

It actually feels that defining a new type is simplest way to not break existing code, I'm sure there are few people using Choice.Choice2of2 for Ok and adding semantic to it which wasn't baked in the type initially is going to choke.

Anyways, I think I expressed my opinion too much on the matter and leave it to all of you to figure out the right outcome :)

Contributor

smoothdeveloper commented Mar 6, 2016

@mexx strawman busted! although I believe in some languages char = 1 byte (python 2?).

Yes alias on DU cases, but as @vasily-kirichenko mention, active pattern + functions kind of hides it, although the type signatures are going to be very confusing.

It actually feels that defining a new type is simplest way to not break existing code, I'm sure there are few people using Choice.Choice2of2 for Ok and adding semantic to it which wasn't baked in the type initially is going to choke.

Anyways, I think I expressed my opinion too much on the matter and leave it to all of you to figure out the right outcome :)

@Overlord-Zurg

This comment has been minimized.

Show comment
Hide comment
@Overlord-Zurg

Overlord-Zurg Mar 7, 2016

I'd like to make sure that any implementation of this RFC provides a ResultBuilder computation expression like the one illustrated in @vasily-kirichenko 's last post, along with any other computation expressions or functions that make sense (nothing springs to mind right now).

Is the expected behaviour of such a computation expression sufficiently "obvious"? Or are there ambiguities that need to be resolved?

I'd like to make sure that any implementation of this RFC provides a ResultBuilder computation expression like the one illustrated in @vasily-kirichenko 's last post, along with any other computation expressions or functions that make sense (nothing springs to mind right now).

Is the expected behaviour of such a computation expression sufficiently "obvious"? Or are there ambiguities that need to be resolved?

@vasily-kirichenko

This comment has been minimized.

Show comment
Hide comment
@vasily-kirichenko

vasily-kirichenko Mar 8, 2016

@Overlord-Zurg the Result type is Either monad, which has well known laws as a monad and well established behavior as this concrete type of monad. A proper implementation can be found in ExtCore library (ChoiceBuilder). Also, see other builders there for inspiration.

@Overlord-Zurg the Result type is Either monad, which has well known laws as a monad and well established behavior as this concrete type of monad. A proper implementation can be found in ExtCore library (ChoiceBuilder). Also, see other builders there for inspiration.

@haf

This comment has been minimized.

Show comment
Hide comment
@haf

haf Mar 8, 2016

I have another opinion: when you do add this type, please also add combinators around it, like in ExtCore or maybe just a small subset of them like in YoLo

haf commented Mar 8, 2016

I have another opinion: when you do add this type, please also add combinators around it, like in ExtCore or maybe just a small subset of them like in YoLo

@enricosada

This comment has been minimized.

Show comment
Hide comment
@enricosada

enricosada Mar 8, 2016

Contributor

I think it's better to have only the Result type in FSharp.Core, not a default implementation of combinators, computation exceptions, helper functions etc. Less is better.

Any external library (Ext.Core, FSharp.Core.Attempt, you create it) can add these.

A common result type (in FSharp.Core) help libraries share the type, and each library can evolve indipendently.

No need to add more functions inside FSharp.Core (it's already big). an FSharp.Core is a fixed api, with long backward compatibiliy, it's difficult to evolve.

It's not 2009 anymore, now with nuget packages, it's easier to add more assemblies (from packages)
So if we want to add new combinators, helper functions, it's easier, because these can be implemented in normal library in a community fsproject or in an external awesome library like ExtCore.
No need to wait for RFCs of FSharp.Core

I think FSharp.Core should contains the common types (Choice, Option, Result, Map, List) with conversion function when needed (like List.ofArray).
But all functions should be in separate assemblies if possibile.

I think declaring types inside FSharp.Core and functions in external libraries, can help improve feedback loop, and quality

Maybe the only one i'd like to add to FSharp.Core is ofChoice: Choice -> Result and toChoice: Result -> Choice because is conversion to and from a type already declared inside FSharp.Core ( so it's clear that Choice1Of2 = Ok, Choice2Of2 = Error ).

Contributor

enricosada commented Mar 8, 2016

I think it's better to have only the Result type in FSharp.Core, not a default implementation of combinators, computation exceptions, helper functions etc. Less is better.

Any external library (Ext.Core, FSharp.Core.Attempt, you create it) can add these.

A common result type (in FSharp.Core) help libraries share the type, and each library can evolve indipendently.

No need to add more functions inside FSharp.Core (it's already big). an FSharp.Core is a fixed api, with long backward compatibiliy, it's difficult to evolve.

It's not 2009 anymore, now with nuget packages, it's easier to add more assemblies (from packages)
So if we want to add new combinators, helper functions, it's easier, because these can be implemented in normal library in a community fsproject or in an external awesome library like ExtCore.
No need to wait for RFCs of FSharp.Core

I think FSharp.Core should contains the common types (Choice, Option, Result, Map, List) with conversion function when needed (like List.ofArray).
But all functions should be in separate assemblies if possibile.

I think declaring types inside FSharp.Core and functions in external libraries, can help improve feedback loop, and quality

Maybe the only one i'd like to add to FSharp.Core is ofChoice: Choice -> Result and toChoice: Result -> Choice because is conversion to and from a type already declared inside FSharp.Core ( so it's clear that Choice1Of2 = Ok, Choice2Of2 = Error ).

@vasily-kirichenko

This comment has been minimized.

Show comment
Hide comment
@vasily-kirichenko

vasily-kirichenko Mar 8, 2016

@enricosada honestly, I don't see why Result type should be in FSharp.Core. It duplicates Choice'2.

@enricosada honestly, I don't see why Result type should be in FSharp.Core. It duplicates Choice'2.

@vasily-kirichenko

This comment has been minimized.

Show comment
Hide comment
@vasily-kirichenko

vasily-kirichenko Mar 8, 2016

It would be completely different story if FSharp.Core would use Result itself, I mean, if it'd have some (or lots of) functional API which does not throw exceptions. Unfortunately, the language does not embrace FP error handling at all (the only single exception is Async.Catch).

It would be completely different story if FSharp.Core would use Result itself, I mean, if it'd have some (or lots of) functional API which does not throw exceptions. Unfortunately, the language does not embrace FP error handling at all (the only single exception is Async.Catch).

@yawaramin

This comment has been minimized.

Show comment
Hide comment
@yawaramin

yawaramin Dec 2, 2016

@pblasucci note that a computation expression may be syntax sugar, but the type which implements it is not. In fact a result or choice or whatever instance of some CE type would, I think, fit all your criteria, including being usable outside of F#, because it would be just method calls.

@pblasucci note that a computation expression may be syntax sugar, but the type which implements it is not. In fact a result or choice or whatever instance of some CE type would, I think, fit all your criteria, including being usable outside of F#, because it would be just method calls.

@pblasucci

This comment has been minimized.

Show comment
Hide comment
@pblasucci

pblasucci Dec 2, 2016

@yawaramin I am, of course, open to anything. But I'm initially skeptical that a CE "builder" would, in and of itself, meet most of the criteria above. Not that it couldn't, but that you'd still need module functions with equivalent functionality because CEs are NOT always the best fit. In fact, that's probably why many CE "builders" are implemented as a thin layer over a bunch of functions (defined outside the builder). Also, simply exposing "tupled methods" rather than "curried functions" isn't really enough to make an API viable to non-F# languages. Even if it was, most of the methods on a builder would seem a bit jarring to many C# or VB devs (e.g. instance methods like Combine and Delay would have very little meaning). So, like I said, I'm happy to see if anyone comes up with a good approach. But for now I'm very reticent.

@yawaramin I am, of course, open to anything. But I'm initially skeptical that a CE "builder" would, in and of itself, meet most of the criteria above. Not that it couldn't, but that you'd still need module functions with equivalent functionality because CEs are NOT always the best fit. In fact, that's probably why many CE "builders" are implemented as a thin layer over a bunch of functions (defined outside the builder). Also, simply exposing "tupled methods" rather than "curried functions" isn't really enough to make an API viable to non-F# languages. Even if it was, most of the methods on a builder would seem a bit jarring to many C# or VB devs (e.g. instance methods like Combine and Delay would have very little meaning). So, like I said, I'm happy to see if anyone comes up with a good approach. But for now I'm very reticent.

@neoeinstein

This comment has been minimized.

Show comment
Hide comment
@neoeinstein

neoeinstein Dec 2, 2016

Contributor

@pblasucci When you mention "a more directed way of modelling errors (which has to account for exn in some fashion", are you looking for an API that handles things with a similar feel to the tri-state TaskCompletionSource? Something like the following?

let makeResult () =
  match aValue with
  | SomethingExpected y -> succeed y
  | NotCorrect -> fail "not correct"
  | _ -> except (exn "can't handle the truth")

match makeResult () with
| Success x -> printfn "It was successful! The value was %A" x
| Failure f -> printfn "It failed… The failure was %A" f
| Exception ex ->
  printfn
    "There was an exception... Message: %s Stack Trace: %s"
    ex.Message
    ex.StackTrace
private Result<S,F> MakeResult() {
  if (someCondition) {
    return Result.Succeed(y);
  } else if (knownFailureMode) {
    return Result.Fail("not correct");
  } else {
    return Result.Except(new Exception("can't handle the truth"));
  }
}

var result = MakeResult();
result
  .Handle(
    onSuccess: x => Console.WriteLine($"It was successful! The value was {x}"),
    onFailure: f => Console.WriteLine($"It failed… The failure was {f}"),
    onException: ex => Console.WriteLine($"There was an exception... Message: {ex.Message} Stack Trace: {ex.StackTrace}");

(Excuse the style/formatting and excessive free variables.)

Contributor

neoeinstein commented Dec 2, 2016

@pblasucci When you mention "a more directed way of modelling errors (which has to account for exn in some fashion", are you looking for an API that handles things with a similar feel to the tri-state TaskCompletionSource? Something like the following?

let makeResult () =
  match aValue with
  | SomethingExpected y -> succeed y
  | NotCorrect -> fail "not correct"
  | _ -> except (exn "can't handle the truth")

match makeResult () with
| Success x -> printfn "It was successful! The value was %A" x
| Failure f -> printfn "It failed… The failure was %A" f
| Exception ex ->
  printfn
    "There was an exception... Message: %s Stack Trace: %s"
    ex.Message
    ex.StackTrace
private Result<S,F> MakeResult() {
  if (someCondition) {
    return Result.Succeed(y);
  } else if (knownFailureMode) {
    return Result.Fail("not correct");
  } else {
    return Result.Except(new Exception("can't handle the truth"));
  }
}

var result = MakeResult();
result
  .Handle(
    onSuccess: x => Console.WriteLine($"It was successful! The value was {x}"),
    onFailure: f => Console.WriteLine($"It failed… The failure was {f}"),
    onException: ex => Console.WriteLine($"There was an exception... Message: {ex.Message} Stack Trace: {ex.StackTrace}");

(Excuse the style/formatting and excessive free variables.)

@pblasucci

This comment has been minimized.

Show comment
Hide comment
@pblasucci

pblasucci Dec 3, 2016

@neoeinstein That's certainly one possible option. Though I'm beginning to regret making that statement at all. It was born out of my frustrations, which are probably not something to which I should subject the rest of the community.

@neoeinstein That's certainly one possible option. Though I'm beginning to regret making that statement at all. It was born out of my frustrations, which are probably not something to which I should subject the rest of the community.

@voronoipotato

This comment has been minimized.

Show comment
Hide comment
@voronoipotato

voronoipotato Dec 3, 2016

+1 for Ok | Error, it's used in OCaml, and it's brief but not abbreviated. I need it as a clear indicator in ROP for when I need to stop or when I can go forward. Bind, Map, and such should be included see @Chicker 's post. Keep in mind these kinds of convenience changes might not matter so much to some of you, but beginners will feel the pain when they have to add a library just to do this thing. For example say I'm getting started in F#, I want to do things in functional style I just read F# for fun and profit, and I'm looking at his example. Huh, he seems to have written his own... should I use that or chessie? (chessie is also linked on the page) Which is better? Is chessie overkill? Had I found ROP some other way I'd probably try (badly) to mimic the functionality via Choice1of2 and lament that it doesn't even slightly look like one is the go forward and the other is the stop. yknow this isn't out of experience or anything ahaha :(

https://github.com/swlaschin/Railway-Oriented-Programming-Example/blob/master/src/FsRopExample/Rop.fs

voronoipotato commented Dec 3, 2016

+1 for Ok | Error, it's used in OCaml, and it's brief but not abbreviated. I need it as a clear indicator in ROP for when I need to stop or when I can go forward. Bind, Map, and such should be included see @Chicker 's post. Keep in mind these kinds of convenience changes might not matter so much to some of you, but beginners will feel the pain when they have to add a library just to do this thing. For example say I'm getting started in F#, I want to do things in functional style I just read F# for fun and profit, and I'm looking at his example. Huh, he seems to have written his own... should I use that or chessie? (chessie is also linked on the page) Which is better? Is chessie overkill? Had I found ROP some other way I'd probably try (badly) to mimic the functionality via Choice1of2 and lament that it doesn't even slightly look like one is the go forward and the other is the stop. yknow this isn't out of experience or anything ahaha :(

https://github.com/swlaschin/Railway-Oriented-Programming-Example/blob/master/src/FsRopExample/Rop.fs

@gusty

This comment has been minimized.

Show comment
Hide comment
@gusty

gusty Dec 11, 2016

I strongly agree with @pblasucci and I think that since the first alternative he proposed is far to make it into F# 4.1 we should opt for his second alternative: use a general purpose name, like Either or something else (if re-using a Haskell name offends some devs).

But I definitely see the point, let's suppose we continue with Error/Ok then we wouldn't be able to re-use generic two-alternative functions that are not specifically related to error handling.
We will end wrapping and un-wrapping or duplicating code.

Still we can implement Error/OK as a type alias with an active pattern in FSharp.Core so the code you write today with F# 4.1 RC wouldn't change but then we will have more compatibility between types and libraries.

gusty commented Dec 11, 2016

I strongly agree with @pblasucci and I think that since the first alternative he proposed is far to make it into F# 4.1 we should opt for his second alternative: use a general purpose name, like Either or something else (if re-using a Haskell name offends some devs).

But I definitely see the point, let's suppose we continue with Error/Ok then we wouldn't be able to re-use generic two-alternative functions that are not specifically related to error handling.
We will end wrapping and un-wrapping or duplicating code.

Still we can implement Error/OK as a type alias with an active pattern in FSharp.Core so the code you write today with F# 4.1 RC wouldn't change but then we will have more compatibility between types and libraries.

@Pauan

This comment has been minimized.

Show comment
Hide comment
@Pauan

Pauan Dec 12, 2016

@gmpl I don't see the purpose of introducing an Either type, since we already have Choice, which is exactly the same thing.

The purpose of Result is that we do want to use it specifically for error handling. I can't think of any situation where it makes sense to pass a Result to a function which expects a Choice. Could you please provide an example?

Pauan commented Dec 12, 2016

@gmpl I don't see the purpose of introducing an Either type, since we already have Choice, which is exactly the same thing.

The purpose of Result is that we do want to use it specifically for error handling. I can't think of any situation where it makes sense to pass a Result to a function which expects a Choice. Could you please provide an example?

@gusty

This comment has been minimized.

Show comment
Hide comment
@gusty

gusty Dec 12, 2016

@Pauan if you read back some comments, @pblasucci explained very well why we need to introduce Either or something else.
Note that we don't have already Choice as you said. Choice is a reference type, we're talking about adding a Value type, so it's not the same thing.

An example? It's hard to forecast which libraries will come in the future, but let's suppose that a library that provides Arrows like functionality becomes very popular. You have functions there and probably datatypes that deals with two 'branches' of the workflow.
So this could be used also for error handling but if we have already code base that returns a different type it won't be compatible and we'll have to either wrap and unwrap or ask the maintainer of that library to duplicate the code.

Finally lets think about the opposite question: what's wrong about defining another set of Choice or Either DUs as value types and then having a type alias with an active pattern for OK/Error in FSharp Core? What's the disadvantage of this wrt what we have already in the RC?

gusty commented Dec 12, 2016

@Pauan if you read back some comments, @pblasucci explained very well why we need to introduce Either or something else.
Note that we don't have already Choice as you said. Choice is a reference type, we're talking about adding a Value type, so it's not the same thing.

An example? It's hard to forecast which libraries will come in the future, but let's suppose that a library that provides Arrows like functionality becomes very popular. You have functions there and probably datatypes that deals with two 'branches' of the workflow.
So this could be used also for error handling but if we have already code base that returns a different type it won't be compatible and we'll have to either wrap and unwrap or ask the maintainer of that library to duplicate the code.

Finally lets think about the opposite question: what's wrong about defining another set of Choice or Either DUs as value types and then having a type alias with an active pattern for OK/Error in FSharp Core? What's the disadvantage of this wrt what we have already in the RC?

@Pauan

This comment has been minimized.

Show comment
Hide comment
@Pauan

Pauan Dec 12, 2016

@gmpl Yes I've read the whole thread, including @pblasucci's comments. I don't find them convincing.

Perhaps I am just ignorant, but from what I understand the purpose of value types is that they are faster in some situations. In other words, they are an optimization.

Adding in a StructChoice does sound like a good idea, but that's completely separate from Result. The purpose of Result is semantics, not optimization.

I don't think that's a very good example. If a value is conceptually a union, then using Choice makes sense. If a value is conceptually an error, then using Result makes sense. I can't think of any situations where an API would want to support both. If you want to convert from one to the other, you will have to wrap/unwrap.

This is obvious when you consider the semantics of Choice: because it is not biased to either side, it is possible for either the left or right side to be the error type. So if you want to convert to a Result you need two functions: ofChoice1 and ofChoice2. This demonstrates that Result is not the same as Choice (at a conceptual level).

If you want to preserve the conceptual semantics of Choice and Result then you must wrap/unwrap, that is unavoidable. And in practice I doubt wrapping/unwrapping will be very common, because Choice and Result serve different purposes, and so it's very rare to convert from one to the other.

The only exception is when dealing with pre-4.1 code which uses Choice and hasn't upgraded yet to use Result. In that case there will be some temporary pain with needing to convert. But that pain will go away over time, as libraries switch to using Result (when it makes sense). And the pain is pretty minimal: it's just a matter of calling ofChoice1 or ofChoice2 depending on the library.

I don't have any problem with defining a new StructChoice type, but as I said that is a separate issue from Result. You seem to be misunderstanding why people want Result. They want it because:

  1. It makes it crystal clear that it is dealing with error handling, not a union of types.

  2. The Ok and Error constructors further reinforce that.

  3. Because it is dealing with error handling, it is implicitly biased toward Ok, which is very important. This affects everything, but it especially affects computation expressions.

Choice on the other hand conceptually represents a union of types. Therefore it is not clear that it is being used for error handling, and it is not biased toward the left or right. Therefore it doesn't make conceptual sense for Choice to have a computation expression.

The purpose is to cleanly separate concepts: if somebody sees a Result they know "aha, this is doing error handling and I can use a computation expression to make my life easier, knowing that it is biased toward success". If somebody sees a Choice they know "aha, this is a union of types which isn't biased, so I have to account for either possibility with equal probability"

This helps to make APIs easier to understand, easier to use, and helps to prevent bugs. As proof of that, some people use Choice1Of2 to mean "success" and some people use Choice2Of2 to mean "success". Either option is completely valid, because Choice is not biased. But Result is biased, and that makes all the difference.

Pauan commented Dec 12, 2016

@gmpl Yes I've read the whole thread, including @pblasucci's comments. I don't find them convincing.

Perhaps I am just ignorant, but from what I understand the purpose of value types is that they are faster in some situations. In other words, they are an optimization.

Adding in a StructChoice does sound like a good idea, but that's completely separate from Result. The purpose of Result is semantics, not optimization.

I don't think that's a very good example. If a value is conceptually a union, then using Choice makes sense. If a value is conceptually an error, then using Result makes sense. I can't think of any situations where an API would want to support both. If you want to convert from one to the other, you will have to wrap/unwrap.

This is obvious when you consider the semantics of Choice: because it is not biased to either side, it is possible for either the left or right side to be the error type. So if you want to convert to a Result you need two functions: ofChoice1 and ofChoice2. This demonstrates that Result is not the same as Choice (at a conceptual level).

If you want to preserve the conceptual semantics of Choice and Result then you must wrap/unwrap, that is unavoidable. And in practice I doubt wrapping/unwrapping will be very common, because Choice and Result serve different purposes, and so it's very rare to convert from one to the other.

The only exception is when dealing with pre-4.1 code which uses Choice and hasn't upgraded yet to use Result. In that case there will be some temporary pain with needing to convert. But that pain will go away over time, as libraries switch to using Result (when it makes sense). And the pain is pretty minimal: it's just a matter of calling ofChoice1 or ofChoice2 depending on the library.

I don't have any problem with defining a new StructChoice type, but as I said that is a separate issue from Result. You seem to be misunderstanding why people want Result. They want it because:

  1. It makes it crystal clear that it is dealing with error handling, not a union of types.

  2. The Ok and Error constructors further reinforce that.

  3. Because it is dealing with error handling, it is implicitly biased toward Ok, which is very important. This affects everything, but it especially affects computation expressions.

Choice on the other hand conceptually represents a union of types. Therefore it is not clear that it is being used for error handling, and it is not biased toward the left or right. Therefore it doesn't make conceptual sense for Choice to have a computation expression.

The purpose is to cleanly separate concepts: if somebody sees a Result they know "aha, this is doing error handling and I can use a computation expression to make my life easier, knowing that it is biased toward success". If somebody sees a Choice they know "aha, this is a union of types which isn't biased, so I have to account for either possibility with equal probability"

This helps to make APIs easier to understand, easier to use, and helps to prevent bugs. As proof of that, some people use Choice1Of2 to mean "success" and some people use Choice2Of2 to mean "success". Either option is completely valid, because Choice is not biased. But Result is biased, and that makes all the difference.

@gusty

This comment has been minimized.

Show comment
Hide comment
@gusty

gusty Dec 13, 2016

Thanks @Pauan for the detailed analysis but you didn't answer my last question.
I mean all the features you are mentioning still apply by using the type alias, or am I missing something?

gusty commented Dec 13, 2016

Thanks @Pauan for the detailed analysis but you didn't answer my last question.
I mean all the features you are mentioning still apply by using the type alias, or am I missing something?

@Overlord-Zurg

This comment has been minimized.

Show comment
Hide comment
@Overlord-Zurg

Overlord-Zurg Dec 13, 2016

@gmpl My impression is that Either does not make a distinction between the left and right values, but Result is based around following the Ok path, which will inform its supplementing functions (e.g. the result computation expression's let! will proceed along the Ok path, and the Ok type can change with each let! while the Err type must be the same throughout the entire computation expression).

This is why @Pauan and I argue that Either and Result are not the same thing. Does this make sense? Or are we missing some nuance of the Either concept that makes it overlap with Result more than we realize?

@gmpl My impression is that Either does not make a distinction between the left and right values, but Result is based around following the Ok path, which will inform its supplementing functions (e.g. the result computation expression's let! will proceed along the Ok path, and the Ok type can change with each let! while the Err type must be the same throughout the entire computation expression).

This is why @Pauan and I argue that Either and Result are not the same thing. Does this make sense? Or are we missing some nuance of the Either concept that makes it overlap with Result more than we realize?

@gusty

This comment has been minimized.

Show comment
Hide comment
@gusty

gusty Dec 14, 2016

I wonder why everybody is telling me what I already know and agree :)
Then everybody's happy, thumbs up, ... but avoid to answer the key question.
Here is it for the third time: WHAT'S WRONG WITH IMPLEMENTING THIS AS A TYPE ALIAS?

One more time, I agree with you all in that Either/Choice has not the same meaning as ERROR/OK, for me that's out of discussion. The question is how to implement it, since Either/Choice has a more general meaning than Error/Ok it could be implemented as a type alias with an active pattern.

This was done before in many libraries with Choice but I think the problem was that everybody was implementing it on his own, now we have the chance to do a single implementation in the Core lib, so that would solve the problem of having many versions with different names.

@Overlord-Zurg I agree 100% with you but even the fact the the let! will proceed along the OK pattern is not incompatible with a type alias. In the end Either/Choice will also have a let! that would proceed along the Right/Choice pattern. So that's definitely not an issue. Naming is also not an issue since you can use the alias or not, depending on what do you want to express.

gusty commented Dec 14, 2016

I wonder why everybody is telling me what I already know and agree :)
Then everybody's happy, thumbs up, ... but avoid to answer the key question.
Here is it for the third time: WHAT'S WRONG WITH IMPLEMENTING THIS AS A TYPE ALIAS?

One more time, I agree with you all in that Either/Choice has not the same meaning as ERROR/OK, for me that's out of discussion. The question is how to implement it, since Either/Choice has a more general meaning than Error/Ok it could be implemented as a type alias with an active pattern.

This was done before in many libraries with Choice but I think the problem was that everybody was implementing it on his own, now we have the chance to do a single implementation in the Core lib, so that would solve the problem of having many versions with different names.

@Overlord-Zurg I agree 100% with you but even the fact the the let! will proceed along the OK pattern is not incompatible with a type alias. In the end Either/Choice will also have a let! that would proceed along the Right/Choice pattern. So that's definitely not an issue. Naming is also not an issue since you can use the alias or not, depending on what do you want to express.

@radekm

This comment has been minimized.

Show comment
Hide comment
@radekm

radekm Dec 14, 2016

WHAT'S WRONG WITH IMPLEMENTING THIS AS A TYPE ALIAS?

You can use it with functions / (extension) methods which were designed for the aliased type and don't make sense for Result.

radekm commented Dec 14, 2016

WHAT'S WRONG WITH IMPLEMENTING THIS AS A TYPE ALIAS?

You can use it with functions / (extension) methods which were designed for the aliased type and don't make sense for Result.

@gusty

This comment has been minimized.

Show comment
Hide comment
@gusty

gusty Dec 14, 2016

@radekm good point, and since Choice/Either is more generic than Error/Ok there will no be such methods. Right?

gusty commented Dec 14, 2016

@radekm good point, and since Choice/Either is more generic than Error/Ok there will no be such methods. Right?

@radekm

This comment has been minimized.

Show comment
Hide comment
@radekm

radekm Dec 15, 2016

and since Choice/Either is more generic than Error/Ok there will no be such methods. Right?

I think there will be such functions / methods / properties - eg. projections getLeft and getRight (getError and getOk should be used instead with Result alias but type checker won't catch this).

radekm commented Dec 15, 2016

and since Choice/Either is more generic than Error/Ok there will no be such methods. Right?

I think there will be such functions / methods / properties - eg. projections getLeft and getRight (getError and getOk should be used instead with Result alias but type checker won't catch this).

@Pauan

This comment has been minimized.

Show comment
Hide comment
@Pauan

Pauan Dec 15, 2016

@gmpl In Haskell there is a Foldable typeclass, which contains some useful functions for folding over things. The Either type has an instance for Foldable

I'm going to focus specifically on toList, but this also applies to the other Foldable functions, and it applies to some other functions too, like in Semigroup

Let's try implementing toList for the Choice type:

let toList value =
  match value with
  | Choice1Of2 a -> [a]
  | Choice2Of2 a -> [a]

Now let's try implementing toList for Result:

let toList value =
  match value with
  | Error _ -> []
  | Ok a    -> [a]

In the case of Choice we treat both sides equally because it is unbiased. But in the case of Result we treat the Error and Ok branches differently.

In Haskell they use the Result implementation, because in Haskell the Either type is biased to the Right (well, most of the time anyways).

This was a pretty simple example, but hopefully it demonstrates that there are semantic differences between Choice and Result, and therefore implementing Result as a type alias will cause subtle bugs.


In addition, if Result were implemented as a type alias, it would implicitly be biased toward either Choice1Of2 or Choice2Of2 (depending on the implementation of Result). But there's nothing stopping somebody from writing a function for Choice which is biased differently from Result. After all, Choice is unbiased, so they can choose whatever bias they want.

Unfortunately you don't get a warning or error if you get the bias "wrong". So both library authors and library users must be careful to use the "correct" bias for Choice, even though Choice is supposed to be unbiased.

Let me give a concrete example. Let's suppose that Result is biased so that Choice1Of2 is Ok and Choice2Of2 is Error. And somebody writes a library which uses Choice, but they choose a different bias: Choice1Of2 means error and Choice2Of2 means success. But now when somebody else uses that library and tries to treat it as a Result it doesn't work, because the bias is wrong.

Rather than requiring programmer discipline, I would prefer if these kinds of mistakes were caught at compile-time by having two separate static types, rather than a type alias. With two separate types you must now use ofChoice1 or ofChoice2 to convert, so everything is clear.


In my opinion, good static type design is just as much about what you can't do as with what you can do. If you make Result a type alias, then any function which works on Result can also work on Choice (but with potentially wrong results if the bias is wrong!). If they are two separate types, then that is not possible: functions which only make sense on Result only work with Result


So I'll flip the question back to you: why should we implement Result as a type alias? It sounds like you believe it will increase function reuse. So, what functions fulfill the following two requirements:

  1. Usable for both Choice and Result (with correct semantics for both!)

  2. Used frequently enough that manually converting from Choice to Result (or vice versa) is painful

The second requirement is somewhat subjective, but I would appreciate it if you could give an example of the first requirement. Ideally something concrete. I'm not expecting an actual implementation, but I do expect a type signature and a high level description of what the function does.

You might be able to find a function like that by looking at existing Choice functions (which are not being used for error handling) and imagining how they should behave if Result were a type alias.

Pauan commented Dec 15, 2016

@gmpl In Haskell there is a Foldable typeclass, which contains some useful functions for folding over things. The Either type has an instance for Foldable

I'm going to focus specifically on toList, but this also applies to the other Foldable functions, and it applies to some other functions too, like in Semigroup

Let's try implementing toList for the Choice type:

let toList value =
  match value with
  | Choice1Of2 a -> [a]
  | Choice2Of2 a -> [a]

Now let's try implementing toList for Result:

let toList value =
  match value with
  | Error _ -> []
  | Ok a    -> [a]

In the case of Choice we treat both sides equally because it is unbiased. But in the case of Result we treat the Error and Ok branches differently.

In Haskell they use the Result implementation, because in Haskell the Either type is biased to the Right (well, most of the time anyways).

This was a pretty simple example, but hopefully it demonstrates that there are semantic differences between Choice and Result, and therefore implementing Result as a type alias will cause subtle bugs.


In addition, if Result were implemented as a type alias, it would implicitly be biased toward either Choice1Of2 or Choice2Of2 (depending on the implementation of Result). But there's nothing stopping somebody from writing a function for Choice which is biased differently from Result. After all, Choice is unbiased, so they can choose whatever bias they want.

Unfortunately you don't get a warning or error if you get the bias "wrong". So both library authors and library users must be careful to use the "correct" bias for Choice, even though Choice is supposed to be unbiased.

Let me give a concrete example. Let's suppose that Result is biased so that Choice1Of2 is Ok and Choice2Of2 is Error. And somebody writes a library which uses Choice, but they choose a different bias: Choice1Of2 means error and Choice2Of2 means success. But now when somebody else uses that library and tries to treat it as a Result it doesn't work, because the bias is wrong.

Rather than requiring programmer discipline, I would prefer if these kinds of mistakes were caught at compile-time by having two separate static types, rather than a type alias. With two separate types you must now use ofChoice1 or ofChoice2 to convert, so everything is clear.


In my opinion, good static type design is just as much about what you can't do as with what you can do. If you make Result a type alias, then any function which works on Result can also work on Choice (but with potentially wrong results if the bias is wrong!). If they are two separate types, then that is not possible: functions which only make sense on Result only work with Result


So I'll flip the question back to you: why should we implement Result as a type alias? It sounds like you believe it will increase function reuse. So, what functions fulfill the following two requirements:

  1. Usable for both Choice and Result (with correct semantics for both!)

  2. Used frequently enough that manually converting from Choice to Result (or vice versa) is painful

The second requirement is somewhat subjective, but I would appreciate it if you could give an example of the first requirement. Ideally something concrete. I'm not expecting an actual implementation, but I do expect a type signature and a high level description of what the function does.

You might be able to find a function like that by looking at existing Choice functions (which are not being used for error handling) and imagining how they should behave if Result were a type alias.

@gusty

This comment has been minimized.

Show comment
Hide comment
@gusty

gusty Dec 15, 2016

@radekm Both functions and static members are things that can be added later, so that's not a problem. The same way there are type alias, there are also function aliases.
That explains the projections you mentioned is not really an issue if you ever define them.

@Pauan I'm surprised, again people thumbed up but nobody noticed that the Foldable instance you wrote is completely wrong and nonsense.

If you write that toList function, the Choice type would be constrained to be Choice<'t,'t> so it wouldn't even compile for the most common case in which the types are different, it would work only when both are the same, which defeats the whole purpose of the discriminated union.

The Haskell guys did the 'right' thing, that's a pretty good reasonable Foldable instance for Either independent of the purpose of error handling or not.

Let me point out that there is some confusion because in theory a type like Either is not biased but, when we talk about monads, foldables and other type-classes, things changes. Why it changes? Because the type classes mechanism allows only one instance to be defined per type.

Now that's certainly a problem when there are more than one valid (and useful) instances (i.e. Applicative for lists) a decision has to be made which requires a lot of discussion and in few cases is inevitable arbitrary.

Still that's not the case of Either. For Either there is only one valid instance that makes sense.

I know, at this point you might want to stop me saying "nooo, there are two" ... yes that's right indeed there are two, but they are the same, one is biased to the right and another is biased to the left, they are duals.

So, the logic is the same but one operates to the right and another one to the left. So which one is the most obvious?

Answer: Right for two different reasons:

  • In the very common case where it's used for error handling the mnemonic is that Right is the right value.

  • Even in other cases there is a technical issue. Type application is first class in Haskell-like type systems, so if we define a monad/applicative/functor (they have a single type variable) the curried type application is (Either Left) (Right). Then by applying the first type parameter we have already a monad/applicative/functor and it turns out that the type variable which is the "monadic parameter" is the right one, the one in the right side, which is called ... (guess what) ... right.

For the curious reader, the other monad instance could be used (by wrapping to the dual of Either) as the success monad, that is a computation that stops on the first success.

So the conclusion is something like yes and no. Yes, they mean different things but no, in the end they are the same, they have the same mechanic which is inevitable biased to one of the two types.

The same applies for tuples. The instances for tuples (the reader monad) are biased to one of the two elements.
And if you insist that we need a separate type for Result when doing error handling nobody would be less correct by stating that we need a separate tuple for a Result of a function which returns 2 values.

Finally let me say that the bug you described is avoidable by having the Result type alias defined at the Core lib. Then there will be no confusion, we all will be forced to use that lib so nobody will get the bias wrong.

Regarding the examples you requested let me elaborate on that, for sure they are, I know some functions like that, but will have to think and write a good example to show you what could go wrong.

gusty commented Dec 15, 2016

@radekm Both functions and static members are things that can be added later, so that's not a problem. The same way there are type alias, there are also function aliases.
That explains the projections you mentioned is not really an issue if you ever define them.

@Pauan I'm surprised, again people thumbed up but nobody noticed that the Foldable instance you wrote is completely wrong and nonsense.

If you write that toList function, the Choice type would be constrained to be Choice<'t,'t> so it wouldn't even compile for the most common case in which the types are different, it would work only when both are the same, which defeats the whole purpose of the discriminated union.

The Haskell guys did the 'right' thing, that's a pretty good reasonable Foldable instance for Either independent of the purpose of error handling or not.

Let me point out that there is some confusion because in theory a type like Either is not biased but, when we talk about monads, foldables and other type-classes, things changes. Why it changes? Because the type classes mechanism allows only one instance to be defined per type.

Now that's certainly a problem when there are more than one valid (and useful) instances (i.e. Applicative for lists) a decision has to be made which requires a lot of discussion and in few cases is inevitable arbitrary.

Still that's not the case of Either. For Either there is only one valid instance that makes sense.

I know, at this point you might want to stop me saying "nooo, there are two" ... yes that's right indeed there are two, but they are the same, one is biased to the right and another is biased to the left, they are duals.

So, the logic is the same but one operates to the right and another one to the left. So which one is the most obvious?

Answer: Right for two different reasons:

  • In the very common case where it's used for error handling the mnemonic is that Right is the right value.

  • Even in other cases there is a technical issue. Type application is first class in Haskell-like type systems, so if we define a monad/applicative/functor (they have a single type variable) the curried type application is (Either Left) (Right). Then by applying the first type parameter we have already a monad/applicative/functor and it turns out that the type variable which is the "monadic parameter" is the right one, the one in the right side, which is called ... (guess what) ... right.

For the curious reader, the other monad instance could be used (by wrapping to the dual of Either) as the success monad, that is a computation that stops on the first success.

So the conclusion is something like yes and no. Yes, they mean different things but no, in the end they are the same, they have the same mechanic which is inevitable biased to one of the two types.

The same applies for tuples. The instances for tuples (the reader monad) are biased to one of the two elements.
And if you insist that we need a separate type for Result when doing error handling nobody would be less correct by stating that we need a separate tuple for a Result of a function which returns 2 values.

Finally let me say that the bug you described is avoidable by having the Result type alias defined at the Core lib. Then there will be no confusion, we all will be forced to use that lib so nobody will get the bias wrong.

Regarding the examples you requested let me elaborate on that, for sure they are, I know some functions like that, but will have to think and write a good example to show you what could go wrong.

@radekm

This comment has been minimized.

Show comment
Hide comment
@radekm

radekm Dec 15, 2016

Both functions and static members are things that can be added later, so that's not a problem.

@gmpl I would say it is a potential problem for the clarity of programs and for the usefulness of the code completion because all functions/methods defined for Result will work with Either and the other way around.

And if you insist that we need a separate type for Result when doing error handling nobody would be less correct by sating that we need a separate tuple for a Result of a function which returns 2 values.

I don't think this is the case. Because the need for a separate tuple for a Result of a function which returns 2 values is probably less urgent than the need for the Result sum type.

radekm commented Dec 15, 2016

Both functions and static members are things that can be added later, so that's not a problem.

@gmpl I would say it is a potential problem for the clarity of programs and for the usefulness of the code completion because all functions/methods defined for Result will work with Either and the other way around.

And if you insist that we need a separate type for Result when doing error handling nobody would be less correct by sating that we need a separate tuple for a Result of a function which returns 2 values.

I don't think this is the case. Because the need for a separate tuple for a Result of a function which returns 2 values is probably less urgent than the need for the Result sum type.

@Pauan

This comment has been minimized.

Show comment
Hide comment
@Pauan

Pauan Dec 16, 2016

@gmpl

If you write that toList function, the Choice type would be constrained to be Choice<'t,'t> so it wouldn't even compile for the most common case in which the types are different, it would work only when both are the same, which defeats the whole purpose of the discriminated union.

Yes it is constrained to Choice<'A, 'A>, which is also true for the Haskell implementation of Either. There are perfectly valid reasons to use Choice (or Result) where both types are the same. It is not completely wrong, it is not nonsense, and it doesn't nullify my point at all.

My point is that Choice<string, string> behaves differently than Result<string, string>, so people will write code which works fine with Choice but fails with Result (and vice versa). Keeping the types separate prevents those mistakes.

The Haskell guys did the 'right' thing, that's a pretty good reasonable Foldable instance for Either independent of the purpose of error handling or not.

The Haskell implementation only does the right thing if Either is used for error handling. It is definitely not the right thing to do if Either is used as a union type. That's precisely my point.

Let me point out that there is some confusion because in theory a type like Either is not biased but, when we talk about monads, foldables and other type-classes, things changes. Why it changes? Because the type classes mechanism allows only one instance to be defined per type.

I am fully aware of all of the nuances of Either in Haskell. Of course things like Applicative and Monad only make sense if Either is biased, I never disputed that.

I know, at this point you might want to stop me saying "nooo, there are two" ... yes that's right indeed there are two, but they are the same, one is biased to the right and another is biased to the left, they are duals.

No, I would say that there are zero instances, because Applicative/Monad/etc. don't make sense on Either, because it is unbiased.

So, the logic is the same but one operates to the right and another one to the left. So which one is the most obvious?

The "most obvious" option is to use Result where Ok is the obvious answer, and not implement them at all for Either.

So the conclusion is something like yes and no. Yes, they mean different things but no, in the end they are the same, they have the same mechanic which is inevitable biased to one of the two types.

No the bias is not inevitable. You could have two separate types: Either and Result. You would implement Applicative/Monad/etc. for Result but not for Either.

Like I said, static types are just as much about telling you what you can't do as what you can do.

Knowing that Either is never biased is useful information. Knowing that Result is always biased is useful information. Making it impossible to accidentally mix up the bias is useful, for the same reason that newtype is useful: it might be the same mechanically, but it is distinct at the conceptual/type level.

The same applies for tuples. The instances for tuples (the reader monad) are biased to one of the two elements.
And if you insist that we need a separate type for Result when doing error handling nobody would be less correct by sating that we need a separate tuple for a Result of a function which returns 2 values.

Indeed, I do not like that the Applicative/Monad implementation for (,) is biased. I think the tuple type in Haskell is abused for all sorts of different things.

I am generally in favor of a very clean separation of concepts at the static type level. I don't like when the same static type serves multiple different purposes, I think it inevitably leads to subtle (and not so subtle) bugs, which partially defeats the purpose of having a static type system in the first place.

I don't understand your second sentence, though. You are describing two different types: Result<'A, 'B> * Result<'C, 'D> or Result<'A * 'B, 'C>. Which do you mean? And in any case I do not see a problem with either of those types, and I do not see any particular reason why it would require a definition for a new type, since the existing types work perfectly. Could you clarify?

Finally let me say that the bug you described is avoidable by having the Result type alias defined at the Core lib. Then there will be no confusion, we all will be forced to use that lib so nobody will get the bias wrong.

No. Including the Result type alias does not solve the problem. I said that a library author uses Choice but with the "wrong" bias. They are not using Result, so including Result in the core library does not change anything. It is the user of the library that uses Result, but they have no control over the library.

Pauan commented Dec 16, 2016

@gmpl

If you write that toList function, the Choice type would be constrained to be Choice<'t,'t> so it wouldn't even compile for the most common case in which the types are different, it would work only when both are the same, which defeats the whole purpose of the discriminated union.

Yes it is constrained to Choice<'A, 'A>, which is also true for the Haskell implementation of Either. There are perfectly valid reasons to use Choice (or Result) where both types are the same. It is not completely wrong, it is not nonsense, and it doesn't nullify my point at all.

My point is that Choice<string, string> behaves differently than Result<string, string>, so people will write code which works fine with Choice but fails with Result (and vice versa). Keeping the types separate prevents those mistakes.

The Haskell guys did the 'right' thing, that's a pretty good reasonable Foldable instance for Either independent of the purpose of error handling or not.

The Haskell implementation only does the right thing if Either is used for error handling. It is definitely not the right thing to do if Either is used as a union type. That's precisely my point.

Let me point out that there is some confusion because in theory a type like Either is not biased but, when we talk about monads, foldables and other type-classes, things changes. Why it changes? Because the type classes mechanism allows only one instance to be defined per type.

I am fully aware of all of the nuances of Either in Haskell. Of course things like Applicative and Monad only make sense if Either is biased, I never disputed that.

I know, at this point you might want to stop me saying "nooo, there are two" ... yes that's right indeed there are two, but they are the same, one is biased to the right and another is biased to the left, they are duals.

No, I would say that there are zero instances, because Applicative/Monad/etc. don't make sense on Either, because it is unbiased.

So, the logic is the same but one operates to the right and another one to the left. So which one is the most obvious?

The "most obvious" option is to use Result where Ok is the obvious answer, and not implement them at all for Either.

So the conclusion is something like yes and no. Yes, they mean different things but no, in the end they are the same, they have the same mechanic which is inevitable biased to one of the two types.

No the bias is not inevitable. You could have two separate types: Either and Result. You would implement Applicative/Monad/etc. for Result but not for Either.

Like I said, static types are just as much about telling you what you can't do as what you can do.

Knowing that Either is never biased is useful information. Knowing that Result is always biased is useful information. Making it impossible to accidentally mix up the bias is useful, for the same reason that newtype is useful: it might be the same mechanically, but it is distinct at the conceptual/type level.

The same applies for tuples. The instances for tuples (the reader monad) are biased to one of the two elements.
And if you insist that we need a separate type for Result when doing error handling nobody would be less correct by sating that we need a separate tuple for a Result of a function which returns 2 values.

Indeed, I do not like that the Applicative/Monad implementation for (,) is biased. I think the tuple type in Haskell is abused for all sorts of different things.

I am generally in favor of a very clean separation of concepts at the static type level. I don't like when the same static type serves multiple different purposes, I think it inevitably leads to subtle (and not so subtle) bugs, which partially defeats the purpose of having a static type system in the first place.

I don't understand your second sentence, though. You are describing two different types: Result<'A, 'B> * Result<'C, 'D> or Result<'A * 'B, 'C>. Which do you mean? And in any case I do not see a problem with either of those types, and I do not see any particular reason why it would require a definition for a new type, since the existing types work perfectly. Could you clarify?

Finally let me say that the bug you described is avoidable by having the Result type alias defined at the Core lib. Then there will be no confusion, we all will be forced to use that lib so nobody will get the bias wrong.

No. Including the Result type alias does not solve the problem. I said that a library author uses Choice but with the "wrong" bias. They are not using Result, so including Result in the core library does not change anything. It is the user of the library that uses Result, but they have no control over the library.

@dsyme

This comment has been minimized.

Show comment
Hide comment
@dsyme

dsyme Dec 27, 2016

Contributor

Here is it for the third time: WHAT'S WRONG WITH IMPLEMENTING THIS AS A TYPE ALIAS?

Another reason is that if you print the values as data using %A then they will show the Choice1Of2 etc. labels instead of Error/Ok. Likewise in the debugger. Both matter in practice

Contributor

dsyme commented Dec 27, 2016

Here is it for the third time: WHAT'S WRONG WITH IMPLEMENTING THIS AS A TYPE ALIAS?

Another reason is that if you print the values as data using %A then they will show the Choice1Of2 etc. labels instead of Error/Ok. Likewise in the debugger. Both matter in practice

@gusty

This comment has been minimized.

Show comment
Hide comment
@gusty

gusty Dec 27, 2016

Thanks @dsyme for going directly to the point :)

I find this discussion really interesting and in these comments there are interesting arguments for and against this design decision.

These days I also found that this is a hot topic in the Haskell world. I can write some sample code as it was requested but I feel like this decision is already taken and there is no way back, so right now I doubt if it worths keep discussing it.

Your point is a good point, still I have some ideas to overcome that limitation but again, it's a bit too late I think.

Anyway, I would like to ask you what would be the way to go to have an Either / Choice / Success (biased to the other side) struct types?

Would everybody start its own version as it was before with Error/Ok or is there any plan to include it in the core lib?

gusty commented Dec 27, 2016

Thanks @dsyme for going directly to the point :)

I find this discussion really interesting and in these comments there are interesting arguments for and against this design decision.

These days I also found that this is a hot topic in the Haskell world. I can write some sample code as it was requested but I feel like this decision is already taken and there is no way back, so right now I doubt if it worths keep discussing it.

Your point is a good point, still I have some ideas to overcome that limitation but again, it's a bit too late I think.

Anyway, I would like to ask you what would be the way to go to have an Either / Choice / Success (biased to the other side) struct types?

Would everybody start its own version as it was before with Error/Ok or is there any plan to include it in the core lib?

@rmunn rmunn referenced this issue in haf/expecto Jan 7, 2017

Closed

Add isOk and isError expectations #62

@baronfel

This comment has been minimized.

Show comment
Hide comment
@baronfel

baronfel Jan 18, 2017

Contributor

Is there a compiler flag or directive available to determine when this type should be used instead of a hand rolled version? I'm thinking specifically of Chessie, where due to Paket single file dependencies we'd want to be able to wrap the definition of result there in the core type if available, so that we don't break consumers.

Contributor

baronfel commented Jan 18, 2017

Is there a compiler flag or directive available to determine when this type should be used instead of a hand rolled version? I'm thinking specifically of Chessie, where due to Paket single file dependencies we'd want to be able to wrap the definition of result there in the core type if available, so that we don't break consumers.

@wallymathieu

This comment has been minimized.

Show comment
Hide comment
@wallymathieu

wallymathieu Feb 1, 2017

If Chessie has a dependency on FSharp.Core of some version, then that should result in you having access to the standard one. I tried to see how one such change of Chessie would look. Though it is a solution I'm not happy with.

wallymathieu commented Feb 1, 2017

If Chessie has a dependency on FSharp.Core of some version, then that should result in you having access to the standard one. I tried to see how one such change of Chessie would look. Though it is a solution I'm not happy with.

@enricosada

This comment has been minimized.

Show comment
Hide comment
@enricosada

enricosada Feb 9, 2017

Contributor

@wallymathieu instead of set log as separate property

type RopResult<'TSuccess, 'TMessage> = {
   Result:Result<'TSuccess, unit>
   Log:'TMessage list
}

why not do exactly like now, so less code changes for migration?

type RopResult<'TSuccess, 'TMessage> = Result<'TSuccess * 'TMessage list , 'TMessage list>
Contributor

enricosada commented Feb 9, 2017

@wallymathieu instead of set log as separate property

type RopResult<'TSuccess, 'TMessage> = {
   Result:Result<'TSuccess, unit>
   Log:'TMessage list
}

why not do exactly like now, so less code changes for migration?

type RopResult<'TSuccess, 'TMessage> = Result<'TSuccess * 'TMessage list , 'TMessage list>
@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost Feb 9, 2017

@enricosada that would look better :)

ghost commented Feb 9, 2017

@enricosada that would look better :)

@wallymathieu

This comment has been minimized.

Show comment
Hide comment

The c# code needs some care in order to compile: fsprojects/Chessie@master...wallymathieu:result_from_fsharp_core

@21c-HK

This comment has been minimized.

Show comment
Hide comment
@21c-HK

21c-HK Apr 11, 2017

Please close this issue and update the corresponding RFS description since it is out-of-date now with the official release of F# 4.1. I found it confusing to read the RFS description reading as if this feature was still open to change.

21c-HK commented Apr 11, 2017

Please close this issue and update the corresponding RFS description since it is out-of-date now with the official release of F# 4.1. I found it confusing to read the RFS description reading as if this feature was still open to change.

@21c-HK

This comment has been minimized.

Show comment
Hide comment
@21c-HK

21c-HK Apr 11, 2017

While this is too late now - after the reading this discussion - I realized that the source of the numerous misunderstands in this discussion were due to the non-descriptive (non-)name of this type. „Result“ is really the worst possible name for a type that is supposed to be used for structured error handling of recoverable errors - just slightly less meaningful than „Choice“ and „Either“, which may be appropriate names for other purposes, but not for recoverable errors. This is why people here were talking about 4 completely different concerns as if it could/should be covered with a single type:

  • recoverable/expected errors like checked exceptions (ROP) [must be a monad for the right semantics]
  • unrecoverable/unexpected errors like unchecked exceptions (Scala's Try) [must be a monad for the right semantics]
  • validation [must be an applicative functor for the right semantics, not a monad!]
  • logging [must be an applicative functor for the right semantics, not a monad!]

Each concern requires a different dedicated type - at least partly due to the obviously different semantics provided by monads (i.e. shortcut semantics) vs. applicative functor (i.e. aggregating semantics). The other reason is that names are considered to be helpful. This is why I also think that it is a mistake to abbreviate identifiers even if they are commonly used. I think it's unreasonable to assume that someone somewhere may be programming on a 4" smartphone in an notepad-like editor where typing “Err“ vs. „Error“ actually matters.

We don't need to repeat the mistakes of other languages. The name „Result“ is completely meaningless in the context of functions since the return value of every proper function is referred to as the result - regardless of whether the function returns a value for every input in its domain, or not. I also feel that "Success" and "Failure" are more appropriate for unrecoverable errors like unchecked exceptions since division by zero is not an unexpected result. You know that you won't get the expected result in that case, so why should it be called "Failure" if you anticipated it?

21c-HK commented Apr 11, 2017

While this is too late now - after the reading this discussion - I realized that the source of the numerous misunderstands in this discussion were due to the non-descriptive (non-)name of this type. „Result“ is really the worst possible name for a type that is supposed to be used for structured error handling of recoverable errors - just slightly less meaningful than „Choice“ and „Either“, which may be appropriate names for other purposes, but not for recoverable errors. This is why people here were talking about 4 completely different concerns as if it could/should be covered with a single type:

  • recoverable/expected errors like checked exceptions (ROP) [must be a monad for the right semantics]
  • unrecoverable/unexpected errors like unchecked exceptions (Scala's Try) [must be a monad for the right semantics]
  • validation [must be an applicative functor for the right semantics, not a monad!]
  • logging [must be an applicative functor for the right semantics, not a monad!]

Each concern requires a different dedicated type - at least partly due to the obviously different semantics provided by monads (i.e. shortcut semantics) vs. applicative functor (i.e. aggregating semantics). The other reason is that names are considered to be helpful. This is why I also think that it is a mistake to abbreviate identifiers even if they are commonly used. I think it's unreasonable to assume that someone somewhere may be programming on a 4" smartphone in an notepad-like editor where typing “Err“ vs. „Error“ actually matters.

We don't need to repeat the mistakes of other languages. The name „Result“ is completely meaningless in the context of functions since the return value of every proper function is referred to as the result - regardless of whether the function returns a value for every input in its domain, or not. I also feel that "Success" and "Failure" are more appropriate for unrecoverable errors like unchecked exceptions since division by zero is not an unexpected result. You know that you won't get the expected result in that case, so why should it be called "Failure" if you anticipated it?

@neoeinstein

This comment has been minimized.

Show comment
Hide comment
@neoeinstein

neoeinstein Apr 11, 2017

Contributor

@21c-HK You're correct, this issue should now be closed. I do encourage you to bring these types of concerns to fslang-suggestions, as there are certainly things we can do to improve the current state.

Contributor

neoeinstein commented Apr 11, 2017

@21c-HK You're correct, this issue should now be closed. I do encourage you to bring these types of concerns to fslang-suggestions, as there are certainly things we can do to improve the current state.

@21c-HK

This comment has been minimized.

Show comment
Hide comment
@21c-HK

21c-HK Apr 13, 2017

Could someone who is in the know update the RFC to include the current state of Result? I can't find any official/up-to-date documentation on it except “Announcing F# 4.1 and the Visual F# Tools for Visual Studio 2017”, which refers to A Peek into F# 4.1 with regards to Result, which refers to this discussion/RFC and Microsoft/visualfsharp#964. I would like to know if Result stayed a “.NET reference type” (heap-allocated) or was changed to a “.NET value type” (stack-allocatable) using the new struct feature for multi-case unions?

21c-HK commented Apr 13, 2017

Could someone who is in the know update the RFC to include the current state of Result? I can't find any official/up-to-date documentation on it except “Announcing F# 4.1 and the Visual F# Tools for Visual Studio 2017”, which refers to A Peek into F# 4.1 with regards to Result, which refers to this discussion/RFC and Microsoft/visualfsharp#964. I would like to know if Result stayed a “.NET reference type” (heap-allocated) or was changed to a “.NET value type” (stack-allocatable) using the new struct feature for multi-case unions?

@cmeeren

This comment has been minimized.

Show comment
Hide comment
@cmeeren

cmeeren Sep 26, 2017

Is there any word on when/if this will be included in F# core?

cmeeren commented Sep 26, 2017

Is there any word on when/if this will be included in F# core?

@wallymathieu

This comment has been minimized.

Show comment
Hide comment
@wallymathieu

wallymathieu Sep 26, 2017

Try the latest FSharp.Core nuget package

Try the latest FSharp.Core nuget package

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment