เมื่อการเขียนโปรแกรมในลักษณะการทำงานคุณมีสถานะแอปพลิเคชันเดียวที่คุณสานผ่านตรรกะของแอปพลิเคชันหรือไม่


12

ฉันจะสร้างระบบที่มีสิ่งต่อไปนี้ทั้งหมดได้อย่างไร :

  1. การใช้ฟังก์ชั่นบริสุทธิ์กับวัตถุที่ไม่เปลี่ยนรูป
  2. ส่งผ่านไปยังข้อมูลฟังก์ชันที่ฟังก์ชันต้องการเท่านั้นไม่เกิน (เช่นไม่มีวัตถุสถานะแอปพลิเคชันขนาดใหญ่)
  3. หลีกเลี่ยงการขัดแย้งกับฟังก์ชันมากเกินไป
  4. หลีกเลี่ยงการสร้างวัตถุใหม่เพียงเพื่อจุดประสงค์ในการบรรจุและเอาพารามิเตอร์ออกจากฟังก์ชั่นเพื่อหลีกเลี่ยงการส่งพารามิเตอร์ไปยังฟังก์ชันมากเกินไป ถ้าฉันจะแพ็คหลายรายการไปยังฟังก์ชันเป็นวัตถุเดียวฉันต้องการให้วัตถุนั้นเป็นเจ้าของข้อมูลนั้นไม่ใช่สิ่งที่สร้างขึ้นชั่วคราว

สำหรับฉันแล้วดูเหมือนว่ารัฐ monad ทำผิดกฎข้อที่ 2 ถึงแม้ว่ามันจะไม่ชัดเจนเพราะมันถูกสานต่อผ่าน monad

ฉันรู้สึกว่าฉันจำเป็นต้องใช้เลนส์อย่างใด แต่มีน้อยมากที่เขียนเกี่ยวกับมันสำหรับภาษาที่ไม่สามารถใช้งานได้

พื้นหลัง

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

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

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

จากนั้นฉันก็ผ่านแอปพลิเคชันของฉันและ: 1. ขั้นแรกให้สถานะแอปพลิเคชันทั้งหมดของฉันเป็นวัตถุระดับโลกเดียว (GameState) 2. ประการที่สองฉันทำให้ GameState ไม่เปลี่ยนรูป คุณไม่สามารถเปลี่ยนได้ หากคุณต้องการการเปลี่ยนแปลงคุณต้องสร้างสิ่งใหม่ ฉันทำสิ่งนี้โดยการเพิ่มตัวคัดลอกตัวเลือกที่จะเลือกหนึ่งหรือมากกว่าหนึ่งช่องที่มีการเปลี่ยนแปลง 3. สำหรับแต่ละแอปพลิเคชันฉันส่งผ่าน GameState เป็นพารามิเตอร์ ภายในฟังก์ชั่นหลังจากที่ทำสิ่งที่มันจะทำมันจะสร้าง GameState ใหม่และส่งคืน

ฉันมีคอร์ที่ใช้งานได้จริงได้อย่างไรและมีลูปด้านนอกที่ดึง GameState นั้นเข้าสู่เวิร์กโฟลว์หลักของแอปพลิเคชัน

คำถามของฉัน:

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

มีบางอย่างเกี่ยวกับที่ดูเหมือนผิด ฟังก์ชั่นไม่ต้องการ GameState ทั้งหมด มันแค่ต้องการวัตถุคะแนน ดังนั้นฉันจึงอัปเดตเพื่อให้ผ่านคะแนนและคืนคะแนนเท่านั้น

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

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

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

ดูเหมือนว่าจะผ่าน GameState ไปตามทางเพื่อหลีกเลี่ยงปัญหานี้ แต่ฉันกลับไปที่ปัญหาดั้งเดิมของการส่งผ่านข้อมูลเพิ่มเติมไปยังฟังก์ชันมากกว่าความต้องการของฟังก์ชัน


1
ฉันไม่มีความเชี่ยวชาญในการออกแบบและฟังก์ชั่นพิเศษ แต่เนื่องจากเกมของคุณมีสภาพที่พัฒนาขึ้นคุณแน่ใจหรือไม่ว่าการเขียนโปรแกรมการทำงานเป็นกระบวนทัศน์ที่เหมาะสมกับทุกเลเยอร์ของแอปพลิเคชันของคุณ?
Walfrat

