วิธีนี้บริสุทธิ์หรือไม่


9

ฉันมีวิธีการต่อไปนี้:

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

มันใช้การกระทำกับแต่ละรายการในลำดับก่อนที่จะส่งคืน

ฉันสงสัยว่าฉันควรใช้แอPureททริบิวต์ (จากคำอธิบายประกอบ Resharper)กับวิธีนี้หรือไม่และฉันสามารถเห็นการขัดแย้งและต่อต้านได้

ข้อดี:

  • พูดอย่างเคร่งครัดก็คือบริสุทธิ์ เพียงแค่เรียกมันว่าลำดับไม่ได้เปลี่ยนลำดับ (มันกลับลำดับใหม่) หรือทำการเปลี่ยนแปลงสถานะที่สังเกตได้
  • การเรียกใช้โดยไม่ใช้ผลลัพธ์นั้นเป็นความผิดพลาดอย่างชัดเจนเนื่องจากไม่มีผลยกเว้นลำดับที่ระบุดังนั้นฉันต้องการให้ Resharper เตือนฉันหากฉันทำเช่นนั้น

จุดด้อย:

  • แม้ว่าApplyวิธีการนั้นจะบริสุทธิ์ แต่การระบุลำดับผลลัพธ์จะทำให้เกิดการเปลี่ยนแปลงสถานะที่สังเกตได้ (ซึ่งเป็นจุดของวิธีการ) ตัวอย่างเช่นitems.Apply(i => i.Count++)จะเปลี่ยนค่าของรายการทุกครั้งที่ระบุ ดังนั้นการใช้แอตทริบิวต์ Pure อาจทำให้เข้าใจผิด ...

คุณคิดอย่างไร? ฉันควรใช้คุณสมบัตินี้หรือไม่?


ที่เกี่ยวข้อง: stackoverflow.com/questions/23997554/...
Den

คำตอบ:


15

ไม่บริสุทธิ์เพราะมีผลข้างเคียง ในขณะเดียวกันก็กำลังโทรหาactionแต่ละรายการ นอกจากนี้มันไม่ได้เป็น threadsafe

คุณสมบัติที่สำคัญของฟังก์ชั่นบริสุทธิ์คือมันสามารถเรียกได้ว่าจำนวนครั้งใด ๆ และมันไม่เคยทำอะไรอย่างอื่นนอกจากกลับค่าเดียวกัน ซึ่งไม่ใช่กรณีของคุณ นอกจากนี้การเป็นผู้บริสุทธิ์หมายถึงคุณไม่ได้ใช้อย่างอื่นนอกจากพารามิเตอร์อินพุต ซึ่งหมายความว่าสามารถเรียกใช้ได้จากเธรดใด ๆ ได้ตลอดเวลาและไม่ทำให้เกิดพฤติกรรมที่ไม่คาดคิด อีกครั้งนั่นไม่ใช่กรณีของฟังก์ชั่นของคุณ

นอกจากนี้คุณอาจถูกเข้าใจผิดในสิ่งหนึ่ง: ฟังก์ชั่นความบริสุทธิ์ไม่ใช่คำถามของข้อดีหรือข้อเสีย แม้แต่ข้อสงสัยเพียงอย่างเดียวว่ามันสามารถมีผลข้างเคียงก็เพียงพอที่จะทำให้มันไม่บริสุทธิ์

Eric Lippertยกประเด็นที่ดี ฉันจะใช้http://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspxเป็นส่วนหนึ่งของข้อโต้แย้งของฉัน โดยเฉพาะสาย

วิธีบริสุทธิ์ได้รับอนุญาตให้แก้ไขวัตถุที่สร้างขึ้นหลังจากการเข้าสู่วิธีการบริสุทธิ์

ให้เราสร้างวิธีการเช่นนี้:

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

