Elegant Solution for Discriminated Unions Representing Failures in F#What is the most idiomatic way of representing errors in F#How to use symbols/punctuation characters in discriminated unionsType-safe discriminated unions in C#, or: How to limit the number of implementations of an interface?How to enumerate a discriminated union in F#?F# understanding discriminated unionf#: constant union case tag numberF# Subtype Discriminated UnionsDocumenting discriminated unions in F#F# - Create a Recursive Discriminated Union at RuntimeF# Discriminated Unions and Printing“Merging” Discriminated Unions in F#?

How to realistically deal with a shield user?

Changing Row Keys into Normal Rows

The Game of the Century - why didn't Byrne take the rook after he forked Fischer?

Purchased new computer from DELL with pre-installed Ubuntu. Won't boot. Should assume its an error from DELL?

The meaning of "scale" in "because diversions scale so easily wealth becomes concentrated"

What does the ISO setting for mechanical 35mm film cameras actually do?

Getting an entry level IT position later in life

Can a Hogwarts student refuse the Sorting Hat's decision?

Does a 4 bladed prop have almost twice the thrust of a 2 bladed prop?

What is an air conditioner compressor hard start kit and how does it work?

What could prevent players from leaving an island?

Did Apollo leave poop on the moon?

Is an "are" omitted in this sentence

Ubuntu show wrong disk sizes, how to solve it?

How easy is it to get a gun illegally in the United States?

How many years before enough atoms of your body are replaced to survive the sudden disappearance of the original body’s atoms?

What prevents ads from reading my password as I type it?

Why is the Vasa Museum in Stockholm so Popular?

Is there a way to say "double + any number" in German?

Is it double speak?

I am considering a visit to a Nevada brothel. What should I say at the US border?

Should I take out a personal loan to pay off credit card debt?

What was the role of Commodore-West Germany?

Why should I "believe in" weak solutions to PDEs?



Elegant Solution for Discriminated Unions Representing Failures in F#


What is the most idiomatic way of representing errors in F#How to use symbols/punctuation characters in discriminated unionsType-safe discriminated unions in C#, or: How to limit the number of implementations of an interface?How to enumerate a discriminated union in F#?F# understanding discriminated unionf#: constant union case tag numberF# Subtype Discriminated UnionsDocumenting discriminated unions in F#F# - Create a Recursive Discriminated Union at RuntimeF# Discriminated Unions and Printing“Merging” Discriminated Unions in F#?






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;








2















I’m moving away from creating and catching exceptions in F# to something built around Result<'T, 'TError>. I found this, which agrees with my initial pursuit of representing failures with a discriminated union, but I ran into the problem of having a lot of different cases for my Failure discriminated union:



type TypedValue =
| Integer of int
| Long of int64
| …

type Failure =
| ArgumentOutOfRange of Argument : TypedValue; Minimum : TypedValue; Maximum : TypedValue
| BufferTooSmall of RequiredSize : int
| Exception of exn
| IndexOutOfRange of Index : int
| …


I’d prefer not to have a multitude of types dedicated to error handling. This “typed value” thing is not elegant at all as I either have to create conflicting names (Byte versus System.Byte) or create long names to avoid conflict (| UnsignedByte of byte).



Generics is a possibility, but then what would the 'T in Failure<'T> represent? ArgumentOutOfRange wouldn’t be the only case in the discriminated union, and some cases might require more type parameters or none at all.










share|improve this question



















  • 1





    Failure<'T> is normally just a string - an error message. Stop building that Failure DU to cover everything. If you absolutely do need something more than a string, then as a general rule place the failure type(s) needed for that in the domain where they belong, along with the specific success type(s) if there are any.

    – Bent Tranberg
    Mar 27 at 6:22












  • And if you haven't discovered this yet, do read it carefully: fsharpforfunandprofit.com/posts/recipe-part2

    – Bent Tranberg
    Mar 27 at 6:23











  • Also note that the Result type is a special case of the Choice type, which gives you more than two possible outcomes. Not saying you should start to use that for error handling in a lot of places, but just be aware of its usefulness.

    – Bent Tranberg
    Mar 27 at 6:34











  • Exceptions has a common base class, it would be quite difficult in the languages we usually use to have to declare a union exception class of every type. I think one can make a similar argument that the Failure type should only have a few relevant cases where one case is common base class. The Message ie string also makes sense to me (which I for error handling see as a limit common "base" class).

    – Just another metaprogrammer
    Mar 27 at 6:56

















