Должны ли функции, которые принимают функции в качестве параметров, также принимать параметры этих функций в качестве параметров?

20

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

public static string GetFormattedRate(
        Func<string, RateType>> getRate,
        string rateKey)
{
    var rate = getRate(rateKey);
    var formattedRate = rate.DollarsPerMonth.ToString("C0");
    return formattedRate;
}

Или

public static string GetFormattedRate(
        Func<RateType, string> formatRate,
        Func<string, RateType>> getRate,
        string rateKey)
{
    var rate = getRate(rateKey);
    var formattedRate = formatRate(rate);
    return formattedRate;
}

Тогда я использую что-то вроде этого:

using FormatterModule;

public static Main()
{
    var getRate = GetRateFunc(connectionStr);
    var formattedRate = GetFormattedRate(getRate, rateType);
    // or alternatively
    var formattedRate = GetFormattedRate(getRate, FormatterModule.FormatRate, rateKey);

    System.PrintLn(formattedRate);
}

Это обычная практика? Я чувствую, что должен делать что-то более похожее

public static string GetFormattedRate(
        Func<RateType> getRate())
{
    var rate = getRate();
    return rate.DollarsPerMonth.ToString("C0");
}

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

Иногда я чувствую, что должен делать

public static string GetFormattedRate(RateType rate)
{
   return rate.DollarsPerMonth.ToString("C0");
}

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

Что мне не хватает в функциональном программировании? Это правильный способ сделать это, или есть лучший шаблон, который легко поддерживать и использовать?

rushinge
источник
50
До сих пор рак DI распространился ...
Идан Арье
16
Я изо всех сил пытаюсь понять, почему эта структура будет использоваться в первую очередь. Конечно, более удобно (и понятно ) GetFormattedRate()принимать скорость форматирования в качестве параметра, а не принимать функцию, которая возвращает скорость для форматирования в качестве параметра?
Aroth
6
Лучшим способом является использование того, closuresгде вы передаете сам параметр в функцию, которая, в свою очередь, дает вам функцию, ссылающуюся на этот конкретный параметр. Эта «настроенная» функция будет передана в качестве параметра функции, которая ее использует.
Томас Джанк
7
@IdanArye DI рак?
Жюль
11
Инъекционный рак @Jules
кот

Ответы:

39

Если вы будете делать это достаточно долго, вы в конечном итоге будете писать эту функцию снова и снова:

public static Type3 CombineFunc1AndFunc2(
    Func<Type1, Type2> func1,
    Func<Type2, Type3>> func2,
    Type1 input)
{
    return func2(func1(input))
}

Поздравляем, вы придумали композицию функций .

Подобные функции Wrapper не имеют особого смысла, когда они специализированы для одного типа. Однако, если вы введете некоторые переменные типа и пропустите входной параметр, то ваше определение GetFormattedRate будет выглядеть так:

public static Func<A, C> Compose(
    Func<B, C> outer, Func<A, B>> inner)
{
    return (input) => outer(inner(input))
}

var GetFormattedRate = Compose(FormatRate, GetRate);
var formattedRate = GetFormattedRate(rateKey);

Как бы то ни было, то, что вы делаете, имеет мало смысла. Это не универсальный, поэтому вы должны дублировать этот код повсюду. Это усложняет ваш код, потому что теперь ваш код должен собрать все, что ему нужно, из тысячи крошечных функций самостоятельно. Однако ваше сердце находится в нужном месте: вам просто нужно привыкнуть к использованию этих видов универсальных функций высшего порядка для объединения вещей. Или использовать хорошую старую моды лямбду превратить Func<A, B>и Aв Func<B>.

Не повторяйся.