อันดับแรกถือว่านี่GetEnumeratorเป็นสิ่งที่บริสุทธิ์เช่นกัน (ฉันไม่สามารถหาแหล่งที่มาได้ในนั้น) ถ้าเป็นเช่นนั้นตามกฎข้างต้นเราสามารถเพิ่มความคิดเห็นด้วยวิธีนี้ด้วย [บริสุทธิ์] เพราะมันจะแก้ไขอินสแตนซ์ที่สร้างขึ้นภายในร่างกายเท่านั้น หลังจากนั้นเราสามารถเขียนสิ่งนี้และสิ่งApplyIteratorที่ควรส่งผลให้ฟังก์ชั่นบริสุทธิ์ใช่ไหม?

Count(ApplyIterator(source, action));

ไม่องค์ประกอบนี้ไม่บริสุทธิ์แม้ว่าทั้งสองCountและApplyIteratorบริสุทธิ์ แต่ฉันอาจสร้างข้อโต้แย้งนี้ในหลักฐานที่ผิด ฉันคิดว่าความคิดที่ว่ากรณีที่สร้างขึ้นภายในวิธีการได้รับการยกเว้นจากกฎความบริสุทธิ์นั้นผิดหรืออย่างน้อยก็ไม่เจาะจงพอ


1
ฟังก์ชั่นความบริสุทธิ์ +1 ไม่ใช่คำถามของข้อดีหรือข้อเสีย ฟังก์ชั่นความบริสุทธิ์เป็นคำใบ้ในการใช้งานและความปลอดภัย น่าแปลกที่ OP ใส่เข้าไปwhere T : classแต่ถ้า OP แสดงwhere T : strutว่ามันจะบริสุทธิ์
ArTs

4
ฉันไม่เห็นด้วยกับคำตอบนี้ การโทรsequence.Apply(action)ไม่มีผลข้างเคียง หากเป็นเช่นนั้นให้ระบุผลข้างเคียงที่มี ตอนนี้การโทรsequence.Apply(action).GetEnumerator().MoveNext()มีผลข้างเคียง แต่เรารู้แล้วว่า มันจะกลายเป็นตัวแจงนับ! เหตุใดจึงควรsequence.Apply(action)พิจารณาว่าไม่บริสุทธิ์เนื่องจากการโทรMoveNextนั้นไม่บริสุทธิ์ แต่sequence.Where(predicate)ถือว่าบริสุทธิ์ sequence.Where(predicate).GetEnumerator().MoveNext()เป็นเพียงเล็กน้อยไม่บริสุทธิ์
Eric Lippert

@EricLippert คุณเพิ่มจุดดี แต่มันจะไม่เพียงพอที่จะโทรหา GetEnumerator ใช่ไหม เราสามารถพิจารณาความบริสุทธิ์นั้นได้หรือไม่?
สุข

@Euphoric: ผลข้างเคียงที่สังเกตได้อะไรที่เรียกว่าGetEnumeratorผลิตนอกเหนือจากการจัดสรรตัวแจงนับในสถานะเริ่มต้น
Eric Lippert

1
@EricLippert แล้วทำไม Enumerable จึงนับว่าเป็น Pure โดยสัญญา Code ของ. NET ฉันไม่มีลิงค์ แต่เมื่อฉันเล่นกับมันใน visual studio ฉันได้รับคำเตือนเมื่อฉันใช้การนับแบบไม่บริสุทธิ์ แต่สัญญาทำงานได้ดีกับ Enumerable
ร่าเริง

18

ฉันไม่เห็นด้วยกับคำตอบของทั้งEuphoricและRobert Harvey แน่นอนว่าเป็นฟังก์ชั่นที่บริสุทธิ์ ปัญหาคือว่า

มันใช้การกระทำกับแต่ละรายการในลำดับก่อนที่จะส่งคืน