Walfrat ฉันคิดว่าถ้าคุณพูดคุยกับผู้เชี่ยวชาญด้านการเขียนโปรแกรมที่ใช้งานได้คุณอาจพบว่าพวกเขาจะบอกว่ากระบวนทัศน์การเขียนโปรแกรมเชิงปฏิบัติการนั้นมีวิธีแก้ปัญหาสำหรับการจัดการสถานะการพัฒนา
Daisha Lynn

คำถามของคุณดูกว้างขึ้นสำหรับฉันที่ระบุเท่านั้น หากเป็นเรื่องเกี่ยวกับการจัดการสถานะที่นี่เป็นการเริ่มต้นเท่านั้น: ดูคำตอบและลิงค์ภายในstackoverflow.com/questions/1020653/…
Walfrat

2
@DaishaLynn ฉันไม่คิดว่าคุณควรลบคำถาม ถูกอัปโหลดแล้วและไม่มีใครพยายามปิดดังนั้นฉันจึงไม่คิดว่ามันจะเกินขอบเขตสำหรับไซต์นี้ การขาดคำตอบจนถึงอาจเป็นเพราะต้องการความเชี่ยวชาญเฉพาะด้าน แต่นั่นไม่ได้หมายความว่ามันจะไม่ถูกค้นพบและตอบในที่สุด
Ben Aaronson

2
การจัดการสถานะที่ไม่แน่นอนในโปรแกรมการทำงานที่ซับซ้อนที่ไม่มีความช่วยเหลือด้านภาษาอย่างมากเป็นความเจ็บปวดอย่างมาก ใน Haskell สามารถจัดการได้เพราะ monads, terse syntax, การอนุมานประเภทที่ดีมาก แต่ก็ยังน่ารำคาญมาก ใน C # ฉันคิดว่าคุณมีปัญหามากขึ้น
Reinstate Monica

คำตอบ:


2

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

  • แบ่งGameStateลำดับขั้นเพื่อให้คุณได้ชิ้นส่วนเล็กลง 3-5 ชิ้นแทน 15
  • ปล่อยให้มันใช้อินเทอร์เฟซดังนั้นวิธีการของคุณเห็นเฉพาะส่วนที่จำเป็น อย่าโยนพวกเขากลับไปเพราะคุณโกหกตัวเองเกี่ยวกับประเภทที่แท้จริง
  • ให้ส่วนต่าง ๆ ใช้อินเทอร์เฟซเพื่อให้คุณควบคุมสิ่งที่คุณผ่านได้ดี
  • ใช้วัตถุพารามิเตอร์ แต่ทำเท่าที่จำเป็นและพยายามที่จะทำให้พวกเขาเป็นวัตถุจริงที่มีพฤติกรรมของตนเอง
  • บางครั้งการส่งเกินความจำเป็นเล็กน้อยอาจดีกว่ารายการพารามิเตอร์ที่มีความยาว

ดังนั้นตอนนี้ฉันสงสัยว่าปัญหาที่ฉันมีคือการทำงานของฉันซ้อนกันลึกเกินไป

ฉันไม่คิดอย่างนั้น การปรับโครงสร้างเป็นฟังก์ชั่นเล็ก ๆ นั้นถูกต้อง แต่บางทีคุณอาจจัดกลุ่มใหม่ให้ดีขึ้นได้ บางครั้งมันเป็นไปไม่ได้บางครั้งก็แค่ต้องมองปัญหาที่สอง (หรือสาม)

เปรียบเทียบการออกแบบของคุณกับสิ่งที่ไม่แน่นอน มีสิ่งใดที่แย่ไปกว่าการเขียนใหม่? ถ้าเป็นเช่นนั้นคุณไม่สามารถทำให้ดีขึ้นในแบบเดียวกับที่คุณเคยทำหรือไม่?


มีคนบอกฉันให้เปลี่ยนการออกแบบของฉันเพื่อที่ว่าฟังก์ชั่นจะใช้พารามิเตอร์เดียวเท่านั้นเพื่อให้ฉันสามารถใช้การแกง ฉันลองฟังก์ชั่นเดียวฟังก์ชั่นดังนั้นแทนที่จะเรียกใช้ DeleteEntity (a, b, c) ตอนนี้ฉันเรียก DeleteEntity (a) (b) (c) น่ารักและมันควรจะทำให้มันแต่งได้มากกว่า แต่ฉันก็ยังไม่เข้าใจ
Daisha Lynn

@DaishaLynn ฉันใช้ Java และไม่มีน้ำตาล syntactic หวานสำหรับการแกงดังนั้น (สำหรับฉัน) มันไม่คุ้มค่าที่จะลอง ฉันค่อนข้างสงสัยเกี่ยวกับการใช้งานฟังก์ชั่นการสั่งซื้อที่สูงขึ้นในกรณีของเรา แต่แจ้งให้เราทราบว่ามันเหมาะกับคุณหรือไม่
maaartinus

