As funções que assumem funções como parâmetros também devem levar parâmetros para essas funções como parâmetros?

20

Costumo me escrever funções que se parecem com isso, porque elas permitem que eu zombe facilmente do acesso a dados e ainda forneço uma assinatura que aceita parâmetros para determinar quais dados acessar.

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

Ou

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

Então eu uso algo assim:

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

Isto é uma prática comum? Eu sinto que deveria estar fazendo algo mais como

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

Mas isso não parece funcionar muito bem porque eu teria que criar uma nova função para passar para o método para cada tipo de taxa.

Às vezes eu sinto que deveria estar fazendo

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

Mas isso parece remover qualquer busca e formato reutilizáveis. Sempre que quero buscar e formatar, tenho que escrever duas linhas, uma para buscar e outra para formatar.

O que sinto falta na programação funcional? É o caminho certo para fazê-lo ou existe um padrão melhor, fácil de manter e usar?

pressa
fonte
50
O câncer DI se espalhou tão longe ...
Idan Arye
16
Eu luto para ver por que essa estrutura seria usada em primeiro lugar. Certamente é mais conveniente (e claro ) GetFormattedRate()aceitar a taxa para formatar como parâmetro, em vez de aceitar uma função que retorna a taxa para formatar como parâmetro?
Aroth 21/03/19
6
Uma maneira melhor é usar closuresonde você passa o próprio parâmetro para uma função, o que, por sua vez, fornece uma função referente a esse parâmetro específico. Essa função "configurada" seria passada como parâmetro para a função que a utiliza.
Thomas Junk
7
@IdanArye DI cancer?
Jules
11
@Jules dependency injection cancer
cat

Respostas:

39

Se você fizer isso por tempo suficiente, acabará escrevendo esta função repetidamente:

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

Parabéns, você inventou a composição das funções .

Funções de wrapper como essa não têm muita utilidade quando são especializadas em um tipo. No entanto, se você introduzir algumas variáveis ​​de tipo e omitir o parâmetro de entrada, sua definição de GetFormattedRate será semelhante a esta:

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

Tal como está, o que você está fazendo tem pouco propósito. Como não é genérico, é necessário duplicar esse código em todo o lugar. Isso complica demais o seu código, porque agora ele precisa reunir tudo o que precisa de milhares de pequenas funções por conta própria. Porém, seu coração está no lugar certo: você só precisa se acostumar a usar esses tipos de funções genéricas de ordem superior para organizar as coisas. Ou, use um bom lambda moda antiga para ligar Func<A, B>e Apara Func<B>.

Não se repita.

Jack
fonte
16
Repita-se se evitar repetir a si mesmo piora o código. Como se você sempre escrevesse essas duas linhas em vez de FormatRate(GetRate(rateKey)).
precisa saber é o seguinte
6
@immibis Acho que a ideia é que ele possa usar GetFormattedRatediretamente a partir de agora.
Carles
Acho que é isso que estou tentando fazer aqui, e já tentei essa função de composição antes, mas parece que raramente a uso porque minha segunda função geralmente precisa de mais de um parâmetro. Talvez eu preciso fazer isso em combinação com tampas para funções configuradas como mencionado por @ thomas-lixo
rushinge
@rushinge Esse tipo de composição funciona na função FP típica, que sempre possui um único argumento (argumentos adicionais são realmente funções próprias, pense nisso como Func<Func<A, B>, C>); isso significa que você só precisa de uma função de composição que funcione para qualquer função. No entanto, você pode trabalhar com funções C # bem o suficiente apenas usando fechamentos - em vez de passar Func<rateKey, rateType>, você só precisa realmente Func<rateType>e, ao passar a função, constrói-a assim () => GetRate(rateKey). O ponto é que você não expõe os argumentos com os quais a função de destino não se importa.
Luaan
1
@immibis Sim, a Composefunção é realmente útil apenas se você precisar atrasar a execução GetRatepor algum motivo, como se você deseja passar Compose(FormatRate, GetRate)para uma função que fornece uma taxa de sua própria escolha, por exemplo, aplicá-la a todos os elementos em um Lista.
jpaugh
107

Não há absolutamente nenhuma razão para passar uma função e seus parâmetros, apenas para chamá-la com esses parâmetros. De fato, em seu caso, você não tem nenhuma razão para passar uma função em tudo . O chamador pode também chamar a própria função e passar o resultado.

Pense nisso - em vez de usar:

var formattedRate = GetFormattedRate(getRate, rateType);

por que não simplesmente usar:

var formattedRate = GetFormattedRate(getRate(rateType));

?

Além de reduzir o código desnecessário, também reduz o acoplamento - se você deseja alterar a forma como a taxa é obtida (por exemplo, se getRateagora precisa de dois argumentos), não é necessário alterar GetFormattedRate.

Da mesma forma, não há razão para escrever em GetFormattedRate(formatRate, getRate, rateKey)vez de escrever formatRate(getRate(rateKey)).

Não complique demais as coisas.

user253751
fonte
3
Nesse caso, você está correto. Mas se a função interna fosse chamada várias vezes, digamos em loop ou em uma função de mapa, a capacidade de passar argumentos seria útil. Ou use composição funcional / curry como proposto na resposta do @Jack.
user949300
15
@ user949300 talvez, mas esse não é o caso de uso do OP (e, se foi, talvez seja isso formatRateque deve ser mapeado sobre as taxas que devem ser formatadas).
precisa saber é o seguinte
4
@ Apenas user949300 se o seu idioma não suporta fechamentos ou quando lamdas são uma hazzle para digitar
Bergi
4
Note que passar uma função e seus parâmetros para outra função é uma forma totalmente válida para avaliação demora em uma linguagem sem semântica preguiçosos en.wikipedia.org/wiki/Thunk
Jared Smith
4
@JaredSmith Passando a função, sim, passando seus parâmetros, apenas se o seu idioma não suportar fechamentos.
precisa saber é o seguinte
15

Se você absolutamente precisar passar uma função para a função porque ela passa algum argumento extra ou a chama em um loop, você pode passar um lambda:

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

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

O lambda vincula os argumentos que a função não conhece e oculta que eles existem.

catraca arrepiante
fonte
-1

Não é isso que você quer?

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

E então chame assim:

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

        System.PrintLn(formattedRate);
    }
}

Se você deseja um método que possa se comportar de várias maneiras diferentes em uma linguagem orientada a objetos, como C #, a maneira usual de fazer isso é fazer com que o método chame um método abstrato. Se você não tiver um motivo específico para fazê-lo de maneira diferente, faça-o dessa maneira.

Isso parece uma boa solução, ou há algumas desvantagens em que você está pensando?

Tanner Swett
fonte
1
Há algumas coisas estranhas em sua resposta (por que o formatador também está recebendo a taxa, se é apenas um formatador? Você também pode remover o GetFormattedRatemétodo e apenas chamar IRateFormatter.FormatRate(rate)). O conceito básico, no entanto, está correto, e acho que o OP também deve implementar seu código polimorficamente, se ele precisar de vários métodos de formatação.
BgrWorker 22/03/19