İşlevleri parametre olarak alan işlevler de parametreleri bu işlevlere parametre olarak mı almalıdır?


20

Sıklıkla kendime böyle görünen işlevleri yazarken buluyorum çünkü veri erişimine kolayca takılmama izin veriyorlar ve yine de hangi verilere erişileceğini belirlemek için parametreleri kabul eden bir imza sağlıyorlar.

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

Veya

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

Sonra böyle bir şey kullanıyorum:

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

Bu yaygın bir uygulama mı? Daha fazla bir şey yapmam gerektiğini hissediyorum

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

Ama bu çok iyi görünmüyor çünkü her ücret türü için yönteme geçmek için yeni bir işlev yapmak zorunda kalıyordum.

Bazen yapmam gerektiğini hissediyorum

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

Ancak bu, getirme ve biçim yeniden kullanılabilirliğini ortadan kaldırıyor gibi görünüyor. Ne zaman almak ve biçimlendirmek istersem, biri getirmek için diğeri biçimlendirmek için iki satır yazmak zorundayım.

İşlevsel programlamada eksik olan ne? Bunu yapmanın doğru yolu mu, yoksa bakımı ve kullanımı kolay olan daha iyi bir kalıp var mı?


50
DI kanseri şu ana kadar yayıldı ...
Idan Arye

16
Bu yapının neden ilk etapta kullanılacağını görmek için mücadele ediyorum. Şüphesiz , biçimlendirme hızını parametre olarak döndüren bir işlevi kabul etmesinin aksine, bir format olarak formatlama hızını kabul etmek daha uygun mu (ve açık ) GetFormattedRate()?
aroth

6
Daha iyi bir yol, closuresparametrenin kendisini bir işleve nereye ilettiğinizi kullanmaktır , bu da size bu özel parametreye atıfta bulunan bir işlev verir. Bu "yapılandırılmış" işlev, onu kullanan işleve bir parametre olarak iletilir.
Thomas Junk

7
@IdanArye DI kanseri?
Jules

11
@Jules bağımlılığı enjeksiyon kanseri
kedi

Yanıtlar:


39

Bunu yeterince uzun yaparsanız, sonunda bu işlevi tekrar tekrar yazarken bulacaksınız:

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

Tebrikler, fonksiyon kompozisyonunu icat ettiniz .

Bunun gibi sarmalayıcı işlevlerinin, tek bir tipte uzmanlaştıklarında fazla bir kullanımı yoktur. Ancak, bazı tür değişkenleri tanıtırsanız ve input parametresini atlarsanız, GetFormattedRate tanımınız şöyle görünür:

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

Haliyle, yaptığınız şeyin çok az amacı vardır. Genel değildir, bu yüzden bu kodu her yerde çoğaltmanız gerekir. Kodunuzu çok karmaşık hale getiriyor, çünkü şimdi kodunuz bin küçük işlevden ihtiyaç duyduğu her şeyi kendi başına bir araya getirmek zorunda. Kalbiniz doğru yerde: sadece şeyleri bir araya getirmek için bu tür yüksek dereceli fonksiyonları kullanmaya alışmanız gerekir. Ya da, bırakmak için iyi bir eski moda lambda kullanmak Func<A, B>ve Aiçine Func<B>.

Kendinizi tekrarlamayın.


16
Kendinizi tekrarlamaktan kaçınmak kodu daha da kötüleştirirse kendinizi tekrarlayın. Sanki her zaman bu iki satırı yazmak yerine FormatRate(GetRate(rateKey)).
user253751

6
@immibis Sanırım GetFormattedRateşu andan itibaren doğrudan kullanabileceği fikri .
Carles

Sanırım burada yapmaya çalıştığım şey budur ve daha önce bu Oluştur işlevini denedim ama 2. fonksiyonum genellikle birden fazla parametreye ihtiyaç duyduğundan nadiren kullanmaya başladım. Thomas-önemsiz @ tarafından belirtildiği gibi Belki yapılandırılmış işlevler için kapanışları ile birlikte yapmanız gerekir
rushinge

