Các hàm có nên lấy các hàm làm tham số, cũng nên lấy tham số cho các hàm đó làm tham số?


20

Tôi thường thấy mình viết các hàm trông như thế này vì chúng cho phép tôi dễ dàng giả định truy cập dữ liệu và vẫn cung cấp chữ ký chấp nhận các tham số để xác định dữ liệu nào sẽ truy cập.

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

Hoặc là

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

Sau đó, tôi sử dụng nó như thế này:

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

Đây có phải là một thực tế phổ biến? Tôi cảm thấy mình nên làm một cái gì đó giống như

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

Nhưng điều đó dường như không hoạt động tốt bởi vì tôi phải tạo một hàm mới để truyền vào phương thức cho mọi loại tỷ lệ.

Đôi khi tôi cảm thấy mình nên làm

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

Nhưng điều đó dường như lấy đi mọi khả năng tìm nạp và định dạng lại. Bất cứ khi nào tôi muốn tìm nạp và định dạng, tôi phải viết hai dòng, một để tìm nạp và một để định dạng.

Tôi còn thiếu gì về lập trình chức năng? Đây có phải là cách đúng đắn để làm điều đó, hay có một mô hình tốt hơn vừa dễ bảo trì và sử dụng?


50
Bệnh ung thư DI đã lan rộng cho đến nay ...
Idan Arye

16
Tôi đấu tranh để xem tại sao cấu trúc này sẽ được sử dụng ở nơi đầu tiên. Chắc chắn sẽ thuận tiện hơn (và rõ ràng ) GetFormattedRate()khi chấp nhận tỷ lệ định dạng dưới dạng tham số, trái ngược với việc nó chấp nhận hàm trả về tỷ lệ để định dạng dưới dạng tham số?
vào

6
Một cách tốt hơn là sử dụng closuresnơi bạn truyền tham số chính nó cho một hàm, đổi lại cung cấp cho bạn một hàm tham chiếu đến tham số cụ thể đó. Hàm "được cấu hình" này sẽ được truyền dưới dạng tham số cho hàm, sử dụng hàm này.
Thomas Junk

7
@IdanArye DI bị ung thư?
Jules

11
@Jules ung thư tiêm phụ thuộc
mèo

Câu trả lời:


39

Nếu bạn làm điều này đủ lâu, cuối cùng bạn sẽ thấy mình viết chức năng này nhiều lần:

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

Xin chúc mừng, bạn đã phát minh ra thành phần chức năng .

Các hàm bao bọc như thế này không được sử dụng nhiều khi chúng chuyên dùng cho một loại. Tuy nhiên, nếu bạn giới thiệu một số biến loại và bỏ qua tham số đầu vào, thì định nghĩa GetFormattedRate của bạn sẽ như sau:

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

Khi nó đứng, những gì bạn đang làm có rất ít mục đích. Nó không chung chung, vì vậy bạn cần sao chép mã đó ở mọi nơi. Nó quá phức tạp mã của bạn bởi vì bây giờ mã của bạn phải tự lắp ráp mọi thứ nó cần từ một nghìn hàm nhỏ. Trái tim của bạn đang ở đúng chỗ: bạn chỉ cần quen với việc sử dụng các loại hàm bậc cao chung này để đặt mọi thứ lại với nhau. Hoặc, sử dụng một lambda thời trang cũ tốt để biến Func<A, B>Athành Func<B>.

Đừng lặp lại chính mình.


16
Đừng lặp lại chính mình nếu tránh lặp lại chính mình làm cho mã tồi tệ hơn. Chẳng hạn như nếu bạn luôn viết hai dòng đó thay vì FormatRate(GetRate(rateKey)).
dùng253751

6
@immibis Tôi đoán ý tưởng là anh ấy sẽ có thể sử dụng GetFormattedRatetrực tiếp từ bây giờ.
Carles

Tôi nghĩ rằng đây là những gì tôi đang cố gắng thực hiện ở đây và tôi đã thử chức năng Soạn này trước đây nhưng dường như tôi hiếm khi sử dụng nó vì chức năng thứ 2 của tôi thường cần nhiều hơn một tham số. Có lẽ tôi cần phải làm điều này kết hợp với việc đóng cửa cho các chức năng được định cấu hình như được đề cập bởi @ thomas-rác
rushinge