2

ฉันไม่สามารถพูดกับ C # ได้ แต่ใน Haskell คุณจะต้องผ่านรัฐทั้งหมดรอบ ๆ คุณสามารถทำสิ่งนี้อย่างชัดเจนหรือกับรัฐ monad สิ่งหนึ่งที่คุณสามารถทำได้เพื่อแก้ไขปัญหาของฟังก์ชั่นที่ได้รับข้อมูลเพิ่มเติมจากนั้นพวกเขาต้องการคือใช้มีประเภทของพิมพ์ (ถ้าคุณไม่คุ้นเคย Haskell typeclasses จะคล้ายกับอินเตอร์เฟส C #) สำหรับแต่ละองค์ประกอบ E ของรัฐคุณสามารถกำหนด typeclass HasE ที่ต้องการฟังก์ชัน getE ที่ส่งคืนค่าของ E. Monad ของรัฐสามารถ ทำตัวอย่างของ typeclasses เหล่านี้ทั้งหมด จากนั้นในการทำงานจริงของคุณแทนที่จะต้องใช้ State Monad ของคุณอย่างชัดเจนคุณต้องใช้ Monad ใด ๆ ที่เป็นของ typeclasses สำหรับองค์ประกอบที่คุณต้องการ ที่ จำกัด สิ่งที่ฟังก์ชั่นสามารถทำได้กับโมนาที่ใช้อยู่ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการนี้ให้ดู Michael Snoyman'sโพสต์ในรูปแบบการออกแบบ ReaderT

คุณอาจทำซ้ำสิ่งนี้ใน C # ขึ้นอยู่กับว่าคุณกำหนดสถานะที่ถูกส่งผ่านไปได้อย่างไร หากคุณมีสิ่งที่ชอบ

public class MyState
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
}

คุณสามารถกำหนดอินเตอร์เฟสIHasMyIntและIHasMyStringด้วยวิธีการGetMyIntและGetMyStringตามลำดับ ระดับรัฐนั้นดูเหมือนว่า:

public class MyState : IHasMyInt, IHasMyString
{
    public int MyInt {get; set; }
    public string MyString {get; set; }
    public double MyDouble {get; set; }

    public int GetMyInt () 
    {
        return MyInt;
    }

    public string GetMyString ()
    {
        return MyString;
    }

    public double GetMyDouble ()
    {
        return MyDouble;
    }
}

วิธีการของคุณอาจต้องใช้ IHasMyInt, IHasMyString หรือ MyState ทั้งหมดตามความเหมาะสม

จากนั้นคุณสามารถใช้ข้อ จำกัด where บนคำจำกัดความของฟังก์ชันเพื่อให้คุณสามารถส่งผ่านวัตถุสถานะ แต่สามารถไปที่ string และ int เท่านั้นไม่ใช่ double

public static T DoSomething<T>(T state) where T : IHasMyString, IHasMyInt
{
    var s = state.GetMyString();
    var i = state.GetMyInt();
    return state;
}

นั่นดูน่าสนใจ. ดังนั้นในปัจจุบันที่ฉันผ่านการเรียกฟังก์ชั่นและส่งผ่าน 10 พารามิเตอร์ตามค่าฉันผ่านใน "gameSt'ate" 10 ครั้ง แต่ถึง 10 ประเภทพารามิเตอร์ที่แตกต่างกันเช่น "IHasGameScore", "IHasGameBoard" ฯลฯ ฉันหวังว่าจะมี เป็นวิธีการส่งผ่านพารามิเตอร์เดียวที่ฟังก์ชั่นสามารถระบุได้ว่าจะต้องใช้อินเทอร์เฟซทั้งหมดในประเภทนั้น ฉันสงสัยว่าฉันสามารถทำได้ด้วย "ข้อ จำกัด ทั่วไป" .. ให้ฉันลองสิ่งนั้น
Daisha Lynn

1
มันได้ผล นี่มันเป็นการทำงาน: dotnetfiddle.net/cfmDbs
Daisha Lynn

1

ฉันคิดว่าคุณน่าจะเรียนรู้เกี่ยวกับ Redux หรือ Elm ได้ดีและพวกเขาจัดการกับคำถามนี้อย่างไร

โดยพื้นฐานแล้วคุณมีฟังก์ชั่นบริสุทธิ์ที่ใช้ทั้งสถานะและการกระทำที่ผู้ใช้ดำเนินการและส่งกลับสถานะใหม่

