滥用关闭?违反各种原则?或者可以吗?

发布于 2024-09-25 22:50:43 字数 1068 浏览 1 评论 0原文

编辑:修复了几个语法和一致性问题,使代码更加明显并接近我实际正在做的事情。

我有一些如下所示的代码:

SomeClass someClass;
var finalResult = 
  DoSomething(() => 
  {
    var result = SomeThingHappensHere();
    someClass = result.Data;
    return result;
  })
  .DoSomething(() => return SomeOtherThingHappensHere(someClass))
  .DoSomething(() => return AndYetAnotherThing())
  .DoSomething(() => return AndOneMoreThing(someClass))
  .Result;

HandleTheFinalResultHere(finalResult);

其中 DoSomething 方法是一个扩展方法,并且它需要传入一个 Func 。因此,每个 DoSomething => 中的每个方法都调用lambda 返回 Result 类型。

这类似于 也许是单子。除了检查空值之外,我正在检查 Result 类的状态,并且调用传递给 DoSomething 的 Func 或返回前一个 Result 而不调用 Func

我面临的问题是想要在其中包含这种组合我的代码,但我还需要能够将数据从一个组合调用结果传递到另一个调用结果中,正如您在 someClass 变量中看到的那样。

我的问题不是这在技术上是否正确......我知道这是可行的,因为我目前正在这样做。我的问题是这是否滥用闭包,或命令查询分离,或任何其他原则......然后询问有什么更好的模式来处理这种情况,因为我相当确定我现在,这种类型的代码陷入了“闪亮的新锤子”模式。

Edit: fixed several syntax and consistency issues to make the code a little more apparent and close to what I actually am doing.

I've got some code that looks like this:

SomeClass someClass;
var finalResult = 
  DoSomething(() => 
  {
    var result = SomeThingHappensHere();
    someClass = result.Data;
    return result;
  })
  .DoSomething(() => return SomeOtherThingHappensHere(someClass))
  .DoSomething(() => return AndYetAnotherThing())
  .DoSomething(() => return AndOneMoreThing(someClass))
  .Result;

HandleTheFinalResultHere(finalResult);

where the DoSomething method is an extension method, and it expects a Func passed into it. So, each of the method calls in each of the DoSomething => lambda's returns a Result type.

this is similar to a Maybe monad. Except instead of checking for nulls, I am checking the status of the Result class, and either calling the Func that was passed into DoSomething or returning the previous Result without calling the Func

the problem i face is that want to have this kind of composition in my code, but i also need to be able to pass data from one of the composed call results into the call of another, as you can see with the someClass variable.

My question isn't whether or not this is technically correct... i know this works, because I'm currently doing it. My question is whether or not this is an abuse of closures, or command-query separation, or any other principles... and then to ask what better patterns there are for handling this situation, because I'm fairly sure that I'm stuck in a "shiny new hammer" mode with this type of code, right now.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

金橙橙 2024-10-02 22:50:43

正如已经指出的,您几乎已经在这里实现了 Monad。

您的代码有点不优雅,因为 lambda 具有副作用。 Monad 可以更优雅地解决这个问题。

那么,为什么不把你的代码变成一个合适的 Monad 呢?

额外的好处:你可以使用 LINQ 语法!


我介绍:

LINQ to Results

示例:

var result =
    from a in SomeThingHappensHere()
    let someData = a.Data
    from b in SomeOtherThingHappensHere(someData)
    from c in AndYetAnotherThing()
    from d in AndOneMoreThing(someData)
    select d;

HandleTheFinalResultHere(result.Value);

对于LINQ to Results,首先执行SomeThingHappensHere。如果成功,它将获取结果的 Data 属性的值并执行 SomeOtherThingHappensHere。如果成功,它将执行 AndYetAnotherThing,依此类推。

正如您所看到的,您可以轻松地链接操作并引用先前操作的结果。每一项操作都会依次执行,遇到错误就会停止执行。

每行的 from x in 位有点嘈杂,但在我看来,没有任何类似复杂性的东西比这更具可读性!


我们如何做到这一点?

C# 中的 Monad 由三部分组成:

  • 类型 Something-of-T

  • Select/SelectMany 扩展方法,以及

  • T 转换为 Something-of-T

您所需要做的就是创建一些看起来像 Monad、感觉像 Monad、闻起来像 Monad 的东西,一切都会自动运行。


LINQ to Results 的类型和方法如下。

结果type:

表示结果的简单类。结果要么是 T 类型的值,要么是错误。结果可以从 TException 构造。

class Result<T>
{
    private readonly Exception error;
    private readonly T value;

    public Result(Exception error)
    {
        if (error == null) throw new ArgumentNullException("error");
        this.error = error;
    }

    public Result(T value) { this.value = value; }

    public Exception Error
    {
        get { return this.error; }
    }

    public bool IsError
    {
        get { return this.error != null; }
    }

    public T Value
    {
        get
        {
            if (this.error != null) throw this.error;
            return this.value;
        }
    }
}

扩展方法

SelectSelectMany 方法的实现。 C# 规范中给出了方法签名,因此您只需担心它们的实现。如果您尝试以有意义的方式组合所有方法参数,那么这些就会很自然地出现。

