以函数为参数的函数是否还应该以那些函数的参数为​​参数?


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
迄今为止,DI癌症已经扩散了……
Idan Arye

16
我很难理解为什么首先要使用这种结构。当然,接受将格式格式化的速率作为参数的方法更方便(而且很明确GetFormattedRate(),而不是让它接受将格式格式化的速率作为参数的函数呢?
aroth

6
更好的方法是利用closures将参数本身传递给函数的位置,这反过来又为您提供了引用该特定参数的函数。该“已配置”功能将作为参数传递给使用它的功能。
Thomas Junk

7
@IdanArye DI癌症?
Jules

11
@Jules依赖注射癌

Answers:


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

就目前而言,您所做的没有什么目的。它不是通用的,因此您需要在各处复制该代码。它使您的代码过于复杂,因为现在您的代码必须自己组装来自千个微型函数的所有内容。但是,您的心在正确的地方:您只需要习惯使用这些通用的高阶函数将事物放在一起即可。或者,使用优质的老式Lambda将Func<A, B>A变成Func<B>

不要重复自己。


16
如果要避免重复做会使代码更糟,请重复做一次。例如,如果您总是写这两行而不是FormatRate(GetRate(rateKey))
user253751 '17

6
@immibis我想这个主意是他GetFormattedRate从现在起将可以直接使用。
卡尔斯

我认为这是我要在这里执行的操作,并且之前已经尝试过此Compose函数,但是由于我的第二个函数通常需要多个参数,因此似乎很少使用它。也许我需要结合@ thomas-junk提到的配置函数的闭包来完成此操作
rushinge

@rushinge这种组合适用于通常只有一个参数的典型FP函数(其他参数实际上是它们自己的函数,请想一想Func<Func<A, B>, C>)。这意味着您只需要一个适用于任何功能的Compose函数。但是,仅使用闭包就可以很好地使用C#函数- Func<rateKey, rateType>您真正需要的是传递而不是传递,Func<rateType>传递函数时,您可以像那样构建函式() => GetRate(rateKey)。关键是您不会公开目标函数不关心的参数。
a安

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
在这种情况下,您是正确的。但是,如果多次调用内部函数(例如在循环或map函数中),则传递参数的功能将很有用。或使用@Jack答案中所建议的功能组合/咖喱。
user949300

15
@ user949300可能是这样,但这不是OP的用例(如果是,则formatRate应该将其映射到应格式化的速率上)。
jonrsharpe

4
@ user949300仅当您的语言不支持闭包或lamda令人讨厌时
Bergi

4
请注意,将一个函数及其参数传递给另一个函数是在没有惰性语义的情况下延迟语言评估的一种完全有效的方法en.wikipedia.org/wiki/Thunk
Jared Smith,

4
@JaredSmith是的,仅当您的语言不支持闭包时才传递函数,是的,传递其参数。
user253751 '17

15

如果由于传递了一些额外的参数或在循环中调用它而绝对需要将一个函数传递给该函数,则可以传递一个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将绑定函数不知道的参数,并隐藏它们甚至存在。


-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需要多种格式化方法,那么OP也应该实现其代码多态性。
BgrWorker
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.