Haruskah fungsi yang mengambil fungsi sebagai parameter, juga mengambil parameter ke fungsi tersebut sebagai parameter?

20

Saya sering menemukan diri saya menulis fungsi yang terlihat seperti ini karena memungkinkan saya untuk dengan mudah mengejek akses data, dan masih memberikan tanda tangan yang menerima parameter untuk menentukan data apa yang akan diakses.

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

Atau

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

Maka saya menggunakannya sesuatu seperti ini:

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

Apakah ini praktik umum? Saya merasa harus melakukan sesuatu yang lebih seperti

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

Tapi itu sepertinya tidak berfungsi dengan baik karena saya harus membuat fungsi baru untuk masuk ke metode untuk setiap jenis tingkat.

Terkadang saya merasa harus melakukan sesuatu

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

Tapi itu tampaknya mengambil pengambilan dan memformat penggunaan kembali. Setiap kali saya ingin mengambil dan memformat saya harus menulis dua baris, satu untuk mengambil dan satu untuk memformat.

Apa yang saya lewatkan tentang pemrograman fungsional? Apakah ini cara yang tepat untuk melakukannya, atau apakah ada pola yang lebih baik yang mudah dipelihara dan digunakan?

bergegas
sumber
50
Kanker DI telah menyebar sejauh ini ...
Idan Arye
16
Saya berjuang untuk melihat mengapa struktur ini akan digunakan di tempat pertama. Tentunya itu lebih nyaman (dan jelas ) untuk GetFormattedRate()menerima laju untuk memformat sebagai parameter, sebagai lawan dari itu menerima fungsi yang mengembalikan laju untuk memformat sebagai parameter?
Agustus
6
Cara yang lebih baik adalah memanfaatkan closurestempat Anda melewatkan parameter itu sendiri ke suatu fungsi, yang sebaliknya memberi Anda fungsi yang merujuk ke parameter khusus itu. Fungsi "yang dikonfigurasi" ini akan diteruskan sebagai parameter ke fungsi, yang menggunakannya.
Thomas Junk
7
@IdanArye DI kanker?
Jules
11
@Jules ketergantungan kanker injeksi
kucing

Jawaban:

39

Jika Anda melakukan ini cukup lama, pada akhirnya Anda akan menemukan diri Anda menulis fungsi ini berulang-ulang:

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

Selamat, Anda telah menemukan komposisi fungsi .

Fungsi wrapper seperti ini tidak banyak digunakan ketika mereka dikhususkan untuk satu jenis. Namun, jika Anda memperkenalkan beberapa variabel tipe dan menghilangkan parameter input, maka definisi GetFormattedRate Anda terlihat seperti ini:

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

Seperti apa adanya, apa yang Anda lakukan hanya memiliki sedikit tujuan. Itu tidak umum, jadi Anda perlu menduplikasi kode itu di semua tempat. Itu terlalu rumit kode Anda karena sekarang kode Anda harus mengumpulkan semua yang dibutuhkan dari seribu fungsi kecil sendiri. Hati Anda berada di tempat yang tepat: Anda hanya perlu membiasakan diri menggunakan fungsi - fungsi tingkat tinggi yang umum ini untuk menyatukan semuanya. Atau, gunakan lambda mode lama yang bagus untuk berubah Func<A, B>dan Amenjadi Func<B>.

Jangan ulangi dirimu sendiri.

Mendongkrak
sumber
16
Ulangi diri Anda sendiri jika menghindari pengulangan sehingga memperburuk kode. Seperti jika Anda selalu menulis dua baris itu, bukan FormatRate(GetRate(rateKey)).
user253751
6
@ imibis Saya kira idenya adalah bahwa dia akan dapat menggunakan GetFormattedRatelangsung mulai sekarang.
Carles
Saya pikir inilah yang saya coba lakukan di sini, dan saya sudah mencoba fungsi Menulis ini sebelumnya tetapi sepertinya saya jarang menggunakannya karena fungsi ke-2 saya sering membutuhkan lebih dari satu parameter. Mungkin saya perlu melakukan ini dalam kombinasi dengan penutupan untuk fungsi yang dikonfigurasi seperti yang disebutkan oleh @ thomas-junk
rushinge
@rushinge Komposisi semacam ini bekerja pada fungsi FP yang khas yang selalu memiliki argumen tunggal (argumen tambahan benar-benar fungsi mereka sendiri, anggap saja seperti Func<Func<A, B>, C>); ini berarti Anda hanya perlu satu fungsi Tulis yang berfungsi untuk fungsi apa pun. Namun, Anda dapat bekerja dengan fungsi C # cukup baik hanya menggunakan penutupan - alih-alih lewat Func<rateKey, rateType>, Anda hanya benar-benar membutuhkan Func<rateType>, dan ketika melewati fungsi, Anda membangunnya seperti () => GetRate(rateKey). Intinya adalah Anda tidak mengekspos argumen yang tidak diperhatikan oleh fungsi target.
Luaan
1
@immibis Ya, Composefungsi ini benar-benar hanya berguna jika Anda perlu menunda eksekusi GetRatekarena beberapa alasan, seperti jika Anda ingin meneruskan Compose(FormatRate, GetRate)ke fungsi yang memberikan tingkat pilihan sendiri, misalnya untuk menerapkannya ke setiap elemen dalam suatu daftar.
jpaugh
107