static class ResultExtensions
{
    public static Result<TResult> Select<TSource, TResult>(this Result<TSource> source, Func<TSource, TResult> selector)
    {
        if (source.IsError) return new Result<TResult>(source.Error);
        return new Result<TResult>(selector(source.Value));
    }

    public static Result<TResult> SelectMany<TSource, TResult>(this Result<TSource> source, Func<TSource, Result<TResult>> selector)
    {
        if (source.IsError) return new Result<TResult>(source.Error);
        return selector(source.Value);
    }

    public static Result<TResult> SelectMany<TSource, TIntermediate, TResult>(this Result<TSource> source, Func<TSource, Result<TIntermediate>> intermediateSelector, Func<TSource, TIntermediate, TResult> resultSelector)
    {
        if (source.IsError) return new Result<TResult>(source.Error);
        var intermediate = intermediateSelector(source.Value);
        if (intermediate.IsError) return new Result<TResult>(intermediate.Error);
        return new Result<TResult>(resultSelector(source.Value, intermediate.Value));
    }
}

您可以自由修改结果例如,类和扩展方法,以实现更复杂的规则。只有扩展方法的签名必须与规定的完全相同。

As has already been noted, you've almost implemented a Monad here.

Your code is a bit inelegant in that the lambdas have side-effects. Monads solve this more elegantly.

So, why not turn your code into a proper Monad?

Bonus: you can use LINQ syntax!


I present:

LINQ to Results

 
Example:

var result =
    from a in SomeThingHappensHere()
    let someData = a.Data
    from b in SomeOtherThingHappensHere(someData)
    from c in AndYetAnotherThing()
    from d in AndOneMoreThing(someData)
    select d;

HandleTheFinalResultHere(result.Value);

With LINQ to Results, this first executes SomeThingHappensHere. If that succeeds, it gets the value of the Data property of the result and executes SomeOtherThingHappensHere. If that succeeds, it executes AndYetAnotherThing, and so on.

As you can see, you can easily chain operations and refer to results of previous operations. Each operation will be executed one after another, and execution will stop when an error is encountered.

The from x in bit each line is a bit noisy, but IMO nothing of comparable complexity will get more readable than this!


How do we make this work?

Monads in C# consist of three parts:

  • a type Something-of-T,

  • Select/SelectMany extension methods for it, and

  • a method to convert a T into a Something-of-T.

All you need to do is create something that looks like a Monad, feels like a Monad and smells like a Monad, and everything will work automagically.


The types and methods for LINQ to Results are as follows.

Result<T> type:

A straightforward class that represents a result. A result is either a value of type T, or an error. A result can be constructed from a T or from an Exception.

class Result<T>
{
    private readonly Exception error;
    private readonly T value;

    public Result(Exception error)
    {
        if (error == null) throw new ArgumentNullException("error");
        this.error = error;
    }

    public Result(T value) { this.value = value; }

    public Exception Error
    {
        get { return this.error; }
    }

    public bool IsError
    {
        get { return this.error != null; }
    }

    public T Value
    {
        get
        {
            if (this.error != null) throw this.error;
            return this.value;
        }
    }
}

Extension methods:

Implementations for the Select and SelectMany methods. The method signatures are given in the C# spec, so all you have to worry about is their implementations. These come quite naturally if you try to combine all method arguments in a meaningful way.

static class ResultExtensions
{
    public static Result<TResult> Select<TSource, TResult>(this Result<TSource> source, Func<TSource, TResult> selector)
    {
        if (source.IsError) return new Result<TResult>(source.Error);
        return new Result<TResult>(selector(source.Value));
    }

    public static Result<TResult> SelectMany<TSource, TResult>(this Result<TSource> source, Func<TSource, Result<TResult>> selector)
    {
        if (source.IsError) return new Result<TResult>(source.Error);
        return selector(source.Value);
    }

    public static Result<TResult> SelectMany<TSource, TIntermediate, TResult>(this Result<TSource> source, Func<TSource, Result<TIntermediate>> intermediateSelector, Func<TSource, TIntermediate, TResult> resultSelector)
    {
        if (source.IsError) return new Result<TResult>(source.Error);
        var intermediate = intermediateSelector(source.Value);
        if (intermediate.IsError) return new Result<TResult>(intermediate.Error);
        return new Result<TResult>(resultSelector(source.Value, intermediate.Value));
    }
}

You can freely modify the Result<T> class and the extension methods, for example, to implement more complex rules. Only the signatures of the extension methods must be exactly as stated.

诗笺 2024-10-02 22:50:43

在我看来,你已经在这里构建了与 monad 非常相似的东西。

您可以通过将委托类型设置为 Func 来使其成为适当的 monad,并通过某种方式设置要传入的初始 SomeClass 值,并让DoSomething 将一个的返回值作为下一个的参数传递——这将使链接显式化,而不是依赖于词法作用域的共享状态。

Looks to me like you've built something very similar to a monad here.

You could make it a proper monad by making your delegate type a Func<SomeClass, SomeClass>, have some way to set up the initial SomeClass value to pass in, and have DoSomething pass the return value of one as the parameter of the next -- this would to make the chaining explicit rather than relying on lexically scoped shared state.

锦上情书 2024-10-02 22:50:43

该代码的弱点是第一个和第二个 lambda 之间的隐式耦合。我不确定修复它的最佳方法。

The weakness of this code is the implicit coupling between the first and second lambdas. I'm not sure of the best way to fix it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文