ไม่ชัดเจนมากว่า "มัน" ครั้งแรกหมายถึงอะไร หาก "มัน" หมายถึงหนึ่งในฟังก์ชั่นเหล่านั้นแสดงว่าไม่ถูกต้อง ทั้งฟังก์ชั่นเหล่านั้นไม่ทำอย่างนั้น; ตัวMoveNextแจงนับของลำดับนั้นและ "ส่งคืน" ไอเท็มผ่านCurrentคุณสมบัติไม่ใช่โดยส่งคืน

ลำดับเหล่านั้นจะถูกระบุอย่างเฉื่อยชาไม่กระหายจึงเป็นสิ่งที่ไม่แน่นอนกรณีว่าเป็นการกระทำที่ถูกนำไปใช้ก่อนที่จะApplyลำดับถูกส่งกลับโดย การดำเนินการจะถูกนำไปใช้หลังจากส่งคืนลำดับหากMoveNextถูกเรียกบนตัวแจงนับ

ตามที่คุณทราบฟังก์ชั่นเหล่านี้จะดำเนินการและลำดับและส่งกลับลำดับ; เอาท์พุทขึ้นอยู่กับอินพุตและไม่มีผลข้างเคียงใด ๆ ดังนั้นสิ่งเหล่านี้จึงเป็นฟังก์ชั่นที่บริสุทธิ์ ..

ทีนี้ถ้าคุณสร้างตัวแจงนับของลำดับผลลัพธ์จากนั้นเรียกใช้ MoveNext บนตัววนซ้ำนั้นเมธอด MoveNext นั้นไม่บริสุทธิ์เพราะมันเรียกการกระทำและสร้างผลข้างเคียง แต่เรารู้อยู่แล้วว่า MoveNext ไม่บริสุทธิ์เพราะมันทำให้กลายเป็นตัวแจงนับ!

ขณะนี้เป็นสำหรับคำถามของคุณคุณควรใช้แอตทริบิวต์: ฉันจะไม่ใช้แอตทริบิวต์เพราะฉันจะไม่เขียนวิธีการนี้ในสถานที่แรก ถ้าฉันต้องการนำการกระทำไปใช้กับลำดับฉันจะเขียน

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

ซึ่งมีความชัดเจนมาก


2
ฉันเดาว่าวิธีนี้อยู่ในถุงเดียวกับForEachวิธีการขยายซึ่งไม่ได้เป็นส่วนหนึ่งของ Linq โดยเจตนาเพราะเป้าหมายคือการสร้างผลข้างเคียง ...
Thomas Levesque

1
@ThomasLevesque: คำแนะนำของฉันคือการไม่เคยทำอย่างนั้น แบบสอบถามควรตอบคำถามไม่กลายพันธุ์ลำดับ ; นั่นเป็นเหตุผลที่พวกเขากำลังเรียกว่าคำสั่ง กรรมวิธีตามลำดับในขณะที่มันถูกสอบถามเป็นอันตรายเป็นพิเศษ ลองพิจารณาตัวอย่างว่าจะเกิดอะไรขึ้นหากมีการสอบถามเช่นนั้นอาจมีการโทรติดต่อหลายAny()ครั้งเมื่อเวลาผ่านไป การกระทำจะถูกดำเนินการซ้ำแล้วซ้ำอีก แต่เฉพาะในรายการแรก! ลำดับควรเป็นลำดับของค่า ; ถ้าคุณต้องการลำดับของการกระทำIEnumerable<Action>แล้วทำให้
Eric Lippert

2
คำตอบนี้ทำให้น้ำขุ่นมากกว่าที่จะส่องสว่าง ในขณะที่ทุกสิ่งที่คุณพูดนั้นเป็นความจริงอย่างไม่อาจปฏิเสธได้หลักการของความไม่สามารถเปลี่ยนแปลงได้และความบริสุทธิ์นั้นเป็นหลักการทางภาษาการเขียนโปรแกรมระดับสูง โปรแกรมเมอร์ที่ทำงานในระดับหน้าที่มีความสนใจในการทำงานของรหัสในระดับหน้าที่ไม่ว่างานภายในนั้นบริสุทธิ์หรือไม่ พวกมันเกือบจะไม่บริสุทธิ์ภายใต้ประทุนถ้าคุณไปต่ำพอ เราทุกคนใช้สิ่งเหล่านี้กับสถาปัตยกรรมของ Von Neumann ซึ่งแน่นอนที่สุดว่าไม่บริสุทธิ์
Robert Harvey

