Phương pháp này có thuần túy không?


9

Tôi có phương pháp mở rộng sau:

    public static IEnumerable<T> Apply<T>(
        [NotNull] this IEnumerable<T> source,
        [NotNull] Action<T> action)
        where T : class
    {
        source.CheckArgumentNull("source");
        action.CheckArgumentNull("action");
        return source.ApplyIterator(action);
    }

    private static IEnumerable<T> ApplyIterator<T>(this IEnumerable<T> source, Action<T> action)
        where T : class
    {
        foreach (var item in source)
        {
            action(item);
            yield return item;
        }
    }

Nó chỉ áp dụng một hành động cho từng mục của chuỗi trước khi trả lại.

Tôi đã tự hỏi liệu tôi có nên áp dụng Purethuộc tính (từ chú thích Resharper) cho phương thức này hay không và tôi có thể thấy các đối số cho và chống lại nó.

Ưu điểm:

  • Nói đúng ra, nó thuần khiết; chỉ gọi nó trên một chuỗi không làm thay đổi trình tự (nó trả về một chuỗi mới) hoặc thực hiện bất kỳ thay đổi trạng thái quan sát được
  • Gọi nó mà không sử dụng kết quả rõ ràng là một sai lầm, vì nó không có tác dụng trừ khi trình tự được liệt kê, vì vậy tôi muốn Resharper cảnh báo tôi nếu tôi làm điều đó.

Nhược điểm:

  • mặc dù Applybản thân phương thức là thuần túy, việc liệt kê chuỗi kết quả sẽ tạo ra những thay đổi trạng thái có thể quan sát được (đó là điểm của phương thức). Chẳng hạn, items.Apply(i => i.Count++)sẽ thay đổi giá trị của các mục mỗi lần liệt kê. Vì vậy, áp dụng thuộc tính Pure có lẽ là sai lệch ...

Bạn nghĩ sao? Có nên áp dụng thuộc tính hay không?


Câu trả lời:


15

Không, nó không nguyên chất, vì nó có tác dụng phụ. Cụ thể là nó đang kêu gọi actiontừng mục. Ngoài ra, nó không phải là chủ đề an toàn.

Thuộc tính chính của các hàm thuần túy là nó có thể được gọi là bất kỳ số lần nào và nó không bao giờ làm bất cứ điều gì khác ngoài trả về cùng một giá trị. Đó không phải là trường hợp của bạn. Ngoài ra, thuần túy có nghĩa là bạn không sử dụng bất cứ thứ gì khác ngoài các tham số đầu vào. Điều này có nghĩa là nó có thể được gọi từ bất kỳ chủ đề nào bất cứ lúc nào và không gây ra bất kỳ hành vi bất ngờ nào. Một lần nữa, đó không phải là trường hợp của chức năng của bạn.

Ngoài ra, bạn có thể bị nhầm lẫn về một điều: độ tinh khiết của chức năng không phải là câu hỏi về ưu và nhược điểm. Ngay cả nghi ngờ duy nhất, rằng nó có thể có tác dụng phụ, là đủ để làm cho nó không tinh khiết.

Eric Lippert nêu lên một điểm tốt. Tôi sẽ sử dụng http://msdn.microsoft.com/en-us/l Library / dd264808 (v = vs.110) .aspx như một phần của đối số phản biện của tôi. Đặc biệt là dòng

Một phương thức thuần túy được phép sửa đổi các đối tượng đã được tạo sau khi nhập vào phương thức thuần túy.

Hãy nói rằng chúng ta tạo phương thức như thế này:

int Count<T>(IEnumerable<T> e)
{
    var enumerator = e.GetEnumerator();
    int count = 0;
    while (enumerator.MoveNext()) count ++;
    return count;
}

Đầu tiên, điều này giả định GetEnumeratorlà hoàn toàn (tôi thực sự không thể tìm thấy bất kỳ nguồn nào về điều đó). Nếu đúng như vậy, theo quy tắc trên, chúng ta có thể chú thích phương thức này với [Pure], bởi vì nó chỉ sửa đổi thể hiện được tạo trong chính cơ thể. Sau đó chúng ta có thể soạn cái này và cái ApplyIterator, cái mà sẽ dẫn đến hàm thuần túy, phải không?