@rushinge Kiểu sáng tác này hoạt động trên hàm FP điển hình luôn có một đối số duy nhất (các đối số bổ sung là các hàm thực sự của riêng chúng, hãy nghĩ về nó như thế nào Func<Func<A, B>, C>); điều này có nghĩa là bạn chỉ cần một hàm Soạn hoạt động cho bất kỳ chức năng nào. Tuy nhiên, bạn có thể làm việc với các hàm C # đủ tốt chỉ bằng cách sử dụng các bao đóng - thay vì vượt qua Func<rateKey, rateType>, bạn thực sự cần Func<rateType>, và khi vượt qua func, bạn xây dựng nó như thế nào () => GetRate(rateKey). Vấn đề là bạn không phơi bày các đối số mà hàm mục tiêu không quan tâm.
Luaan

1
@immibis Có, Composechức năng này thực sự chỉ hữu ích nếu bạn cần trì hoãn việc thực thi GetRatevì một số lý do, chẳng hạn như nếu bạn muốn chuyển Compose(FormatRate, GetRate)đến một chức năng cung cấp một tỷ lệ lựa chọn của riêng mình, ví dụ: áp dụng nó cho mọi phần tử trong một danh sách.
jpaugh

107

Hoàn toàn không có lý do để truyền một hàm và các tham số của nó, chỉ sau đó gọi nó với các tham số đó. Trong thực tế, trong trường hợp của bạn, bạn không có lý do gì để vượt qua một chức năng cả . Người gọi cũng có thể chỉ gọi hàm và truyền kết quả.

Hãy suy nghĩ về nó - thay vì sử dụng:

var formattedRate = GetFormattedRate(getRate, rateType);

Tại sao không sử dụng đơn giản:

var formattedRate = GetFormattedRate(getRate(rateType));

?

Ngoài việc giảm mã không cần thiết, nó cũng làm giảm việc ghép nối - nếu bạn muốn thay đổi cách tìm nạp tốc độ (giả sử, nếu getRatebây giờ cần hai đối số), bạn không phải thay đổi GetFormattedRate.

Tương tự như vậy, không có lý do để viết GetFormattedRate(formatRate, getRate, rateKey)thay vì viết formatRate(getRate(rateKey)).

Đừng quá phức tạp.


3
Trong trường hợp này bạn đúng. Nhưng nếu hàm bên trong được gọi nhiều lần, giả sử trong một vòng lặp hoặc hàm ánh xạ, thì khả năng truyền trong các đối số sẽ hữu ích. Hoặc sử dụng thành phần chức năng / currying như đề xuất trong câu trả lời @Jack.
dùng949300

15
@ user949300 có thể, nhưng đó không phải là trường hợp sử dụng của OP (và nếu có, có lẽ đó formatRatenên được ánh xạ qua tỷ lệ nên được định dạng).
jonrsharpe

4
@ user949300 Chỉ khi ngôn ngữ của bạn không hỗ trợ đóng cửa hoặc khi lamdas bị loại bỏ
Bergi

4
Lưu ý rằng việc chuyển một hàm và các tham số của hàm này sang một hàm khác là một cách hoàn toàn hợp lệ để trì hoãn việc đánh giá bằng ngôn ngữ mà không có ngữ nghĩa lười biếng en.wikipedia.org/wiki/Thunk
Jared Smith

4
@JaredSmith Truyền chức năng, vâng, truyền tham số của nó, chỉ khi ngôn ngữ của bạn không hỗ trợ đóng.
dùng253751

15

Nếu bạn thực sự cần phải truyền một hàm vào hàm vì nó truyền một số đối số phụ hoặc gọi nó trong một vòng lặp thì thay vào đó bạn có thể truyền 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 sẽ ràng buộc các đối số mà hàm không biết và che giấu rằng chúng thậm chí còn tồn tại.


-1

Đây không phải là những gì bạn muốn?

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

Và sau đó gọi nó như thế này:

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

        System.PrintLn(formattedRate);
    }
}

Nếu bạn muốn một phương thức có thể hành xử theo nhiều cách khác nhau trong một ngôn ngữ hướng đối tượng như C #, thì cách thông thường để làm điều đó là gọi phương thức đó là một phương thức trừu tượng. Nếu bạn không có lý do cụ thể để làm theo cách khác, bạn nên làm theo cách đó.

Đây có phải là một giải pháp tốt, hoặc có những nhược điểm bạn đang nghĩ đến?


1
Có một vài điều khó hiểu trong câu trả lời của bạn (tại sao trình định dạng cũng nhận được tỷ lệ, nếu đó chỉ là một trình định dạng? Bạn cũng có thể xóa GetFormattedRatephương thức và chỉ cần gọi IRateFormatter.FormatRate(rate)). Tuy nhiên, khái niệm cơ bản là chính xác và tôi nghĩ OP cũng nên thực hiện mã đa hình của nó nếu anh ta cần nhiều phương thức định dạng.
BgrWorker
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.