Sama sekali tidak ada alasan untuk melewatkan fungsi, dan parameternya, hanya untuk kemudian memanggilnya dengan parameter tersebut. Bahkan, dalam kasus Anda, Anda tidak punya alasan untuk melewatkan fungsi sama sekali . Penelepon mungkin juga hanya memanggil fungsi itu sendiri dan meneruskan hasilnya.

Pikirkan - alih-alih menggunakan:

var formattedRate = GetFormattedRate(getRate, rateType);

mengapa tidak menggunakan saja:

var formattedRate = GetFormattedRate(getRate(rateType));

?

Selain mengurangi kode yang tidak perlu, juga mengurangi kopling - jika Anda ingin mengubah cara pengambilannya (katakanlah, jika getRatesekarang membutuhkan dua argumen) Anda tidak perlu mengubah GetFormattedRate.

Demikian juga, tidak ada alasan untuk menulis GetFormattedRate(formatRate, getRate, rateKey)daripada menulis formatRate(getRate(rateKey)).

Jangan terlalu rumit.

pengguna253751
sumber
3
Dalam hal ini Anda benar. Tetapi jika fungsi bagian dalam dipanggil beberapa kali, katakan dalam satu lingkaran atau fungsi peta, maka kemampuan untuk menyampaikan argumen akan berguna. Atau gunakan komposisi fungsional / kari seperti yang diusulkan dalam jawaban @Jack.
user949300
15
@ user949300 mungkin, tapi itu bukan kasus penggunaan OP (dan jika ya, mungkin itu formatRateyang harus dipetakan atas tarif yang harus diformat).
jonrsharpe
4
@ user949300 Hanya jika bahasa Anda tidak mendukung penutupan atau ketika lamdas sangat suka mengetik
Bergi
4
Perhatikan bahwa meneruskan fungsi dan parameternya ke fungsi lain adalah cara yang benar - benar valid untuk menunda evaluasi dalam bahasa tanpa semantik malas en.wikipedia.org/wiki/Thunk
Jared Smith
4
@JaredSmith Melewati fungsi, ya, meneruskan parameternya, hanya jika bahasa Anda tidak mendukung penutupan.
user253751
15

Jika Anda benar-benar harus melewatkan fungsi ke dalam fungsi karena ia melewati beberapa argumen tambahan atau memanggilnya dalam satu lingkaran maka Anda bisa meneruskan lambda:

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

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

Lambda akan mengikat argumen yang tidak diketahui fungsi dan menyembunyikan bahwa mereka ada.

ratchet freak
sumber
-1

Bukankah ini yang kamu inginkan?

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

Dan menyebutnya seperti ini:

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

        System.PrintLn(formattedRate);
    }
}

Jika Anda menginginkan metode yang dapat berperilaku dalam berbagai cara berbeda dalam bahasa berorientasi objek seperti C #, cara yang biasa dilakukan adalah meminta metode memanggil metode abstrak. Jika Anda tidak memiliki alasan khusus untuk melakukannya dengan cara yang berbeda, Anda harus melakukannya dengan cara itu.

Apakah ini terlihat seperti solusi yang baik, atau apakah ada kelemahan yang Anda pikirkan?

Tanner Swett
sumber
1
Ada beberapa hal yang tidak beres dalam jawaban Anda (mengapa formatter juga mendapatkan nilai, jika itu hanya formatter? Anda juga bisa menghapus GetFormattedRatemetode dan panggil saja IRateFormatter.FormatRate(rate)). Namun konsep dasarnya sudah benar, dan saya pikir OP juga harus mengimplementasikan kode polimorfisnya jika ia memerlukan beberapa metode format.
BgrWorker