Count(ApplyIterator(source, action));

Không. Thành phần này không thuần khiết, ngay cả khi cả hai CountApplyIteratornguyên chất. Nhưng tôi có thể đang xây dựng lập luận này trên tiền đề sai. Tôi nghĩ rằng ý tưởng rằng các thể hiện được tạo ra trong phương thức được miễn trừ khỏi quy tắc thuần túy là sai hoặc ít nhất là không đủ cụ thể.


1
Độ tinh khiết của hàm +1 không phải là câu hỏi về ưu và nhược điểm. Độ tinh khiết chức năng là một gợi ý về cách sử dụng và an toàn. Thật kỳ lạ, OP đưa vào where T : class, tuy nhiên nếu OP chỉ đơn giản là đặt where T : strutnó thì sẽ thuần túy.
ArTs

4
Tôi không đồng ý với câu trả lời này. Gọi điện sequence.Apply(action)không có tác dụng phụ; nếu có, hãy nêu tác dụng phụ mà nó có. Bây giờ, gọi điện sequence.Apply(action).GetEnumerator().MoveNext()có tác dụng phụ, nhưng chúng tôi đã biết rằng; nó làm biến đổi các điều tra viên! Tại sao nên sequence.Apply(action)được coi là không trong sạch vì gọi MoveNextlà không trong sạch, nhưng sequence.Where(predicate)được coi là tinh khiết? sequence.Where(predicate).GetEnumerator().MoveNext()là mỗi bit như không tinh khiết.
Eric Lippert

@EricLippert Bạn nêu lên một điểm tốt. Nhưng, liệu có đủ để gọi GetEnumerator không? Chúng ta có thể xem xét rằng Pure?
Euphoric

@Euphoric: Tác dụng phụ nào có thể quan sát được khi gọi GetEnumeratorsản xuất, ngoài việc phân bổ một điều tra viên ở trạng thái ban đầu?
Eric Lippert

1
@EricLippert Vậy thì tại sao Enumerable.Count lại được coi là thuần túy bởi các hợp đồng mã của .NET? Tôi không có liên kết, nhưng khi tôi chơi với nó trong phòng thu trực quan, tôi nhận được cảnh báo khi tôi sử dụng số lượng không thuần túy tùy chỉnh, nhưng hợp đồng hoạt động tốt với Enumerable.Count.
Euphoric

18

Tôi không đồng ý với câu trả lời của cả EuphoricRobert Harvey . Hoàn toàn đó là một chức năng thuần túy; Vấn đề là ở đó

Nó chỉ áp dụng một hành động cho từng mục của chuỗi trước khi trả lại.

là rất không rõ ràng "nó" đầu tiên có nghĩa là gì. Nếu "nó" có nghĩa là một trong những chức năng đó, thì điều đó không đúng; không có chức năng nào làm điều đó; trình MoveNextliệt kê của chuỗi thực hiện điều đó và nó "trả về" vật phẩm thông qua thuộc Currenttính, không phải bằng cách trả lại nó.

Các trình tự đó được liệt kê một cách lười biếng , không háo hức nên chắc chắn không phải là trường hợp hành động được áp dụng trước khi trình tự được trả về Apply. Hành động được áp dụng sau khi chuỗi được trả về, nếu MoveNextđược gọi trên một điều tra viên.

Như bạn lưu ý, các hàm này thực hiện một hành động và một chuỗi và trả về một chuỗi; đầu ra phụ thuộc vào đầu vào và không có tác dụng phụ nào được tạo ra, vì vậy đây là các hàm thuần túy ..

Bây giờ, nếu bạn tạo một bộ liệt kê của chuỗi kết quả và sau đó gọi MoveNext trên trình vòng lặp đó thì phương thức MoveNext không thuần túy, bởi vì nó gọi hành động và tạo ra hiệu ứng phụ. Nhưng chúng tôi đã biết rằng MoveNext không thuần túy vì nó làm biến đổi điều tra viên!

