【问题标题】:Monad Transformers in C#C# 中的 Monad 转换器
【发布时间】:2013-12-19 15:13:33
【问题描述】:

我正在研究在 C# 中使用 monad 转换器。
我想知道我提供的以下代码是否表明我已经理解了这一点。
我对此相当陌生,因此非常欢迎任何反馈/ cmets。
此示例仅用于将可能的 monad 包装在验证 monad 中。

using System;
using NUnit.Framework;

namespace Monads
{
    public static class MaybeExtensions
    {
        public static IMaybe<T> ToMaybe<T>(this T value)
        {
            if (value == null)
                return new None<T>();

            return new Just<T>(value);
        }
    }

    public interface IMaybe<T>
    {
        IMaybe<U> Select<U>(Func<T, U> f);

        IMaybe<U> SelectMany<U>(Func<T, IMaybe<U>> f);

        U Fold<U>(Func<U> error, Func<T, U> success);
    }

    public class Just<T> : IMaybe<T>
    {
        public Just(T value)
        {
            this.value = value;

        }

        public IMaybe<U> Select<U>(Func<T, U> f)
        {
            return f(value).ToMaybe();
        }

        public IMaybe<U> SelectMany<U>(Func<T, IMaybe<U>> f)
        {
            return f(value);
        }

        public U Fold<U>(Func<U> error, Func<T, U> success)
        {
            return success(value);
        }

        public IValidation<U, T> ToValidationT<U>()
        {
            return new ValidationMaybeT<U, T>(this, default(U));
        }

        private readonly T value;
    }

    public class None<T> : IMaybe<T>
    {
        public IMaybe<U> Select<U>(Func<T, U> f)
        {
            return new None<U>();
        }

        public IMaybe<U> SelectMany<U>(Func<T, IMaybe<U>> f)
        {
            return new None<U>();
        }

        public U Fold<U>(Func<U> error, Func<T, U> success)
        {
            return error();
        }

        public IValidation<U, T> ToValidationT<U>(U exceptionalValue)
        {
            return new ValidationMaybeT<U, T>(this, exceptionalValue);
        }
    }

    public class Customer
    {
        public Customer(string name)
        {
            Name = name;
        }

        public string Name { get; set; }
    }

    public interface IValidation<T, U>
    {
        IValidation<T, V> Select<V>(Func<U, V> f);

        IValidation<T, V> SelectMany<V>(Func<U, IValidation<T, V>> f);
    }

    public class ValidationError<T, U> : IValidation<T, U>
    {
        public ValidationError(T error)
        {
            Error = error;
        }

        public IValidation<T, V> Select<V>(Func<U, V> f)
        {
            return new ValidationError<T, V>(Error);
        }

        public IValidation<T, V> SelectMany<V>(Func<U, IValidation<T, V>> f)
        {
            return new ValidationError<T, V>(Error);
        }

        public T Error { get; private set; }
    }

    public class ValidationSuccess<T, U> : IValidation<T, U>
    {
        public ValidationSuccess(U value)
        {
            Result = value;
        }

        public IValidation<T, V> Select<V>(Func<U, V> f)
        {
            return new ValidationSuccess<T, V>(f(Result));
        }

        public IValidation<T, V> SelectMany<V>(Func<U, IValidation<T, V>> f)
        {
            return f(Result);
        }

        public U Result { get; private set; }
    }

    public class ValidationMaybeT<T, U> : IValidation<T, U>
    {
        public ValidationMaybeT(IMaybe<U> value, T error)
        {
            Value = value;
            Error = error;
        }

        public IValidation<T, V> Select<V>(Func<U, V> f)
        {
            return Value.Fold<IValidation<T, V>>(() => new ValidationError<T, V>(Error), s => new ValidationSuccess<T, V>(f(s)));
        }

        ValidationError<T, V> SelectManyError<V>()
        {
            return new ValidationError<T, V>(Error);
        }

        public IValidation<T, V> SelectMany<V>(Func<U, IValidation<T, V>> f)
        {
            return Value.Fold(() => SelectManyError<V>(), s => f(s));
        }

        public IMaybe<U> Value { get; private set; }

        public T Error { get; private set; }
    }

    public interface ICustomerRepository
    {
        IValidation<Exception, Customer> GetById(int id);
    }

