Объединение сложного взаимодействия последовательностей делегатов

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

Без лишних слов объясню:

Сценарий состоит в том, что у вас есть делегаты преобразования (Func[A, B]) и поведение преобразования (Func[A, Func[Func[A, B], B]]).

Идея состоит в том, что вокруг данного перевода у вас будет определенный набор поведений, которые оборачивают вызов в перевод, т. е. что они берут А, не стесняются делать с ним все, что хотят, передают это следующему. поведение, при этом имея возможность изменить возвращаемое значение (B).

Вероятно, есть какая-то монада, которая идеально описывает это, но, возможно, некоторый код поможет больше, чем что-либо еще.

Жертвы:

public class B
{

}

public class A
{

}

Делегат поведения

public delegate Func<Func<A, B>, B> TranslationBehavior(A input);

Функция, которая должна связать их вместе и вернуть Func, которая позволяет передать функцию перевода и получить новую функцию перевода, которая обернута поведением.

static Func<Func<A, B>, Func<A, B>> Chain(IEnumerable<TranslationBehavior> behaviors)
{
    throw new NotImplementedException();
}

сценарий использования

static void Main(string[] args)
{
    var behaviors = new[]
    {
        (TranslationBehavior) (inp => next => next(inp)),
        (TranslationBehavior) (inp => next => next(inp)),
        (TranslationBehavior) (inp => next => next(inp)),
    };

    var input = new A();

    var chained = Chain(behaviors);

    var output = chained(a => new B());

}

В примере кода реализации поведения ничего не делают, а вызывают следующее поведение, а наша реализация перевода просто возвращает новый код B.

Функция «цепочка» — это проблемная функция, возможность связать поведение вместе ускользнула от меня, но чтобы доказать себе, что это действительно должно работать, я жестко запрограммировал наивное решение, которое специально связывает три поведения вместе:

static Func<Func<A, B>, Func<A, B>> Chain(IEnumerable<TranslationBehavior> behaviors)
{
    var behavior1 = (TranslationBehavior)null;
    var behavior2 = (TranslationBehavior)null;
    var behavior3 = (TranslationBehavior)null;

    return translation => input =>
        behavior1(input)(transformed1 =>
            behavior2(transformed1)(transformed2 =>
                behavior3(transformed2)(translation)
            )
        );
}

Это работает, но очевидно; совсем бесполезно.

Любая помощь по этому поводу была бы действительно полезной, и любая информация о том, является ли это известным шаблоном или монадой, была бы действительно интересной, я не думаю, что этот код далеко не обобщаемый.

Заранее спасибо, Стивен.


person meandmycode    schedule 12.02.2011    source источник
comment
Вы показываете (неполное) решение, но не говорите, в чем проблема. Чего вы пытаетесь достичь?   -  person dtb    schedule 13.02.2011
comment
Проблема заключается в функции Chain, окончательный код иллюстрирует, как поведения должны соединяться, но реализация жестко запрограммирована и фактически не использует последовательность действий.   -  person meandmycode    schedule 13.02.2011


Ответы (2)


Я не совсем понял ваш сценарий - без дополнительного контекста это звучит слишком сложно :-) Однако, только из типов, я думаю, что реализация, которую вы ищете, выглядит примерно так. Хитрость заключается в том, чтобы добавить метод, который работает с IEnumerator и является рекурсивным:

Func<Func<A, B>, Func<A, B>> Chain
  (IEnumerable<TranslationBehavior> behaviors, Func<A, B> final) {
    return translation => Chain(behaviors.GetEnumerator(), translation);
}

// Recursive method that takes IEnumerator and processes one element..
Func<A, B> Chain
  (IEnumerator<TranslationBehavior> behaviors, Func<A, B> last) {
    if (behaviors.MoveNext())
      return input => behaviors.Current(input)(Chain(behaviors, last));
    else
      return last;
}

Вы можете добавить обработку исключений и удалить перечислитель, но это должна быть правильная структура.

person Tomas Petricek    schedule 13.02.2011
comment
Ха! Томас, я поражен, что ты понял это из моего вопроса. Большое спасибо. - person meandmycode; 13.02.2011

Идея состоит в том, что вокруг данного перевода у вас будет определенный набор поведений, которые оборачивают вызов в перевод, т. е. что они берут А, не стесняются делать с ним все, что хотят, передают это следующему. поведение, при этом имея возможность изменить возвращаемое значение (B).

Это предложение кажется самым ясным изложением вашей проблемы в вопросе. Похоже, у вас есть

A -> {Black Box} -> B

и хотите

A -> pre-process A -> {same Black Box} -> B -> post-process and return B

Если это все, что вам нужно, я бы порекомендовал интерфейс и реализацию этого интерфейса, обертывающую другую реализацию. Это похоже на шаблоны Composite или Proxy, хотя и не совсем ни то, ни другое.

interface ITranslator 
{
    B Transform(A input);
}
class TranslatorWrapper : ITranslator
{
    TranslatorWrapper(ITranslator wrapped)
    {
        _wrapped = wrapped;
    }

    ITranslator _wrapped;

    B Transform(A input)
    {
        //do preprocessing here
        B result = _wrapped.Transform(input);
        //do postprocessing here
        return result;
    }
}

Здесь хорошо то, что вы можете передать «гнездо» или «цепочку» TranslatorWrappers и другие подобные классы, передав их в параметрах конструктора. В зависимости от желаемого синтаксиса вы можете добавить некоторые методы расширения, чтобы обеспечить более гибкий стиль кодирования для этих оболочек.

person Gideon Engelberth    schedule 13.02.2011
comment
Спасибо, Гидеон, это определенно какая-то форма прокси-шаблона, но очень важно, что он находится вокруг «вызова метода» и может переводить вызывающие аргументы и тип возвращаемого значения, насколько я могу судить, я не могу получить нужное мне поведение более лаконично. - person meandmycode; 13.02.2011