2
@ThomasEding: วิธีการไม่เรียกactionดังนั้นความบริสุทธิ์ของactionไม่เกี่ยวข้อง ฉันรู้ว่ามันดูเหมือนว่าจะเรียกactionแต่วิธีนี้เป็นน้ำตาล syntactic สำหรับสองวิธีหนึ่งที่ส่งกลับค่าตัวแจงนับและหนึ่งซึ่งเป็นMoveNextของตัวแจงนับ อดีตนั้นชัดเจนและไม่ชัดเจน ลองดูวิธีนี้: คุณจะบอกว่าIEnumerable ApplyIterator(whatever) { return new MyIterator(whatever); }บริสุทธิ์หรือไม่ เพราะนั่นคือฟังก์ชั่นที่เป็นแบบนี้จริงๆ
Eric Lippert

1
@ThomasEding: คุณขาดอะไรไป นั่นไม่ใช่วิธีการวนซ้ำ ApplyIteratorวิธีการส่งกลับทันที ไม่มีรหัสในเนื้อความของApplyIteratorถูกเรียกใช้จนกว่าการเรียกครั้งแรกไปยังMoveNextตัวแจงนับของวัตถุที่ส่งคืน ตอนนี้คุณรู้แล้วคุณสามารถอนุมานคำตอบสำหรับปริศนานี้: blogs.msdn.com/b/ericlippert/archive/2007/09/05/… คำตอบอยู่ที่นี่: blogs.msdn.com/b/ericlippert/archive / 2007/09/06 / …
Eric Lippert

3

มันไม่ใช่ฟังก์ชั่นที่แท้จริงดังนั้นการใช้แอตทริบิวต์ Pure นั้นทำให้เข้าใจผิด

ฟังก์ชั่นแท้ๆไม่ได้ปรับเปลี่ยนคอลเลกชันดั้งเดิมและมันไม่สำคัญว่าคุณจะผ่านการกระทำที่ไม่มีผลหรือไม่ มันยังคงเป็นฟังก์ชั่นที่ไม่บริสุทธิ์เพราะเจตนาที่จะทำให้เกิดผลข้างเคียง

หากคุณต้องการทำให้ฟังก์ชั่นนั้นบริสุทธิ์ให้คัดลอกคอลเลกชันไปยังคอลเลกชันใหม่ใช้การเปลี่ยนแปลงที่แอคชั่นดำเนินการกับคอลเลกชันใหม่และส่งคืนคอลเลกชันใหม่โดยไม่เปลี่ยนคอลเล็กชันเดิม


มันไม่ได้ปรับเปลี่ยนคอลเลกชันดั้งเดิมเพราะมันแค่ส่งคืนลำดับใหม่ด้วยรายการเดียวกัน นี่คือเหตุผลที่ฉันกำลังพิจารณาทำให้บริสุทธิ์ แต่มันอาจเปลี่ยนสถานะของรายการเมื่อคุณระบุผลลัพธ์
Thomas Levesque

หากitemเป็นประเภทการอ้างอิงก็เป็นการปรับเปลี่ยนคอลเลกชันดั้งเดิมแม้ว่าคุณจะกลับมาitemในตัววนซ้ำ ดูstackoverflow.com/questions/1538301
Robert Harvey