2















I’m moving away from creating and catching exceptions in F# to something built around Result<'T, 'TError>. I found this, which agrees with my initial pursuit of representing failures with a discriminated union, but I ran into the problem of having a lot of different cases for my Failure discriminated union:



type TypedValue =
| Integer of int
| Long of int64
| …

type Failure =
| ArgumentOutOfRange of Argument : TypedValue; Minimum : TypedValue; Maximum : TypedValue
| BufferTooSmall of RequiredSize : int
| Exception of exn
| IndexOutOfRange of Index : int
| …


I’d prefer not to have a multitude of types dedicated to error handling. This “typed value” thing is not elegant at all as I either have to create conflicting names (Byte versus System.Byte) or create long names to avoid conflict (| UnsignedByte of byte).



Generics is a possibility, but then what would the 'T in Failure<'T> represent? ArgumentOutOfRange wouldn’t be the only case in the discriminated union, and some cases might require more type parameters or none at all.










share|improve this question



















  • 1





    Failure<'T> is normally just a string - an error message. Stop building that Failure DU to cover everything. If you absolutely do need something more than a string, then as a general rule place the failure type(s) needed for that in the domain where they belong, along with the specific success type(s) if there are any.

    – Bent Tranberg
    Mar 27 at 6:22












  • And if you haven't discovered this yet, do read it carefully: fsharpforfunandprofit.com/posts/recipe-part2

    – Bent Tranberg
    Mar 27 at 6:23











  • Also note that the Result type is a special case of the Choice type, which gives you more than two possible outcomes. Not saying you should start to use that for error handling in a lot of places, but just be aware of its usefulness.

    – Bent Tranberg
    Mar 27 at 6:34











  • Exceptions has a common base class, it would be quite difficult in the languages we usually use to have to declare a union exception class of every type. I think one can make a similar argument that the Failure type should only have a few relevant cases where one case is common base class. The Message ie string also makes sense to me (which I for error handling see as a limit common "base" class).

    – Just another metaprogrammer
    Mar 27 at 6:56













2












2








2








I’m moving away from creating and catching exceptions in F# to something built around Result<'T, 'TError>. I found this, which agrees with my initial pursuit of representing failures with a discriminated union, but I ran into the problem of having a lot of different cases for my Failure discriminated union:



type TypedValue =
| Integer of int
| Long of int64
| …

type Failure =
| ArgumentOutOfRange of Argument : TypedValue; Minimum : TypedValue; Maximum : TypedValue
| BufferTooSmall of RequiredSize : int
| Exception of exn
| IndexOutOfRange of Index : int
| …


I’d prefer not to have a multitude of types dedicated to error handling. This “typed value” thing is not elegant at all as I either have to create conflicting names (Byte versus System.Byte) or create long names to avoid conflict (| UnsignedByte of byte).



Generics is a possibility, but then what would the 'T in Failure<'T> represent? ArgumentOutOfRange wouldn’t be the only case in the discriminated union, and some cases might require more type parameters or none at all.










share|improve this question














I’m moving away from creating and catching exceptions in F# to something built around Result<'T, 'TError>. I found this, which agrees with my initial pursuit of representing failures with a discriminated union, but I ran into the problem of having a lot of different cases for my Failure discriminated union:



type TypedValue =
| Integer of int
| Long of int64
| …

type Failure =
| ArgumentOutOfRange of Argument : TypedValue; Minimum : TypedValue; Maximum : TypedValue
| BufferTooSmall of RequiredSize : int
| Exception of exn
| IndexOutOfRange of Index : int
| …


I’d prefer not to have a multitude of types dedicated to error handling. This “typed value” thing is not elegant at all as I either have to create conflicting names (Byte versus System.Byte) or create long names to avoid conflict (| UnsignedByte of byte).



Generics is a possibility, but then what would the 'T in Failure<'T> represent? ArgumentOutOfRange wouldn’t be the only case in the discriminated union, and some cases might require more type parameters or none at all.







error-handling f# conventions






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Mar 27 at 2:52









Kevin LiKevin Li

1841 silver badge10 bronze badges