@rushinge Bu tür bir kompozisyon, her zaman tek bir argümana sahip olan tipik FP işlevi üzerinde çalışır (ek argümanlar gerçekten kendi işlevleridir, bunu düşünün Func<Func<A, B>, C>); bu, herhangi bir işlev için çalışan yalnızca bir Oluşturma işlevine ihtiyacınız olduğu anlamına gelir. Ancak, C # fonksiyonları ile sadece kapakları kullanarak yeterince iyi çalışabilirsiniz - geçmek yerine, sadece Func<rateKey, rateType>gerçekten ihtiyacınız var Func<rateType>ve fonu geçerken, onu gibi inşa edersiniz () => GetRate(rateKey). Mesele şu ki, hedef fonksiyonun umurunda olmayan argümanları ortaya çıkarmazsınız.
Luaan

1
@immibis Evet, Composeişlev gerçekten yalnızca bir GetRatenedenden dolayı yürütmeyi ertelemeniz gerekiyorsa Compose(FormatRate, GetRate), örneğin kendi seçimini sağlayan bir işleve geçmek istiyorsanız, örneğin bir öğedeki her öğeye uygulamak için liste.
jpaugh

107

Bir işlevi ve parametrelerini iletmek için kesinlikle hiçbir neden yoktur, ancak o zaman bu parametrelerle çağırmak için. Aslında, durumda bir işlev geçmesine sebep yok hiç . Arayan kişi sadece işlevin kendisini çağırabilir ve sonucu iletebilir.

Bunu düşün - kullanmak yerine:

var formattedRate = GetFormattedRate(getRate, rateType);

neden sadece kullanmıyorsunuz:

var formattedRate = GetFormattedRate(getRate(rateType));

?

Gereksiz kodu azaltmanın yanı sıra bağlantıyı da azaltır - oranın getirilme şeklini değiştirmek istiyorsanız (örneğin, getRateşimdi iki bağımsız değişkene ihtiyaç duyuyorsanız) değiştirmeniz gerekmez GetFormattedRate.

Aynı şekilde, yazmak GetFormattedRate(formatRate, getRate, rateKey)yerine yazmak için hiçbir neden yoktur formatRate(getRate(rateKey)).

Aşırı karmaşık şeyler yapmayın.


3
Bu durumda haklısınız. Ancak iç işlev, örneğin bir döngüde veya bir harita işlevinde birden çok kez çağrıldıysa, argümanları iletme yeteneği yararlı olacaktır. Veya @Jack cevabında önerildiği gibi fonksiyonel kompozisyon / köri kullanın.
user949300

15
@ user949300 belki, ancak bu OP'nin kullanım durumu değildir (ve eğer öyleyse, formatRatebiçimlendirilmesi gereken oranlar üzerinde eşlenmesi gerekir).
jonrsharpe

4
@ user949300 Yalnızca diliniz kapanışları desteklemiyorsa veya lamdalar yazmak için uygun değilse
Bergi

4
Bir işlevi ve parametrelerini başka bir işleve geçirmenin , tembel anlambilim içermeyen bir dilde değerlendirmeyi geciktirmenin tamamen geçerli bir yolu olduğunu unutmayın. En.wikipedia.org/wiki/Thunk
Jared Smith

4
@JaredSmith İşlevi geçmek, evet, parametrelerini iletmek, ancak diliniz kapanışları desteklemiyorsa.
user253751

15

Fonksiyona kesinlikle bir işlev iletmeniz gerekiyorsa, çünkü ekstra bir argüman geçirir veya bir döngüde çağırırsa, bunun yerine bir lambda iletebilirsiniz:

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

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

Lambda, işlevin bilmediği argümanları bağlayacak ve var olduklarını bile gizleyecektir.


-1

İstediğin bu değil mi?

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

Ve sonra şöyle deyin:

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

        System.PrintLn(formattedRate);
    }
}

C # gibi nesne yönelimli bir dilde birden çok farklı şekilde davranabilen bir yöntem istiyorsanız, bunu yapmanın genel yolu, yöntemin soyut bir yöntem çağırmasıdır. Farklı bir şekilde yapmak için özel bir nedeniniz yoksa, bu şekilde yapmalısınız.

Bu iyi bir çözüm gibi mi görünüyor, yoksa düşündüğünüz dezavantajlar var mı?


1
Cevabınızda birkaç sakat şey var (sadece bir biçimlendirici ise neden biçimlendirici de oranı alıyor? GetFormattedRateYöntemi kaldırabilir ve sadece arayabilirsiniz IRateFormatter.FormatRate(rate)). Ancak temel kavram doğrudur ve çok OP çok biçimlendirme yöntemleri gerekiyorsa onun kod polimorfik uygulamak gerektiğini düşünüyorum.
BgrWorker
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.