Bây giờ, như câu hỏi của bạn, bạn có nên áp dụng thuộc tính: Tôi sẽ không áp dụng thuộc tính này vì tôi sẽ không viết phương thức này ở vị trí đầu tiên . Nếu tôi muốn áp dụng một hành động cho một chuỗi thì tôi viết

foreach(var item in sequence) action(item);

đó là rõ ràng độc đáo.


2
Tôi đoán phương pháp này nằm trong cùng một túi với ForEachphương pháp mở rộng, đây không phải là một phần của Linq vì mục tiêu của nó là tạo ra tác dụng phụ ...
Thomas Levesque

1
@ThomasLevesque: Lời khuyên của tôi là đừng bao giờ làm điều đó . Một truy vấn sẽ trả lời một câu hỏi , không làm thay đổi trình tự ; đó là lý do tại sao chúng được gọi là truy vấn . Đột biến trình tự khi nó được truy vấn là cực kỳ nguy hiểm . Ví dụ, xem xét những gì xảy ra nếu một truy vấn như vậy sau đó phải chịu nhiều cuộc gọi Any()theo thời gian; hành động sẽ được thực hiện lặp đi lặp lại, nhưng chỉ trên mục đầu tiên! Một chuỗi nên là một chuỗi các giá trị ; nếu bạn muốn một chuỗi các hành động thì thực hiện một IEnumerable<Action>.
Eric Lippert

2
Câu trả lời này làm vẩn đục nước nhiều hơn nó chiếu sáng. Mặc dù tất cả mọi thứ bạn nói là không thể nghi ngờ, các nguyên tắc bất biến và thuần khiết là các nguyên tắc ngôn ngữ lập trình cấp cao, không phải là chi tiết triển khai cấp thấp. Các lập trình viên làm việc ở cấp độ chức năng quan tâm đến cách mã của họ hoạt động ở cấp độ chức năng, chứ không phải liệu hoạt động bên trong của nó có thuần túy hay không . Chúng gần như chắc chắn không tinh khiết dưới mui xe nếu bạn đủ thấp. Tất cả chúng ta thường chạy những thứ này trên kiến ​​trúc Von Neumann, điều này chắc chắn không thuần túy.
Robert Harvey

2
@ThomasEding: Phương thức không gọi action, vì vậy độ tinh khiết của actionkhông liên quan. Tôi biết nó trông giống như nó gọi action, nhưng phương thức này là một đường cú pháp cho hai phương thức, một phương thức trả về một điều tra viên và một phương MoveNextthức là điều tra viên. Cái trước rõ ràng là thuần khiết, và cái sau rõ ràng là không. Nhìn vào nó theo cách này: bạn sẽ nói rằng đó IEnumerable ApplyIterator(whatever) { return new MyIterator(whatever); }là tinh khiết? Bởi vì đó là chức năng mà điều này thực sự là.
Eric Lippert

1
@ThomasEding: Bạn đang thiếu một cái gì đó; đó không phải là cách các trình vòng lặp hoạt động. Các ApplyIteratorphương thức trả về ngay lập tức . Không có mã nào trong phần thân ApplyIteratorđược chạy cho đến khi lệnh gọi đầu tiên đến MoveNexttrên bảng liệt kê của đối tượng được trả về. Bây giờ bạn đã biết điều đó, bạn có thể suy ra câu trả lời cho câu đố này: blog.msdn.com/b/ericlippert/archive 2007/09/5/5 Câu trả lời là ở đây: blog.msdn.com/b/ericlippert/archive / 2007/09/06 / Ngày
Eric Lippert

3

Đây không phải là một hàm thuần túy, vì vậy việc áp dụng thuộc tính Pure là sai lệch.

Các hàm thuần túy không sửa đổi bộ sưu tập gốc và việc bạn chuyển một hành động không có tác dụng hay không là không quan trọng; nó vẫn là một chức năng không tinh khiết bởi vì mục đích của nó là gây ra tác dụng phụ.

