【问题标题】:CSharpFunctionalExtensions and railway oriented programmingCSharpFunctionalExtensions 和面向铁路的编程
【发布时间】:2019-10-08 08:20:28
【问题描述】:

来自这个惊人的课程:Applying Functional Principles in C#

我正在尝试应用领域驱动设计、功能原理和面向铁路的编程方法。

谁能帮我把下面的那几行代码精简一下?

我知道我需要创建一些 Result of T 扩展方法,我尝试过但无法让它们工作。

伪代码的作用是……

  1. 创建一个电子邮件值对象 -> Email.Create
  2. 创建和 EmailAddress 实体 -> EmailAddress.Create
  3. 结果存储在 T 变量 emailAddress 的 Result 中

(我们在访问数据库进行更新操作之前执行此操作,如果我们得到无效的EmailAddress,则完全没有任何意义访问数据库)

(由于需要稍后提供变量 emailAddress,铁路被破坏了:(太伤心了!!。更糟糕的是......未检查失败的 EmailAddress Result 实体,应该检查)

  1. 通过播放器存储库按 Id 获取播放器 -> repository.GetAsync("336978e9-837a-4e8d-6b82-08d6347fe6b6")
  2. 结果存储在T变量playerResult的Result中

(由于稍后需要使用变量 playerResult 导致铁路再次损坏 :( 太可悲了!!。更糟糕的是……未检查返回结果失败的播放器实体,它应该是)

  1. 将新创建的 EmailAddress 添加到 Player 电子邮件地址集合 -> p.AddEmailAddress(emailAddress.Value)

(铁路又被破坏了:(太伤心了!!。更糟糕的是......没有检查将电子邮件地址添加到玩家收藏的失败,它应该检查)

  1. 最后异步保存更改并返回受影响的行数。

为简洁起见,以下代码行减少

   var emailAddress = Email.Create("jamesbond@gmail.com")
                .OnSuccess(email => EmailAddress.Create(email, default));

            var playerResult = await emailAddress.OnSuccess(e => repository.GetAsync("336978e9-837a-4e8d-6b82-08d6347fe6b6")).ToResult(""));

            var wasAdded = playerResult.OnSuccess(p => p.AddEmailAddress(emailAddress.Value));

            var wasSaved = await wasAdded.OnSuccess(a => unitOfWork.SaveChangesAsync());

            var message = wasSaved
                .Ensure(r => r > 0, "No record were saved")
                .OnBoth(result => result.IsSuccess ? "Ok" : result.Error);

这里是方法的签名

Email.Create -> public static Result<Email> Create(string email)
EmailAddress.Create -> public static Result<EmailAddress> Create(Email mailAddress, string mailAddressInstructions)
GetAsync -> public Task<Maybe<Player>> GetAsync(string id)
AddEmailAddress -> public Result<bool> AddEmailAddress(EmailAddress emailAddress)
SaveChangesAsync -> public async Task<int> SaveChangesAsync()

我进行了一些单元测试,代码可以正常工作,但就您所见,与铁路相关的工作还很遥远。

提前致谢。

【问题讨论】:

    标签: c# functional-programming


    【解决方案1】:

    就个人而言,我更喜欢使用局部变量;我认为它更易于维护。在处理诸如工作单元之类的非功能性设计时尤其如此。但是,您可以使用函数式编程结构来实现。

    您需要使用一些东西来删除局部变量。首先,您的 monad 需要一个 bind;这就是允许您解开多个值然后将它们映射到新值的原因。第二个是元组。

    我发现让我的域类型本身对函数类型一无所知很有用。所以只有常规方法之类的,不返回 Result&lt;T&gt; 类型:

    private static Task<Player> RepositoryGetAsync(string id) => Task.FromResult(new Player());
    private static Task<int> RepositorySaveChangesAsync() => Task.FromResult(0);
    
    public sealed class Player
    {
        public bool AddEmailAddress(EmailAddress address) => true;
    }
    
    public sealed class Email
    {
        public Email(string address) => Address = address ?? throw new ArgumentNullException(nameof(address));
    
        public string Address { get; }
    }
    
    public sealed class EmailAddress
    {
        public static EmailAddress Create(Email address, int value) => new EmailAddress();
    }
    

    这是您的代码的第一个版本 - 使用 my own Try&lt;T&gt; type,因为我不确定您使用的是哪个 Maybe&lt;T&gt;Result&lt;T&gt; 类型。我的Try&lt;T&gt; 类型支持SelectMany,因此可以在multiple-from 子句中使用来解开多个实例:

    static async Task Main(string[] args)
    {
        var emailAddress = Try.Create(() => new Email("jamesbond@gmail.com"))
                .Map(email => EmailAddress.Create(email, default));
        var playerResult = await Try.Create(() => RepositoryGetAsync("336978e9-837a-4e8d-6b82-08d6347fe6b6"));
    
        var wasAdded = from address in emailAddress
                        from player in playerResult
                        select player.AddEmailAddress(address);
    
        var wasSaved = await wasAdded.Map(_ => RepositorySaveChangesAsync());
    
        if (wasSaved.Value == 0)
            throw new Exception("No records were saved");
    }
    

    如果我们开始使用元组,我们就可以组合几个变量。语法有点笨拙(例如,解构 lambda 参数),但它是可行的:

    static async Task Main(string[] args)
    {
        var emailAddressAndPlayerResult = await Try.Create(() => new Email("jamesbond@gmail.com"))
                .Map(email => EmailAddress.Create(email, default))
                .Map(async emailAddress => (emailAddress, await RepositoryGetAsync("336978e9-837a-4e8d-6b82-08d6347fe6b6")));
    
        var wasAdded = emailAddressAndPlayerResult.Map(((EmailAddress Address, Player Player) v) => v.Player.AddEmailAddress(v.Address));
    
        var wasSaved = await wasAdded.Map(_ => RepositorySaveChangesAsync());
    
        if (wasSaved.Value == 0)
            throw new Exception("No records were saved");
    }
    

    一旦我们混合了元组,其余的变量就会很好地折叠起来。剩下的唯一尴尬的部分是await 通常需要括号。例如,这段代码和上面的一样:

    static async Task Main(string[] args)
    {
        var wasAdded =
            (
                await Try.Create(() => new Email("jamesbond@gmail.com"))
                    .Map(email => EmailAddress.Create(email, default))
                    .Map(async emailAddress => (emailAddress, await RepositoryGetAsync("336978e9-837a-4e8d-6b82-08d6347fe6b6")))
            )
            .Map(((EmailAddress Address, Player Player) v) => v.Player.AddEmailAddress(v.Address));
    
        var wasSaved = await wasAdded.Map(_ => RepositorySaveChangesAsync());
    
        if (wasSaved.Value == 0)
            throw new Exception("No records were saved");
    }
    

    现在,当我们结合这些变量时,您可以看到await 表达式特别是如何在道路上产生“颠簸”。丑陋,但可行。删除最后一个变量:

    static async Task Main(string[] args)
    {
        var wasSaved =
            await
            (
                await Try.Create(() => new Email("jamesbond@gmail.com"))
                    .Map(email => EmailAddress.Create(email, default))
                    .Map(async emailAddress => (emailAddress, await RepositoryGetAsync("336978e9-837a-4e8d-6b82-08d6347fe6b6")))
            )
            .Map(((EmailAddress Address, Player Player) v) => v.Player.AddEmailAddress(v.Address))
            .Map(_ => RepositorySaveChangesAsync());
    
        if (wasSaved.Value == 0)
            throw new Exception("No records were saved");
    }
    

    重申我在开头所说的,可以做到这一点,但 IMO 很丑陋且不易维护。底线是 C# 是一种命令式语言而不是函数式语言。在代码的部分中采用函数式语言的一些方面可以使其更加优雅;试图强制你的所有代码完全正常运行是不可维护的秘诀。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-03
    • 1970-01-01
    • 1970-01-01
    • 2016-06-26
    • 2016-07-31
    • 1970-01-01
    • 2023-02-12
    • 2016-04-14
    相关资源
    最近更新 更多