Language-Ext, How to use applicatives in c#?

2.3k Views Asked by At

Hi I am building a sample of f# from https://fsharpforfunandprofit.com/posts/elevated-world-3/ in c#.

My code is as below,

 public class CustomerId : NewType<CustomerId, int> { public CustomerId(int id) : base(id) { } }

    public class EmailAddress : NewType<EmailAddress, string> { public EmailAddress(string email) : base(email) { } }

    public class Customer : Record<Customer>
    {
        public readonly CustomerId Id;
        public readonly EmailAddress Email;
        public Customer(CustomerId id, EmailAddress email)
        {
            Id = id;
            Email = email;
        }
    }

    public static class CustomerConstructor
    {
        public static Result<CustomerId> CreateCustomerId(int id)
        {
            if (id > 0) return new Result<CustomerId>.Success(new CustomerId(id));
            else return new Result<CustomerId>.Error(new[] { "invalid id" });
        }

        public static Result<EmailAddress> CreateCustomerEmail(string email)
        {
            if (string.IsNullOrEmpty(email)) return new Result<EmailAddress>.Error(new[] { "empty email" });
            else if (!email.Contains("@")) return new Result<EmailAddress>.Error(new[] { "invalid email" });
            else return new Result<EmailAddress>.Success(new EmailAddress(email));
        }
    }

    public abstract class Result<A>
    {
        public class Success : Result<A>
        {
            public readonly A Value;

            public Success(A value)
            {
                Value = value;
            }
        }

        public class Error : Result<A>
        {
            public readonly Arr<string> Errors;

            public Error(IEnumerable<string> errors)
            {
                Errors = errors.ToArr();
            }
        }

    }

public static class ResultModule
{
    public static UnitTest1.Result<A> Return<A>(this UnitTest1.Result<A> self, A a)
    {
        return new UnitTest1.Result<A>.Success(a);

    }

    public static UnitTest1.Result<A> Return<A>(A a)
    {
        return new UnitTest1.Result<A>.Success(a);

    }

    public static UnitTest1.Result<B> Select<A, B>(this UnitTest1.Result<A> self, Func<A, B> map)
        => Map<A, B>(self, map);

    public static UnitTest1.Result<B> Map<A, B>(this UnitTest1.Result<A> self, Func<A, B> map)
    {
        if (self is UnitTest1.Result<A>.Success)
        {
            var sx = (UnitTest1.Result<A>.Success)self;
            return new UnitTest1.Result<B>.Success(map(sx.Value));
        }
        else
        {
            var er = (UnitTest1.Result<A>.Error)self;
            return new UnitTest1.Result<B>.Error(er.Errors);
        }
    }

    public static UnitTest1.Result<B> ApplyMine<A, B>(this UnitTest1.Result<A> self, UnitTest1.Result<Func<A, B>> apply)
    {
        if (apply is UnitTest1.Result<Func<A, B>>.Success && self is UnitTest1.Result<A>.Success)
        {
            var f = (UnitTest1.Result<Func<A, B>>.Success)apply;
            var x = (UnitTest1.Result<A>.Success)self;

            return new UnitTest1.Result<B>.Success(f.Value(x.Value));
        }

        if (apply is UnitTest1.Result<Func<A, B>>.Error && self is UnitTest1.Result<A>.Success)
        {
            var f = (UnitTest1.Result<Func<A, B>>.Error)apply;
            return new UnitTest1.Result<B>.Error(f.Errors);
        }

        if (apply is UnitTest1.Result<Func<A, B>>.Success && self is UnitTest1.Result<A>.Error)
        {
            var x = (UnitTest1.Result<A>.Error)self;
            return new UnitTest1.Result<B>.Error(x.Errors);
        }

        if (apply is UnitTest1.Result<Func<A, B>>.Error && self is UnitTest1.Result<A>.Error)
        {
            var f = (UnitTest1.Result<Func<A, B>>.Error)apply;
            var x = (UnitTest1.Result<A>.Error)self;
            return new UnitTest1.Result<B>.Error(f.Errors.Concat(x.Errors));
        }

        return default(UnitTest1.Result<B>);//fn should never hit here
    }

    public static UnitTest1.Result<B> Bind<A, B>(this UnitTest1.Result<A> self, Func<A, UnitTest1.Result<B>> bind)
    {
        if (self is UnitTest1.Result<A>.Success)
        {
            var sx = (UnitTest1.Result<A>.Success)self;
            return bind(sx.Value);
        }
        else
        {
            var er = (UnitTest1.Result<A>.Error)self;
            return new UnitTest1.Result<B>.Error(er.Errors);
        }
    }