Nếu bạn muốn làm cho chức năng thuần túy, hãy sao chép bộ sưu tập vào bộ sưu tập mới, áp dụng các thay đổi mà Hành động thực hiện cho bộ sưu tập mới và trả lại bộ sưu tập mới, giữ nguyên bộ sưu tập ban đầu.


Chà, nó không sửa đổi bộ sưu tập gốc, vì nó chỉ trả về một chuỗi mới với cùng các mục; đây là lý do tại sao tôi đã xem xét làm cho nó tinh khiết. Nhưng nó có thể thay đổi trạng thái của các mục khi bạn liệt kê kết quả.
Thomas Levesque

Nếu itemlà một loại tham chiếu, nó sẽ sửa đổi bộ sưu tập gốc, mặc dù bạn đang quay trở lại itemtrong một trình vòng lặp. Xem stackoverflow.com/questions/1538602
Robert Harvey

1
Ngay cả khi anh ta sao chép sâu bộ sưu tập, nó vẫn không thuần túy, vì actioncó thể có tác dụng phụ ngoài việc sửa đổi vật phẩm được chuyển cho nó.
Idan Arye

@IdanArye: Đúng vậy, Hành động cũng sẽ phải trong sạch.
Robert Harvey

1
@IdanArye: ()=>{}có thể chuyển đổi thành Hành động và đó là một chức năng thuần túy. Đầu ra của nó chỉ phụ thuộc vào đầu vào của nó và nó không có tác dụng phụ có thể quan sát được.
Eric Lippert

0

Theo tôi, việc nó nhận được một Hành động (chứ không phải thứ gì đó như PureAction) khiến nó không thuần túy.

Và tôi thậm chí không đồng ý với Eric Lippert. Ông đã viết điều này "() => {} có thể chuyển đổi thành Hành động và đó là một chức năng thuần túy. Đầu ra của nó chỉ phụ thuộc vào đầu vào của nó và nó không có tác dụng phụ có thể quan sát được".

Chà, hãy tưởng tượng rằng thay vì sử dụng một đại biểu, ApplyIterator đang gọi một phương thức có tên là Action.

Nếu Action là thuần túy, thì ApplyIterator cũng thuần túy. Nếu Hành động không thuần túy, thì ApplyIterator không thể thuần túy.

Xem xét loại đại biểu (không phải giá trị thực tế đã cho), chúng tôi không đảm bảo rằng nó sẽ thuần túy, vì vậy phương thức sẽ chỉ hoạt động như một phương thức thuần túy khi đại biểu thuần túy. Vì vậy, để làm cho nó thực sự thuần túy, nó cần nhận một đại biểu thuần túy (và tồn tại, chúng ta có thể khai báo một đại biểu là [Pure], vì vậy chúng ta có thể có PureAction).

Giải thích nó một cách khác nhau, một phương thức Pure sẽ luôn cho cùng một kết quả với cùng các đầu vào và không tạo ra các thay đổi có thể quan sát được. ApplyIterator có thể được cung cấp cùng một nguồn và ủy nhiệm hai lần, nhưng nếu đại biểu đang thay đổi loại tham chiếu, lần thực hiện tiếp theo sẽ cho kết quả khác nhau. Ví dụ: Đại biểu thực hiện một số thứ như item.Content + = "Đã thay đổi";

Vì vậy, bằng cách sử dụng ApplyIterator trong danh sách "các chuỗi chứa" (một đối tượng có thuộc tính Content của chuỗi loại), chúng ta có thể có các giá trị ban đầu sau:

Test

Test2

Sau lần thực hiện đầu tiên, danh sách sẽ có:

Test Changed

Test2 Changed

Và đây là lần thứ 3:

Test Changed Changed

Test2 Changed Changed

Vì vậy, chúng tôi đang thay đổi nội dung của danh sách vì đại biểu không thuần túy và không thể thực hiện tối ưu hóa để tránh thực hiện cuộc gọi 3 lần nếu được gọi 3 lần, vì mỗi lần thực hiện sẽ tạo ra một kết quả khác nhau.

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.