    public class CustomerRepository : ICustomerRepository
    {
        public IValidation<Exception, Customer> GetById(int id)
        {

            if (id < 0)
                return new None<Customer>().ToValidationT<Exception>(new Exception("Customer Id less than zero"));

            return new Just<Customer>(new Customer("Structerre")).ToValidationT<Exception>();
        }
    }

    public interface ICustomerService
    {
        void Delete(int id);
    }

    public class CustomerService : ICustomerService
    {
        public CustomerService(ICustomerRepository customerRepository)
        {
            this.customerRepository = customerRepository;

        }

        public void Delete(int id)
        {
            customerRepository.GetById(id)
                .SelectMany(x => SendEmail(x).SelectMany(y => LogResult(y)));


        }

        public IValidation<Exception, Customer> LogResult(Customer c)
        {
            Console.WriteLine("Deleting: " + c.Name);
            return new ValidationSuccess<Exception, Customer>(c);
            //return new ValidationError<Exception, Customer>(new Exception("Unable write log"));
        }

        private IValidation<Exception, Customer> SendEmail(Customer c)
        {
            Console.WriteLine("Emailing: " + c.Name);
            return new ValidationSuccess<Exception, Customer>(c);
        }

        ICustomerRepository customerRepository;
    }

    [TestFixture]
    public class MonadTests
    {
        [Test]
        public void Testing_With_Maybe_Monad()
        {
            new CustomerService(new CustomerRepository()).Delete(-1);
        }
    }
}

另一个较小的子问题是,如果 C# 有更高种类的类型,我可以只实现这个类一次 (ValidationT),它适用于所有其他包装的 monad,还是不正确?

【问题讨论】:

标签: c# functional-programming monads monad-transformers higher-kinded-types


【解决方案1】:

差不多,是最快的答案。您的ValidationMaybeT 存储Maybe 的值,而真正的monad 转换器将具有Maybe 和Validation monad 的行为,并且可以在需要时修改包装monad 的默认行为。

这是一种非常手动的方法,我不一定会推荐它,它会变得非常混乱,非常快。 C# 缺乏更高层次的多态性会让你一有机会就绊倒。

我管理的最接近的(即使它不是一个合适的单子变压器系统)是我的图书馆:Language-Ext

项目中有 13 个 monad(Option、Map、Lst、Either、Try、Reader 等),我为它们实现了一组标准函数:

Sum      
Count    
Bind     
Exists   
Filter   
Fold     
ForAll   
Iter     
Map      
Select
SeletMany
Where
Lift

这些函数在函数式编程中最有用,并且几乎可以让您执行任何需要的操作。

因此,所有 monad 都实现了这些标准函数,它们变成了更高种类的类型。并不是编译器知道这一点,它们都只是同一个“集合”的一部分。

然后我编写了一个 T4 模板来生成作为扩展方法的转换器函数(它们有一个 T 后缀),用于 'higher-kinded 类型' 中的 monad 和函数的每个组合。

例如:

var list = List(Some(1),None,Some(2),None,Some(3));
var total = list.SumT();

上面的代码生成6。 SumT 的定义是:

int SumT(Lst<Option<int>> self) => 
    self.Map( s => s.Sum() ).Sum();

FilterT 例如也适用于内部单子:

var list = List(Some(1),None,Some(2),None,Some(3));
list = list.FilterT(x => x > 2);

所以扩展方法路由是一个非常好的方法。不要创建新类型,而是使用:

IValidation<IMaybe<T>>

然后为IValidation&lt;IMaybe&lt;T&gt;&gt;提供Maybe扩展方法

您可以按照我的做法从标准集自动生成,也可以手动编写它们。然后,它使您的 Maybe 和 Validation 实现保持干净,并且将定制的转换器功能分开。

如果您有兴趣,这是我用来生成转换器方法的 T4 模板(老实说,这很不稳定):LanguageExt.Core/HKT.tt

这是生成的代码:LanguageExt.Core/HKT.cs

在我做上面的 HKT 东西之前,我做了一个与你尝试类似的方法,我有一个名为 TryOption&lt;T&gt; 的单子,它是一个 Try 和一个 Option。但是有了新的 HKT 东西,我现在可以写 Try&lt;Option&lt;T&gt;&gt;。原来的实现是here:

无论如何,我希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-01
    • 1970-01-01
    • 2018-02-06
    • 1970-01-01
    • 2021-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多