разъем
источник
16
Повторяйте себя, если избегание повторения делает код хуже. Например, вы всегда пишете эти две строки вместо FormatRate(GetRate(rateKey)).
user253751
6
@immibis Полагаю, идея в том, что он GetFormattedRateтеперь сможет использовать напрямую.
Карлес
Я думаю, что это то, что я пытаюсь сделать здесь, и я пробовал эту функцию Compose раньше, но мне кажется, что я редко использую ее, потому что моей 2-й функции часто требуется более одного параметра. Возможно, мне нужно сделать это в сочетании с замыканиями для сконфигурированных функций, как упомянуто @ thomas-junk
rushinge
@rushinge Этот тип композиции работает с типичной функцией FP, которая всегда имеет один аргумент (дополнительные аргументы на самом деле являются собственными функциями, представьте себе, что они похожи Func<Func<A, B>, C>); это означает, что вам нужна только одна функция Compose, которая работает для любой функции. Тем не менее, вы можете достаточно хорошо работать с функциями C #, просто используя замыкания - вместо того, чтобы передавать Func<rateKey, rateType>, вы действительно нуждаетесь в нем Func<rateType>, а когда передаете функцию, вы создаете ее следующим образом () => GetRate(rateKey). Дело в том, что вы не выставляете аргументы, которые не интересуют целевой функции.
Луаан
1
@immibis Да, Composeфункция действительно полезна только в том случае, если вам необходимо по GetRateкакой-то причине отложить выполнение , например, если вы хотите перейти Compose(FormatRate, GetRate)к функции, которая обеспечивает скорость по своему выбору, например, применить ее к каждому элементу в список.
jpaugh
107

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

Подумайте об этом - вместо использования:

var formattedRate = GetFormattedRate(getRate, rateType);

почему бы просто не использовать:

var formattedRate = GetFormattedRate(getRate(rateType));

?

Наряду с уменьшением ненужного кода это также уменьшает сцепление - если вы хотите изменить способ получения скорости (скажем, если getRateтеперь нужно два аргумента), вам не нужно менять GetFormattedRate.

Точно так же нет причин писать GetFormattedRate(formatRate, getRate, rateKey)вместо того, чтобы писать formatRate(getRate(rateKey)).

Не переусердствуйте.

user253751
источник
3
В этом случае вы правы. Но если бы внутренняя функция была вызвана несколько раз, скажем, в цикле или в функции отображения, тогда была бы полезна возможность передавать аргументы. Или используйте функциональную композицию / карри, как предложено в ответе @Jack.
user949300
15
@ user949300 возможно, но это не вариант использования OP (и если это так, возможно, это formatRateдолжно быть сопоставлено со скоростями, которые должны быть отформатированы).
Jonrsharpe
4
@ user949300 Только в том случае, если ваш язык не поддерживает замыкания или когда лямда не позволяет печатать
Bergi
4
Обратите внимание, что передача функции и ее параметров в другую функцию является полностью допустимым способом задержки оценки в языке без ленивой семантики. En.wikipedia.org/wiki/Thunk
Джаред Смит,
4
@JaredSmith Передача функции, да, передача ее параметров, только если ваш язык не поддерживает замыкания.
user253751
15

Если вам абсолютно необходимо передать функцию в функцию, потому что она передает дополнительный аргумент или вызывает ее в цикле, вы можете вместо этого передать лямбду:

public static string GetFormattedRate(
        Func<string> getRate)
{
    var rate = getRate();
    var formattedRate = rate.DollarsPerMonth.ToString("C0");
    return formattedRate;
}

var formattedRate = GetFormattedRate(()=>getRate(rateKey));

Лямбда свяжет аргументы, о которых функция не знает, и скроет, что они даже существуют.

чокнутый урод
источник
-1

Разве это не то, что вы хотите?

class RateFormatter
{
    public abstract RateType GetRate(string rateKey);

    public abstract string FormatRate(RateType rate);

    public string GetFormattedRate(string rateKey)
    {
        var rate = GetRate(rateKey);
        var formattedRate = FormatRate(rate);
        return formattedRate;
    }
}

И затем назовите это так:

static class Program
{
    public static void Main()
    {
        var rateFormatter = new StandardRateFormatter(connectionStr);
        var formattedRate = rateFormatter.GetFormattedRate(rateKey);

        System.PrintLn(formattedRate);
    }
}

Если вам нужен метод, который может вести себя по-разному в объектно-ориентированном языке, таком как C #, обычный способ сделать это - вызвать метод абстрактным методом. Если у вас нет конкретной причины сделать это по-другому, вы должны сделать это таким образом.

Похоже ли это на хорошее решение или есть недостатки?

Таннер Светт
источник
1
В вашем ответе есть несколько странных моментов (почему форматер также получает скорость, если это просто форматер? Вы также можете удалить GetFormattedRateметод и просто вызвать IRateFormatter.FormatRate(rate)). Однако основная концепция верна, и я думаю, что OP тоже должен реализовывать свой код полиморфно, если ему нужно несколько методов форматирования.
BgrWorker