1841 silver badge10 bronze badges










  • 1





    Failure<'T> is normally just a string - an error message. Stop building that Failure DU to cover everything. If you absolutely do need something more than a string, then as a general rule place the failure type(s) needed for that in the domain where they belong, along with the specific success type(s) if there are any.

    – Bent Tranberg
    Mar 27 at 6:22












  • And if you haven't discovered this yet, do read it carefully: fsharpforfunandprofit.com/posts/recipe-part2

    – Bent Tranberg
    Mar 27 at 6:23











  • Also note that the Result type is a special case of the Choice type, which gives you more than two possible outcomes. Not saying you should start to use that for error handling in a lot of places, but just be aware of its usefulness.

    – Bent Tranberg
    Mar 27 at 6:34











  • Exceptions has a common base class, it would be quite difficult in the languages we usually use to have to declare a union exception class of every type. I think one can make a similar argument that the Failure type should only have a few relevant cases where one case is common base class. The Message ie string also makes sense to me (which I for error handling see as a limit common "base" class).

    – Just another metaprogrammer
    Mar 27 at 6:56












  • 1





    Failure<'T> is normally just a string - an error message. Stop building that Failure DU to cover everything. If you absolutely do need something more than a string, then as a general rule place the failure type(s) needed for that in the domain where they belong, along with the specific success type(s) if there are any.

    – Bent Tranberg
    Mar 27 at 6:22












  • And if you haven't discovered this yet, do read it carefully: fsharpforfunandprofit.com/posts/recipe-part2

    – Bent Tranberg
    Mar 27 at 6:23











  • Also note that the Result type is a special case of the Choice type, which gives you more than two possible outcomes. Not saying you should start to use that for error handling in a lot of places, but just be aware of its usefulness.

    – Bent Tranberg
    Mar 27 at 6:34











  • Exceptions has a common base class, it would be quite difficult in the languages we usually use to have to declare a union exception class of every type. I think one can make a similar argument that the Failure type should only have a few relevant cases where one case is common base class. The Message ie string also makes sense to me (which I for error handling see as a limit common "base" class).

    – Just another metaprogrammer
    Mar 27 at 6:56







1




1





Failure<'T> is normally just a string - an error message. Stop building that Failure DU to cover everything. If you absolutely do need something more than a string, then as a general rule place the failure type(s) needed for that in the domain where they belong, along with the specific success type(s) if there are any.

– Bent Tranberg
Mar 27 at 6:22






Failure<'T> is normally just a string - an error message. Stop building that Failure DU to cover everything. If you absolutely do need something more than a string, then as a general rule place the failure type(s) needed for that in the domain where they belong, along with the specific success type(s) if there are any.

– Bent Tranberg
Mar 27 at 6:22














And if you haven't discovered this yet, do read it carefully: fsharpforfunandprofit.com/posts/recipe-part2

– Bent Tranberg
Mar 27 at 6:23





And if you haven't discovered this yet, do read it carefully: fsharpforfunandprofit.com/posts/recipe-part2

– Bent Tranberg
Mar 27 at 6:23













Also note that the Result type is a special case of the Choice type, which gives you more than two possible outcomes. Not saying you should start to use that for error handling in a lot of places, but just be aware of its usefulness.

– Bent Tranberg
Mar 27 at 6:34





Also note that the Result type is a special case of the Choice type, which gives you more than two possible outcomes. Not saying you should start to use that for error handling in a lot of places, but just be aware of its usefulness.

– Bent Tranberg
Mar 27 at 6:34













Exceptions has a common base class, it would be quite difficult in the languages we usually use to have to declare a union exception class of every type. I think one can make a similar argument that the Failure type should only have a few relevant cases where one case is common base class. The Message ie string also makes sense to me (which I for error handling see as a limit common "base" class).

– Just another metaprogrammer
Mar 27 at 6:56





Exceptions has a common base class, it would be quite difficult in the languages we usually use to have to declare a union exception class of every type. I think one can make a similar argument that the Failure type should only have a few relevant cases where one case is common base class. The Message ie string also makes sense to me (which I for error handling see as a limit common "base" class).

– Just another metaprogrammer
Mar 27 at 6:56












2 Answers
2






active

oldest

votes


















2














Using Result<'T, 'TError> makes a lot of sense in cases where you have custom kinds of errors that you definitely need to handle or in cases where you have some other logic for propagating errors than the one implemented by standard exceptions (e.g. if you can continue running code despite the fact that there was an error). However, I would not use it as a 1:1 replacement for exceptions - it will just make your code unnecessarilly complicated and cumbersome without really giving you much benefits.



