13

我正在阅读Tomas Petricek 和 Jon Skeet所著的Real-world functional programming一书,我很难消化关于计算表达式1)(又名 monads)的部分。

通过这本书,我了解到——与我以前的经验相反——LINQ 查询表达式不限于IEnumerable<T>,而是也可以在其他自定义类型上工作。这对我来说似乎很有趣,我想知道是否存在查询表达式语法 ( from x in ... select ...) 非常适合的场景。


一些背景资料:

显然,这种自定义类型被称为计算类型,它们被描述为与Haskell中的monad基本相同。我一直无法理解 monad 到底是什么,但根据书中的说法,它们是通过两个称为bindreturn的操作来定义的。

在函数式编程中,这两个操作的类型签名将是(我认为):

//    Bind      :    M<A'> -> (A' -> B') -> M<B'>
//
//    Return    :    A' -> M<A'>

M一元类型的名称在哪里。

在 C# 中,这对应于:

Func< M<A>, Func<A,B>, M<B> >   Bind;

Func< A, M<A> >                 Return;

事实证明,LINQ Enumerable.Select(投影运算符)与绑定操作具有完全相同的签名M := IEnumerable

我的自定义 LINQ 计算类型:

使用这些知识,我现在可以编写一个自定义计算类型,它不是 IEnumerable

// my custom computation type:
class Wrapped<A>
{
    // this corresponds to the Return operation:
    public Wrapped(A value)
    {
        this.Value = value;
    }

    public readonly A Value;
}

static class Wrapped
{
    // this corresponds to the Bind operation:
    public static Wrapped<B> Select<A, B>(this Wrapped<A> x, Func<A,B> selector)
    {
        return new Wrapped<B>(selector(x.Value));
    }
}

现在我可以Wrapped<T>在 LINQ 查询表达式中使用,例如:

Wrapped<int> wrapped = new Wrapped<int>(41);

Wrapped<int> answer  = from x in wrapped   // works on int values instead 
                       select x + 1;       // of Wrapped<int> values!

当然,这个例子不是很有用,但它演示了如何使查询表达式做一些除了使用集合之外的事情,例如用某种类型包装和展开值。


问题:

上面的计算类型似乎不是很有用。因此我想知道,使用 LINQ 查询表达式还有哪些其他合理的用途(除了处理集合)?


1)第 12.4 节:“介绍替代工作流程”,从第 334 页开始。

4

4 回答 4

4

想法:

  • PushLINQ (me and Jon) - 将 LINQ 反转为推模型(而不是IEnumerable<T>拉模型)
  • Reactive Framework / Reactive Extensions - 另一个非常不同的包含 LINQ 语法的事件模型
  • 我使用 LINQ 查询语法编写了一个线程 API (ab);我不是 100% 相信它,所以放弃了它——但它很有趣;使用from( SelectMany) 等来挑选与可枚举完全无关的分支/合并点
于 2010-06-04T14:01:58.440 回答
4

尽管我不喜欢这样做(因为感觉有点像作弊),但我想这次我必须回答我自己的问题。

我对此进行了更多思考。我的问题有点幼稚。它归结为 LINQ 查询表达式(例如from……………… in)以及其他更基本语法之上的语法糖whereselectforeach

  • foreach适用于任何实现IEnumerator<T> GetEnumerator()方法的东西。IEnumerable<T>恰好满足了这个条件。

  • 类似地,LINQ 查询表达式会根据一些定义明确的规则进行翻译,例如from x in xs where x > 0 select -x变成xs.Where(x => x > 0).Select(x => -x). 只要某些类型实现了部分或全部查询运算符方法,该类型就可以与 LINQ 一起用于几乎任何目的。

我剩下的问题是除了处理集合之外,LINQ 可以实际用于什么;我认为答案是“很少有其他”,因为 LINQ 查询表达式的结构非常僵化。你总是需要from……in部分。似乎select……也总是需要的。如果生成的表达式的“语言”不适合特定的潜在场景,则任何其他关键字(letorderbygroupby等)都不会改善这种情况。LINQ 在设计时非常明确地考虑了一个目标,即查询数据,而由此产生的语法实际上对 LINQ 的限制可能超出了必要的范围。

除了查询数据和 F# 计算表达式的可能性之外,我已经将 LINQ 的可能性与 F# 计算表达式的可能性进行了比较,它们似乎更灵活,因为没有那么多必需的关键字。这往往使它们适用于更多场景。

于 2010-06-10T21:17:36.257 回答
3

LinqToTwitter 以一种不同寻常的方式使用 LINQ。'from' 子句中的东西在逻辑上不是可枚举类型。看看源代码:)

于 2010-06-04T10:46:54.290 回答
1

与 monad 在 Haskell 中有用的原因相同。Either<TLeft, TRight>例如,实现一个类型并为其提供查询理解实现是非常有可能的。这可用于编写更多功能代码,您可以在用例中编写操作,并内置错误处理和日志记录等。基本上,看看像 Haskell (或 F# 这样更贴近家庭的语言)之类的语言,并了解如何在实际代码中使用 monad。

现在,问题当然是这可能不是非常惯用的 C# 代码,即使它可以工作并且可维护等等。重要的是不要与语言抗争,不要编写只有你能理解的代码。至少,您的团队应该“参与其中”。

于 2016-05-19T14:27:09.820 回答