1
แม้ว่าเขาจะคัดลอกคอลเลกชันลึกมันก็ยังคงไม่บริสุทธิ์เพราะactionอาจมีผลข้างเคียงอื่นนอกเหนือจากการแก้ไขรายการที่ส่งผ่านไป
Idan Arye

@IdanArye: จริงการกระทำก็ต้องบริสุทธิ์
Robert Harvey

1
@IdanArye: ()=>{}สามารถแปลงเป็น Action ได้และเป็นฟังก์ชันที่บริสุทธิ์ มันขึ้นอยู่กับปัจจัยการผลิตและมันไม่มีผลข้างเคียงที่สังเกตได้
Eric Lippert

0

ในความคิดของฉันความจริงที่ว่ามันได้รับการกระทำ (และไม่ใช่สิ่งที่เหมือน PureAction) ทำให้มันไม่บริสุทธิ์

และฉันก็ไม่เห็นด้วยกับ Eric Lippert เขาเขียนสิ่งนี้ "() => {} สามารถแปลงเป็น Action ได้และมันเป็นฟังก์ชั่นที่บริสุทธิ์เอาท์พุทขึ้นอยู่กับปัจจัยการผลิตเท่านั้น

ลองนึกภาพว่าแทนที่จะใช้ผู้รับมอบสิทธิ์ ApplyIterator ได้เรียกใช้วิธีการที่ชื่อว่า Action

ถ้าการกระทำนั้นบริสุทธิ์แล้ว ApplyIterator ก็เช่นกัน หากการกระทำไม่บริสุทธิ์จากนั้น ApplyIterator จะไม่บริสุทธิ์

เมื่อพิจารณาประเภทผู้รับมอบสิทธิ์ (ไม่ใช่มูลค่าที่กำหนดจริง) เราไม่มีการรับประกันว่าจะบริสุทธิ์ดังนั้นวิธีการจะทำหน้าที่เป็นวิธีการบริสุทธิ์เฉพาะเมื่อผู้รับมอบสิทธิ์บริสุทธิ์ ดังนั้นเพื่อทำให้บริสุทธิ์จริง ๆ ควรได้รับตัวแทนที่บริสุทธิ์ (และมีอยู่จริงเราสามารถประกาศผู้แทนเป็น [บริสุทธิ์] เพื่อให้เรามี PureAction ได้)

การอธิบายอย่างแตกต่างกันวิธีการบริสุทธิ์ควรให้ผลลัพธ์เดียวกันเสมอเมื่อได้รับอินพุตเดียวกันและไม่ควรสร้างการเปลี่ยนแปลงที่สังเกตได้ ApplyIterator อาจได้รับแหล่งเดียวกันและผู้รับมอบสิทธิ์สองครั้ง แต่หากผู้รับมอบสิทธิ์กำลังเปลี่ยนประเภทการอ้างอิงการดำเนินการต่อไปจะให้ผลลัพธ์ที่แตกต่างกัน ตัวอย่าง: ผู้รับมอบสิทธิ์ทำบางอย่างเช่น item.Content + = "Changed";

ดังนั้นการใช้ ApplyIterator เหนือรายการของ "string container" (วัตถุที่มีคุณสมบัติเนื้อหาของสตริงประเภท) เราอาจมีค่าเดิมเหล่านี้:

Test

Test2

หลังจากการดำเนินการครั้งแรกรายการจะมีสิ่งนี้:

Test Changed

Test2 Changed

และนี่เป็นครั้งที่ 3:

Test Changed Changed

Test2 Changed Changed

ดังนั้นเรากำลังเปลี่ยนแปลงเนื้อหาของรายการเนื่องจากผู้รับมอบสิทธิ์ไม่บริสุทธิ์และไม่มีการปรับให้เหมาะสมเพื่อหลีกเลี่ยงการดำเนินการเรียก 3 ครั้งหากเรียกใช้ 3 ครั้งเนื่องจากการดำเนินการแต่ละครั้งจะสร้างผลลัพธ์ที่แตกต่างกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.