Чи повинні функції, які приймають функції як параметри, також приймати параметри для цих функцій як параметри?


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");
}

Але це, здається, забирає будь-яку обробку та повторну зручність використання формату. Щоразу, коли я хочу отримати і форматувати, я повинен написати два рядки, один для отримання та один для форматування.

Що мені не вистачає у функціональному програмуванні? Це правильний спосіб зробити це, чи є кращий зразок, який простий в обслуговуванні та використанні?


50
Рак ДІ поширився досі ...
Ідан Ар'є

16
Я намагаюся зрозуміти, чому ця структура використовувалася б в першу чергу. Безумовно, зручніше (і зрозуміло ) GetFormattedRate()прийняти швидкість у форматі як параметр, на відміну від того, щоб вона прийняла функцію, яка повертає швидкість до формату як параметр?
aroth

6
Кращим способом є використання місця, closuresколи ви передаєте сам параметр функції, що взамін дає вам функцію, що посилається на цей специфічний параметр. Ця "налаштована" функція буде передана як параметр функції, яка її використовує.
Томас Джунк

7
@IdanArye DI рак?
Жуль

11
@ Рак від ін'єкції залежності від жуля
кот

Відповіді:


39

Якщо ви будете робити це досить довго, ви, зрештою, знову опиняєтеся писати цю функцію:

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

Вітаємо, ви придумали склад функції .

Такі функції обгортки не мають великого використання, коли вони спеціалізуються на одному типі. Однак якщо ви вводите деякі змінні типу і опускаєте вхідний параметр, то ваше визначення 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)).
користувач253751

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)).

Не переплутайте речі.


3
У цьому випадку ви праві. Але якби внутрішня функція була викликана кілька разів, скажімо, в циклі або у функції карти, тоді можливість передачі аргументів була б корисною. Або використовувати функціональну композицію / каррі, як запропоновано у відповіді @Jack.
user949300

15
@ user949300, можливо, але це не випадок використання ОП (і якщо це було, можливо, саме це formatRateслід відобразити за тарифами, які слід відформатувати).
jonrsharpe

4
@ User949300 Тільки якщо ваш язик не підтримує закриття або коли lamdas є hazzle до впечатать
Берг

4
Зауважте, що передача функції та її параметрів іншій функції є цілком коректним способом затримати оцінку мовою без ледачої семантики en.wikipedia.org/wiki/Thunk
Джаред Сміт

4
@JaredSmith Передача функції, так, передача її параметрів, лише якщо ваша мова не підтримує закриття.
користувач253751

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)). Однак основна концепція є правильною, і я думаю, що ОП також повинен реалізовувати свою кодову поліморфність, якщо йому потрібні кілька методів форматування.
BgrWorker
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.