    public static UnitTest1.Result<C> SelectMany<A, B, C>(this UnitTest1.Result<A> self, Func<A, UnitTest1.Result<B>> bind, Func<A, B, C> project)
    {
        var bound = Bind<A, B>(self, bind);
        if (bound is UnitTest1.Result<B>.Success)
        {
            var sxA = (UnitTest1.Result<A>.Success)self;
            var sxB = (UnitTest1.Result<B>.Success)bound;
            return new UnitTest1.Result<C>.Success(project(sxA.Value, sxB.Value));

        }
        else
        {
            var er = (UnitTest1.Result<A>.Error)self;
            return new UnitTest1.Result<C>.Error(er.Errors);
        }
    }
}

Note: UnitTest1 is the namespace added (as there is a Result type in LanguageExt)

For the code above my test are as below

[TestMethod]
public void TestApplicativeValidation()
{
    var goodId = 1;
    var badId = 0;
    var goodEmail = "[email protected]";
    var badEmail = "example.com";

    Func<CustomerId, EmailAddress, Customer> createCustomer = (id, email) => new Customer(id, email);
    var idResult = CustomerConstructor.CreateCustomerId(goodId);
    var emailResult = CustomerConstructor.CreateCustomerEmail(goodEmail);
    var createCustomer1 = ResultModule.Return(createCustomer);

    //ResultModule.ApplyMine(idResult, )




}

[TestMethod]
public void TestMonadaicValidation()
{
    var goodId = 1;
    var badId = 0;
    var goodEmail = "[email protected]";
    var badEmail = "example.com";

    var goodCust = from id in CustomerConstructor.CreateCustomerId(goodId)
                   from email in CustomerConstructor.CreateCustomerEmail(goodEmail)
                   select new Customer(id, email);

    var badCust = from id in CustomerConstructor.CreateCustomerId(badId)
                  from email in CustomerConstructor.CreateCustomerEmail(badEmail)
                  select new Customer(id, email);

}

The Monadiac test runs as expeced and its all find, But I am unable to write test to check the applicative scenario as in the link,

let (<!>) = Result.map
let (<*>) = Result.apply

// applicative version
let createCustomerResultA id email = 
    let idResult = createCustomerId id
    let emailResult = createEmailAddress email
    createCustomer <!> idResult <*> emailResult
// int -> string -> Result<CustomerInfo>

Can any one guide me put some insights here, we have a linq expressions that automagically uses the select / select many, what in the case of applicative style?

2

There are 2 best solutions below

3
louthster On BEST ANSWER

You're looking for the Validation type in language-ext to implement the example. I won't do all the work for you, but you can take a look at one of the units tests which has a real-world example of using the applicative behaviour of the Validation type.

Most of the core types in language-ext support applicative behaviour through the apply function.

2
Abdul Kader Jeelani On

I ve found the right implementation of ApplyMine function above. Below is the test case of applicative.

  [TestMethod]
        public void TestApplicativeValidation()
        {
            var goodId = 1;
            var badId = 0;
            var goodEmail = "[email protected]";
            var badEmail = "example.com";

            Func<CustomerId, EmailAddress, Customer> createCustomer = (id, email) => new Customer(id, email);

            /*
            var idResult = CustomerConstructor.CreateCustomerId(goodId);
            var emailResult = CustomerConstructor.CreateCustomerEmail(goodEmail);
            var goodCustomer = idResult.Lift2(emailResult, createCustomer);
            */

            var good = CustomerConstructor.CreateCustomerId(goodId).Lift2(CustomerConstructor.CreateCustomerEmail(goodEmail), createCustomer);

            var bad22 = CustomerConstructor.CreateCustomerId(badId).Lift2(CustomerConstructor.CreateCustomerEmail(badEmail), createCustomer);

            var bad1 = CustomerConstructor.CreateCustomerId(goodId).Lift2(CustomerConstructor.CreateCustomerEmail(badEmail), createCustomer);

            var bad2 = CustomerConstructor.CreateCustomerId(badId).Lift2(CustomerConstructor.CreateCustomerEmail(goodEmail), createCustomer);


        }

Here is the Lift2 implementation added to the extension class / module.

 public static UnitTest1.Result<C> Lift2<A, B, C>(this UnitTest1.Result<A> self, UnitTest1.Result<B> other, Func<A, B, C> lift2)
        {
            Func<A, Func<B, C>> lifter = a => b => lift2(a, b);

            var aBakedIn = self.ApplyMine(ResultModule.Return(lifter));
            return other.ApplyMine(aBakedIn);
        }

In csharp there is no expression for applicative style programming where as for monadiac style we have linq

  var goodCust = from id in CustomerConstructor.CreateCustomerId(goodId)
                           from email in CustomerConstructor.CreateCustomerEmail(goodEmail)
                           select new Customer(id, email);

, It will more concise to call lift2 explicitly with 2 monads and a 2 parameter function. As like language-ext does through Prelute. I decided to follow "dot into notation" in c sharp as well when using functional constructs.

This article came to rescue when I was having brainfarts! http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html