ฟังก์ชั่นนั้นจะเรียกฟังก์ชันบริสุทธิ์อื่น ๆ ซึ่งแต่ละฟังก์ชันจัดการกับสถานะเฉพาะ ฟังก์ชั่นเหล่านี้อาจไม่ทำอะไรเลย แต่กลับสู่สถานะเดิมโดยไม่ขึ้นอยู่กับการกระทำ

หากต้องการเรียนรู้เพิ่มเติม Google Elm Architecture หรือ Redux.js.org


ฉันไม่รู้ Elm แต่ฉันเชื่อว่ามันคล้ายกับ Redux ใน Redux Reduction ทั้งหมดไม่ถูกเรียกใช้สำหรับการเปลี่ยนสถานะทุกครั้ง ฟังดูไร้ประสิทธิภาพมาก
Daisha Lynn

เมื่อพูดถึงการเพิ่มประสิทธิภาพในระดับต่ำอย่าคิดว่าเป็นการวัด ในทางปฏิบัติมันเร็วพอ
Daniel T.

ขอบคุณ Daniel แต่มันใช้ไม่ได้สำหรับฉัน ฉันได้ทำการพัฒนาพอที่จะรู้ว่ามันไม่ได้แจ้งให้ทุกองค์ประกอบใน UI ทุกครั้งที่มีการเปลี่ยนแปลงข้อมูลใด ๆ
Daisha Lynn

-2

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

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

C # ยังไม่พร้อมที่จะใช้งานได้ตามที่คุณต้องการ


3
ไม่ตื่นเต้นกับคำตอบนี้หรือเสียง ฉันไม่ได้ละเมิดอะไร ฉันกำลัง จำกัด ขอบเขตของ C # เพื่อใช้เป็นภาษาที่ใช้งานได้มากกว่า มันไม่ใช่เรื่องแปลกที่จะทำ คุณดูเหมือนจะต่อต้านปรัชญาซึ่งเป็นเรื่องปกติ แต่ในกรณีนี้อย่ามองคำถามนี้ ความคิดเห็นของคุณไม่มีประโยชน์กับใครเลย เดินหน้า.
Daisha Lynn

@DaishaLynn คุณผิดฉันไม่ได้ต่อต้านในทางใด ๆ และในความเป็นจริงฉันใช้มันมาก ... แต่ที่มันเป็นธรรมชาติและเป็นไปได้และไม่พยายามที่จะเปลี่ยนภาษา OO เป็นหนึ่งในการทำงานเพียงเพราะสะโพก ทำเช่นนั้น คุณไม่จำเป็นต้องเห็นด้วยกับคำตอบของฉัน แต่มันก็ไม่ได้เปลี่ยนความจริงที่ว่าคุณใช้เครื่องมือของคุณไม่ถูกต้อง
t3chb0t

ฉันไม่ได้ทำเพราะมันเป็นสิ่งที่ต้องทำ C # กำลังเคลื่อนไปสู่การใช้สไตล์การทำงาน Anders Hejlsberg ได้ระบุเช่นนี้ ฉันเข้าใจว่าคุณสนใจในการใช้ภาษาหลักเท่านั้นและฉันเข้าใจว่าทำไมและเมื่อใดที่เหมาะสม ฉันไม่รู้ว่าทำไมคนอย่างคุณถึงเป็นหัวข้อนี้ .. คุณช่วยได้อย่างไร?
Daisha Lynn

@DaishaLynn หากคุณไม่สามารถรับมือกับคำตอบที่วิพากษ์วิจารณ์คำถามหรือแนวทางของคุณคุณอาจไม่ควรถามคำถามที่นี่หรือในครั้งต่อไปคุณควรเพิ่มข้อจำกัดความรับผิดชอบที่บอกว่าคุณสนใจคำตอบที่สนับสนุนแนวคิดของคุณ 100% เท่านั้น ต้องการฟังความจริง แต่ต้องการรับความคิดเห็นที่สนับสนุน
t3chb0t

โปรดเป็นมิตรต่อกันมากขึ้นอีกเล็กน้อย เป็นไปได้ที่จะให้คำวิจารณ์โดยไม่ใช้ภาษาที่ดูหมิ่น ความพยายามในการเขียนโปรแกรม C # ในรูปแบบการใช้งานนั้นแน่นอนว่าไม่ใช่ "การละเมิด" หรือเป็นตัวพิมพ์ใหญ่ เป็นเทคนิคทั่วไปที่นักพัฒนา C # หลายคนใช้เพื่อเรียนรู้จากภาษาอื่น
zumalifeguard
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.