Выражения запросов LINQ, которые работают с типами (монадами?), Отличными от IEnumerable <T>. - PullRequest
13 голосов
/ 04 июня 2010

Я читаю книгу Реальное функциональное программирование Томаса Петрисека и Джона Скита , и мне трудно переварить раздел о выражениях вычислений 1 ) (он же монады).

Из этой книги я узнал, что & mdash; вопреки моему предыдущему опыту & mdash; Выражения запросов LINQ не ограничиваются IEnumerable<T>, но могут работать и с другими пользовательскими типами. Мне это кажется очень интересным, и мне интересно, существуют ли сценарии, где синтаксис выражения запроса (from x in ... select ...) было бы неплохо.


Некоторая справочная информация:

По-видимому, такие пользовательские типы называются типами вычислений , которые изображаются как по сути то же самое, что и монады в Haskell . Я никогда не мог понять, что такое монады, но согласно книге они определяются с помощью двух операций, называемых bind и return .

В функциональном программировании сигнатуры типов этих двух операций были бы (я думаю):

//    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, например ::1010*

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 голосов
/ 11 июня 2010

Несмотря на то, что мне не нравится это делать (поскольку это немного похоже на мошенничество), я думаю, что на этот раз я должен ответить на свой вопрос.

Я подумал еще об этом. Мой вопрос был несколько наивным. Суть в том, что выражения запросов LINQ (например, from & hellip; in & hellip; where & hellip; select & hellip;), а также foreach являются синтаксическим сахаром поверх другого, более базового синтаксиса .

  • 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 & hellip; in. Кажется, что select & hellip; тоже всегда нужен. Если «язык» полученных выражений не соответствует конкретному потенциальному сценарию, ни одно из других ключевых слов (let, orderby, groupby и т. Д.) Не улучшит ситуацию. LINQ был довольно четко разработан с единственной целью: запрос данных, а полученная грамматика фактически ограничивает LINQ более, чем, вероятно, необходимо.

Я сравнил возможности LINQ для других целей, помимо запроса данных, с возможностями выражений вычислений F #, и они кажутся более гибкими, поскольку не так много обязательных ключевых слов. Что делает их подходящими для большего количества сценариев.

3 голосов
/ 04 июня 2010

Мысль:

  • PushLINQ (я и Джон) - обращает LINQ к модели push (вместо модели IEnumerable<T> pull)
  • Reactive Framework / Reactive Extensions - еще одна совершенно другая модель событий, включающая синтаксис LINQ
  • Я написал API потоков (ab), используя синтаксис запроса LINQ; Я не был на 100% убежден в этом, поэтому бросил это - но это было интересно; используется from (SelectMany) и т. д. для выбора точек ветвления / слияния полностью , не связанных с перечислимыми
3 голосов
/ 04 июня 2010

LinqToTwitter использует LINQ необычным образом. Материал в предложении from не является логически перечислимым типом. Посмотрите на источник:)

1 голос
/ 19 мая 2016

Это полезно по тем же причинам, по которым монады полезны в Haskell. Очень возможно реализовать тип Either<TLeft, TRight> и предоставить ему, например, реализации для понимания запросов. Это может быть использовано для написания более функционального кода, где вы создаете действия в сценарии использования со встроенной обработкой ошибок, ведением журнала и так далее. В общем, взгляните на такие языки, как Haskell (или F # для чего-то ближе к дому), и почувствуйте, как монады используются в реальном коде.

Теперь проблема, конечно, в том, что это может быть не очень идиоматический код C #, даже если он работает и поддерживается и т. Д. Важно не бороться с языком и не писать код, понятный только вам. По крайней мере, ваша команда должна быть в ней.

...