1

I'm fairly new at functional programming, and am trying to work within a very imperative code base, so I'm trying to write code that's mostly FP, but can't be fully.

I have to write an override for the following method...

protected override string CreateEntity(XElement xe)

...where xe contains two pieces of data needed for a database update. The method needs to validate the contents of xe, then do the update, returning "" on success or a pipe-delimited list of errors if not.

I took a cue from the credit card validation sample in the language-ext tests, and wrote some helper methods like the following (implementation very similar to the sample, so omitted)...

private Validation<string, int> ValidateRegionID(XElement xe)

In the sample, he used Apply on a collection of these, which returned a Validation that was the new credit card object if all went OK, or an Error if not. In my case, I don't have anything to return if all went well (as I'm doing a database update, not creating a new object), so got as far as this...

Validation<string, Unit> validation = (ValidateRegionID(xe), ValidateClinAppsID(xe))
  .Apply((regionID, clinAppsID) =>
  {
    // Do the database update (yes, impure code)...
    return Unit.Default;
  });

I don't know if this is the best way to do it, so any suggestions would be welcome.

My bigger problem is what to do next. The following works, but doesn't look very elegant or FP to me...

return validation.IsFail
  ? string.Join("|", validation.FailAsEnumerable())
  : "";

Is there a better way of doing this?

Arsen Khachaturyan
  • 7,904
  • 4
  • 42
  • 42
Avrohom Yisroel
  • 8,555
  • 8
  • 50
  • 106

2 Answers2

1

The last part of your code (formatting errors) could be done like this:

        [Fact]
        void Test()
        {
            Validation<string, Unit> resultFromDatabase = Fail<string, Unit>("error");

            var showError1 = string.Join("|", resultFromDatabase.FailToSeq()); // use the fact that FailToSeq() will return empty seq for success
            var showError2 = string.Join("|", resultFromDatabase.IfSuccess(Seq<string>())); // explicitly supply a value of the error type (Seq<string>) for success case
            var showError3 = resultFromDatabase.Match(_ => "", errors => string.Join("|", errors)); // only call string.Join for errors
        }

Take the variant you like most. If you want to have clean code / don't rely on FailToSeq() implementation working for both cases you should explicitly handle both cases using Match or IfSuccess/IfFail.

If you want to improve your overall design (avoiding impure code), you probably will have to change some fundamental things. One (very FP) option is to use a Free Monad and there was progress in LanguageExt to make that easier, but it's probably a big step to convert a traditional OO code base. See:

https://github.com/louthy/language-ext/releases/tag/3.4.11 (Free Monad using CodeGen) https://github.com/louthy/language-ext/wiki/Thinking-Functionally:-Application-Architecture

stb
  • 772
  • 5
  • 15
  • Thanks for that. I don't have any reason not to want to use `FailToSeq()` (any reason I should?), and that provides a neat solution. I'm happy to move towards a very FP approach, but am working with a large very imperative code base, and a manager who isn't so keen on radical changes. I'm also hampered by my lack of understanding, although that's getting better as I'm working on it. Thanks again. – Avrohom Yisroel Apr 23 '20 at 14:22
  • 1
    You're welcome. Taking the low hanging fruits to improve code locally is totally fine. Regarding `FailToSeq()`: If you call it for "success", too, you add two (small) risks: If implementation in LanguageExt changes (e.g. disallow this operation on "success" => exception) you're code breaks. I don't think Paul will change this (but seems this is not covered by a unit test). Second risk is that readers of your code might wonder what exactly happens in "success" case (exception vs. empty result). Depends on your context, overall readability ... – stb Apr 24 '20 at 11:58
0

You can simply throw an exception in case of validation failures. Whoever will call your code later can apply exception handling over it.

The other option is to send Error codes instead of messages. The callee, in this case, can have each error code translation to a particular error.

Arsen Khachaturyan
  • 7,904
  • 4
  • 42
  • 42
  • Thanks for the reply, but as I said, I need to override an existing method, so can't change what the calling code does. I must return a string, which will be empty if all went well, or contain pipe-delimited errors if not. – Avrohom Yisroel Apr 22 '20 at 18:08
  • @AvrohomYisroel in that case what you wrote is ok, I don't see other options in case if you certainly need to return error message combined together. – Arsen Khachaturyan Apr 22 '20 at 18:34