To answer your question, since you are mirroring standard .NET exceptions in your discriminated union, you could probably just use a standard .NET exception in your Result type and use Result<'T, exn> as your data type:



if arg < 10 then Error(ArgumentOutOfRangeException("arg", "Value is too small"))
else OK(arg - 1)


Regarding the ArgumentOutOfRange union case and TypedValue - the reason for using something like TypedValue is typically that you need to pattern match on the possible values and do something with them. In case of exceptions, what do you want to do with the values? If you just need to report them to the user, then you can use obj which will let you easily print them (it won't be that easy to get the numerical values and do some further calculations with them, but I don't think you need that).



type Failure = 
| ArgumentOutOfRange of





share|improve this answer
































    1














    Another option (and what I normally do, personally) is to model your domain-specific failures with specific cases in your Failure union, and then have a general-purpose UnexpectedError case that takes an exn as its data and handles any non-domain-related failures. Then, when an error from one domain occurs in another, you can use Result.mapError to convert between them. Here's an example from a real domain I've modeled:



    open System

    // Top-level domain failures
    type EntityValidationError =
    | EntityIdMustBeGreaterThanZero of int64
    | InvalidTenant of string
    | UnexpectedException of exn

    // Sub-domain specific failures
    type AccountValidationError =
    | AccountNumberMustBeTenDigits of string
    | AccountNameIsRequired of string
    | EntityValidationError of EntityValidationError // Sub-domain representaiton of top-level failures
    | AccountValidationUnexpectedException of exn

    // Sub-domain Entity
    // The fields would probably be single-case unions rather than primitives
    type Account =

    Id: int64
    AccountNumber: string


    module EntityId =
    let validate id =
    if id > 0L
    then Ok id
    else Error (EntityIdMustBeGreaterThanZero id)

    module AccountNumber =
    let validate number =
    if number |> String.length = 10 && number |> Seq.forall Char.IsDigit
    then Ok number
    else Error (AccountNumberMustBeTenDigits number)

    module Account =
    let create id number =
    id
    |> EntityId.validate
    |> Result.mapError EntityValidationError // Convert to sub-domain error type
    |> Result.bind (fun entityId ->
    number
    |> AccountNumber.validate
    |> Result.map (fun accountNumber -> Id = entityId; AccountNumber = accountNumber ))





    share|improve this answer



























      Your Answer






      StackExchange.ifUsing("editor", function ()
      StackExchange.using("externalEditor", function ()
      StackExchange.using("snippets", function ()
      StackExchange.snippets.init();
      );
      );
      , "code-snippets");

      StackExchange.ready(function()
      var channelOptions =
      tags: "".split(" "),
      id: "1"
      ;
      initTagRenderer("".split(" "), "".split(" "), channelOptions);

      StackExchange.using("externalEditor", function()
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled)
      StackExchange.using("snippets", function()
      createEditor();
      );

      else
      createEditor();

      );

      function createEditor()
      StackExchange.prepareEditor(
      heartbeatType: 'answer',
      autoActivateHeartbeat: false,
      convertImagesToLinks: true,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: 10,
      bindNavPrevention: true,
      postfix: "",
      imageUploader:
      brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
      contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
      allowUrls: true
      ,
      onDemand: true,
      discardSelector: ".discard-answer"
      ,immediatelyShowMarkdownHelp:true
      );



      );













      draft saved

      draft discarded


















      StackExchange.ready(
      function ()
      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55369082%2felegant-solution-for-discriminated-unions-representing-failures-in-f%23new-answer', 'question_page');

      );

      Post as a guest















      Required, but never shown

























      2 Answers
      2






      active

      oldest

      votes








      2 Answers
      2






      active

      oldest

      votes









      active

      oldest

      votes






      active

      oldest

      votes









      2














      Using Result<'T, 'TError> makes a lot of sense in cases where you have custom kinds of errors that you definitely need to handle or in cases where you have some other logic for propagating errors than the one implemented by standard exceptions (e.g. if you can continue running code despite the fact that there was an error). However, I would not use it as a 1:1 replacement for exceptions - it will just make your code unnecessarilly complicated and cumbersome without really giving you much benefits.



      To answer your question, since you are mirroring standard .NET exceptions in your discriminated union, you could probably just use a standard .NET exception in your Result type and use Result<'T, exn> as your data type:



      if arg < 10 then Error(ArgumentOutOfRangeException("arg", "Value is too small"))
      else OK(arg - 1)


      Regarding the ArgumentOutOfRange union case and TypedValue - the reason for using something like TypedValue is typically that you need to pattern match on the possible values and do something with them. In case of exceptions, what do you want to do with the values? If you just need to report them to the user, then you can use obj which will let you easily print them (it won't be that easy to get the numerical values and do some further calculations with them, but I don't think you need that).



      type Failure = 
      | ArgumentOutOfRange of





      share|improve this answer





























        2














        Using Result<'T, 'TError> makes a lot of sense in cases where you have custom kinds of errors that you definitely need to handle or in cases where you have some other logic for propagating errors than the one implemented by standard exceptions (e.g. if you can continue running code despite the fact that there was an error). However, I would not use it as a 1:1 replacement for exceptions - it will just make your code unnecessarilly complicated and cumbersome without really giving you much benefits.



        To answer your question, since you are mirroring standard .NET exceptions in your discriminated union, you could probably just use a standard .NET exception in your Result type and use Result<'T, exn> as your data type:



        if arg < 10 then Error(ArgumentOutOfRangeException("arg", "Value is too small"))
        else OK(arg - 1)


        Regarding the ArgumentOutOfRange union case and TypedValue - the reason for using something like TypedValue is typically that you need to pattern match on the possible values and do something with them. In case of exceptions, what do you want to do with the values? If you just need to report them to the user, then you can use obj which will let you easily print them (it won't be that easy to get the numerical values and do some further calculations with them, but I don't think you need that).



        type Failure = 
        | ArgumentOutOfRange of





        share|improve this answer



























          2












          2








          2







          Using Result<'T, 'TError> makes a lot of sense in cases where you have custom kinds of errors that you definitely need to handle or in cases where you have some other logic for propagating errors than the one implemented by standard exceptions (e.g. if you can continue running code despite the fact that there was an error). However, I would not use it as a 1:1 replacement for exceptions - it will just make your code unnecessarilly complicated and cumbersome without really giving you much benefits.



          To answer your question, since you are mirroring standard .NET exceptions in your discriminated union, you could probably just use a standard .NET exception in your Result type and use Result<'T, exn> as your data type:



          if arg < 10 then Error(ArgumentOutOfRangeException("arg", "Value is too small"))
          else OK(arg - 1)


          Regarding the ArgumentOutOfRange union case and TypedValue - the reason for using something like TypedValue is typically that you need to pattern match on the possible values and do something with them. In case of exceptions, what do you want to do with the values? If you just need to report them to the user, then you can use obj which will let you easily print them (it won't be that easy to get the numerical values and do some further calculations with them, but I don't think you need that).



          type Failure = 
          | ArgumentOutOfRange of





          share|improve this answer













          Using Result<'T, 'TError> makes a lot of sense in cases where you have custom kinds of errors that you definitely need to handle or in cases where you have some other logic for propagating errors than the one implemented by standard exceptions (e.g. if you can continue running code despite the fact that there was an error). However, I would not use it as a 1:1 replacement for exceptions - it will just make your code unnecessarilly complicated and cumbersome without really giving you much benefits.



          To answer your question, since you are mirroring standard .NET exceptions in your discriminated union, you could probably just use a standard .NET exception in your Result type and use Result<'T, exn> as your data type:



          if arg < 10 then Error(ArgumentOutOfRangeException("arg", "Value is too small"))
          else OK(arg - 1)


          Regarding the ArgumentOutOfRange union case and TypedValue - the reason for using something like TypedValue is typically that you need to pattern match on the possible values and do something with them. In case of exceptions, what do you want to do with the values? If you just need to report them to the user, then you can use obj which will let you easily print them (it won't be that easy to get the numerical values and do some further calculations with them, but I don't think you need that).



          type Failure = 
          | ArgumentOutOfRange of






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Mar 27 at 11:59









          Tomas PetricekTomas Petricek

          206k15 gold badges303 silver badges477 bronze badges




          206k15 gold badges303 silver badges477 bronze badges


























              1














              Another option (and what I normally do, personally) is to model your domain-specific failures with specific cases in your Failure union, and then have a general-purpose UnexpectedError case that takes an exn as its data and handles any non-domain-related failures. Then, when an error from one domain occurs in another, you can use Result.mapError to convert between them. Here's an example from a real domain I've modeled:



              open System

              // Top-level domain failures
              type EntityValidationError =
              | EntityIdMustBeGreaterThanZero of int64
              | InvalidTenant of string
              | UnexpectedException of exn

              // Sub-domain specific failures
              type AccountValidationError =
              | AccountNumberMustBeTenDigits of string
              | AccountNameIsRequired of string
              | EntityValidationError of EntityValidationError // Sub-domain representaiton of top-level failures
              | AccountValidationUnexpectedException of exn

              // Sub-domain Entity
              // The fields would probably be single-case unions rather than primitives
              type Account =

              Id: int64
              AccountNumber: string


              module EntityId =
              let validate id =
              if id > 0L
              then Ok id
              else Error (EntityIdMustBeGreaterThanZero id)

              module AccountNumber =
              let validate number =
              if number |> String.length = 10 && number |> Seq.forall Char.IsDigit
              then Ok number
              else Error (AccountNumberMustBeTenDigits number)

              module Account =
              let create id number =
              id
              |> EntityId.validate
              |> Result.mapError EntityValidationError // Convert to sub-domain error type
              |> Result.bind (fun entityId ->
              number
              |> AccountNumber.validate
              |> Result.map (fun accountNumber -> Id = entityId; AccountNumber = accountNumber ))





              share|improve this answer





























                1














                Another option (and what I normally do, personally) is to model your domain-specific failures with specific cases in your Failure union, and then have a general-purpose UnexpectedError case that takes an exn as its data and handles any non-domain-related failures. Then, when an error from one domain occurs in another, you can use Result.mapError to convert between them. Here's an example from a real domain I've modeled:



                open System

                // Top-level domain failures
                type EntityValidationError =
                | EntityIdMustBeGreaterThanZero of int64
                | InvalidTenant of string
                | UnexpectedException of exn

                // Sub-domain specific failures
                type AccountValidationError =
                | AccountNumberMustBeTenDigits of string
                | AccountNameIsRequired of string
                | EntityValidationError of EntityValidationError // Sub-domain representaiton of top-level failures
                | AccountValidationUnexpectedException of exn

                // Sub-domain Entity
                // The fields would probably be single-case unions rather than primitives
                type Account =

                Id: int64
                AccountNumber: string


                module EntityId =
                let validate id =
                if id > 0L
                then Ok id
                else Error (EntityIdMustBeGreaterThanZero id)

                module AccountNumber =
                let validate number =
                if number |> String.length = 10 && number |> Seq.forall Char.IsDigit
                then Ok number
                else Error (AccountNumberMustBeTenDigits number)

                module Account =
                let create id number =
                id
                |> EntityId.validate
                |> Result.mapError EntityValidationError // Convert to sub-domain error type
                |> Result.bind (fun entityId ->
                number
                |> AccountNumber.validate
                |> Result.map (fun accountNumber -> Id = entityId; AccountNumber = accountNumber ))





                share|improve this answer



























                  1












                  1








                  1







                  Another option (and what I normally do, personally) is to model your domain-specific failures with specific cases in your Failure union, and then have a general-purpose UnexpectedError case that takes an exn as its data and handles any non-domain-related failures. Then, when an error from one domain occurs in another, you can use Result.mapError to convert between them. Here's an example from a real domain I've modeled:



                  open System

                  // Top-level domain failures
                  type EntityValidationError =
                  | EntityIdMustBeGreaterThanZero of int64
                  | InvalidTenant of string
                  | UnexpectedException of exn

                  // Sub-domain specific failures
                  type AccountValidationError =
                  | AccountNumberMustBeTenDigits of string
                  | AccountNameIsRequired of string
                  | EntityValidationError of EntityValidationError // Sub-domain representaiton of top-level failures
                  | AccountValidationUnexpectedException of exn

                  // Sub-domain Entity
                  // The fields would probably be single-case unions rather than primitives
                  type Account =

                  Id: int64
                  AccountNumber: string


                  module EntityId =
                  let validate id =
                  if id > 0L
                  then Ok id
                  else Error (EntityIdMustBeGreaterThanZero id)

                  module AccountNumber =
                  let validate number =
                  if number |> String.length = 10 && number |> Seq.forall Char.IsDigit
                  then Ok number
                  else Error (AccountNumberMustBeTenDigits number)

                  module Account =
                  let create id number =
                  id
                  |> EntityId.validate
                  |> Result.mapError EntityValidationError // Convert to sub-domain error type
                  |> Result.bind (fun entityId ->
                  number
                  |> AccountNumber.validate
                  |> Result.map (fun accountNumber -> Id = entityId; AccountNumber = accountNumber ))





                  share|improve this answer













                  Another option (and what I normally do, personally) is to model your domain-specific failures with specific cases in your Failure union, and then have a general-purpose UnexpectedError case that takes an exn as its data and handles any non-domain-related failures. Then, when an error from one domain occurs in another, you can use Result.mapError to convert between them. Here's an example from a real domain I've modeled:



                  open System

                  // Top-level domain failures
                  type EntityValidationError =
                  | EntityIdMustBeGreaterThanZero of int64
                  | InvalidTenant of string
                  | UnexpectedException of exn

                  // Sub-domain specific failures
                  type AccountValidationError =
                  | AccountNumberMustBeTenDigits of string
                  | AccountNameIsRequired of string
                  | EntityValidationError of EntityValidationError // Sub-domain representaiton of top-level failures
                  | AccountValidationUnexpectedException of exn

                  // Sub-domain Entity
                  // The fields would probably be single-case unions rather than primitives
                  type Account =

                  Id: int64
                  AccountNumber: string


                  module EntityId =
                  let validate id =
                  if id > 0L
                  then Ok id
                  else Error (EntityIdMustBeGreaterThanZero id)

                  module AccountNumber =
                  let validate number =
                  if number |> String.length = 10 && number |> Seq.forall Char.IsDigit
                  then Ok number
                  else Error (AccountNumberMustBeTenDigits number)

                  module Account =
                  let create id number =
                  id
                  |> EntityId.validate
                  |> Result.mapError EntityValidationError // Convert to sub-domain error type
                  |> Result.bind (fun entityId ->
                  number
                  |> AccountNumber.validate
                  |> Result.map (fun accountNumber -> Id = entityId; AccountNumber = accountNumber ))






                  share|improve this answer












                  share|improve this answer



                  share|improve this answer










                  answered Mar 27 at 12:50









                  Aaron M. EshbachAaron M. Eshbach

                  5,3559 silver badges19 bronze badges




                  5,3559 silver badges19 bronze badges






























                      draft saved

                      draft discarded
















































                      Thanks for contributing an answer to Stack Overflow!


                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid


                      • Asking for help, clarification, or responding to other answers.

                      • Making statements based on opinion; back them up with references or personal experience.

                      To learn more, see our tips on writing great answers.




                      draft saved


                      draft discarded














                      StackExchange.ready(
                      function ()
                      StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55369082%2felegant-solution-for-discriminated-unions-representing-failures-in-f%23new-answer', 'question_page');

                      );

                      Post as a guest















                      Required, but never shown





















































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown

































                      Required, but never shown














                      Required, but never shown












                      Required, but never shown







                      Required, but never shown







                      Popular posts from this blog

                      Kamusi Yaliyomo Aina za kamusi | Muundo wa kamusi | Faida za kamusi | Dhima ya picha katika kamusi | Marejeo | Tazama pia | Viungo vya nje | UrambazajiKuhusu kamusiGo-SwahiliWiki-KamusiKamusi ya Kiswahili na Kiingerezakuihariri na kuongeza habari

                      Swift 4 - func physicsWorld not invoked on collision? The Next CEO of Stack OverflowHow to call Objective-C code from Swift#ifdef replacement in the Swift language@selector() in Swift?#pragma mark in Swift?Swift for loop: for index, element in array?dispatch_after - GCD in Swift?Swift Beta performance: sorting arraysSplit a String into an array in Swift?The use of Swift 3 @objc inference in Swift 4 mode is deprecated?How to optimize UITableViewCell, because my UITableView lags

                      Access current req object everywhere in Node.js ExpressWhy are global variables considered bad practice? (node.js)Using req & res across functionsHow do I get the path to the current script with Node.js?What is Node.js' Connect, Express and “middleware”?Node.js w/ express error handling in callbackHow to access the GET parameters after “?” in Express?Modify Node.js req object parametersAccess “app” variable inside of ExpressJS/ConnectJS middleware?Node.js Express app - request objectAngular Http Module considered middleware?Session variables in ExpressJSAdd properties to the req object in expressjs with Typescript