ตัวตรวจสอบประเภทสามารถช่วยฉันได้ไหม ด้วยตระกูลประเภทอาจจะ?

ฉันจึงเขียนเกมฟุตบอลเล็กๆ นี้มาระยะหนึ่งแล้ว และมีสิ่งหนึ่งที่ทำให้เกิดข้อบกพร่อง ฉันตั้งแต่แรกเริ่ม เกมดังกล่าวเป็นไปตาม Yampa Arcade ดังนั้นจึงมีประเภทผลรวมสำหรับ "วัตถุ" ในเกม:

data ObjState = Ball Id Pos Velo
              | Player Id Team Number Pos Velo
              | Game Id Score

ออบเจ็กต์ตอบสนองต่อข้อความ ดังนั้นจึงมีประเภทผลรวมอีกประเภทหนึ่ง:

data Msg = BallMsg BM
         | PlayerMsg PM
         | GameMsg GM
data BM = Gained | Lost
data PM = GoTo Position | Shoot
data GM = GoalScored | BallOutOfBounds

กรอบงาน Yampa อาศัยสิ่งที่เรียกว่าฟังก์ชันสัญญาณ ในกรณีของเรา มีฟังก์ชันสัญญาณสำหรับพฤติกรรมของลูกบอล ผู้เล่น และเกม เรียบง่ายอย่างหยาบคาย:

ballObj, playerObj, gameObj :: (Time -> (GameInput, [Msg])) 
                               -> (Time -> (ObjState, [(Id, Msg)]))

เช่น ballObj ใช้ฟังก์ชันที่ให้ค่า GameInput (การกดแป้น สถานะของเกม ...) และรายการข้อความสำหรับลูกบอลโดยเฉพาะในเวลาใดก็ตาม และส่งกลับฟังก์ชันที่ให้ค่าสถานะของลูกบอลและข้อความของลูกบอลไปยังวัตถุอื่น ๆ (ball , เกม, ผู้เล่น) ในเวลาใดก็ตาม ใน Yampa ลายเซ็นประเภทจะดูดีกว่าเล็กน้อย:

ballObj, playerObj, gameObj :: SF (GameInput, [Msg]) (ObjState, [(Id, Msg)])

ลายเซ็นประเภทเครื่องแบบนี้มีความสำคัญสำหรับกรอบงาน Yampa: (อีกครั้ง ทำให้ง่ายขึ้นอย่างหยาบมาก) โดยจะสร้างฟังก์ชันสัญญาณขนาดใหญ่จากรายการฟังก์ชันสัญญาณ 11 + 11 (ผู้เล่น) + 1 (บอล) + 1 (เกม) ที่มีประเภทเดียวกัน (ผ่าน dpSwitch) ที่มันทำงาน (ผ่านการโต้ตอบ)

ตอนนี้ สิ่งที่กวนใจฉัน: การส่ง BallMsg ไปยัง Ball หรือ PlayerMsg ไปยังผู้เล่นก็สมเหตุสมผลแล้ว หากมีใครส่ง GameMsg ไปยัง Ball โปรแกรมก็จะล่ม ไม่มีวิธีใดที่จะให้ตัวตรวจสอบประเภทอยู่ในตำแหน่งเพื่อหลีกเลี่ยงปัญหานี้ได้หรือไม่ เมื่อเร็วๆ นี้ฉันได้อ่าน Pokemon ดีๆ นี้ โพสต์เกี่ยวกับประเภทครอบครัวและดูเหมือนว่าจะมีการเปรียบเทียบอยู่บ้าง บางทีนี่อาจเป็นจุดเริ่มต้น:

class Receiver a where
  Msg a :: *
  putAddress :: Msg a -> a -> Msg a

data BallObj = ...
data GameObj = ...
data PlayerObj = ...

instance Receiver BallObj where
  Msg BallObj = Gained | Lost
(...)

ตอนนี้ฟังก์ชัน SF อาจมีลักษณะดังนี้:

forall b . (Receiver a, Receiver b) => SF (GameInput, [Msg a]) (a, [(b, Msg b)])

นี่จะพาฉันไปที่ไหนก็ได้เหรอ?


person martingw    schedule 22.05.2013    source แหล่งที่มา
comment
ผ่อนคลาย. โปรแกรม Haskell ไม่สามารถขัดข้องได้เนื่องจากข้อผิดพลาดประเภท (เว้นแต่คุณจะใช้รหัสที่ไม่ปลอดภัยหรือรหัสต่างประเทศ) คุณมีรหัสการทำงานที่แสดงให้เห็นถึงปัญหาหรือไม่?   -  person n. 1.8e9-where's-my-share m.    schedule 23.05.2013
comment
มันขัดข้องเนื่องจากรูปแบบที่ไม่ครบถ้วนสมบูรณ์ในฟังก์ชันในการประมวลผลข้อความ   -  person martingw    schedule 23.05.2013
comment
เอ่อ เกิดเหตุขัดข้องขนาดนั้น   -  person n. 1.8e9-where's-my-share m.    schedule 23.05.2013
comment
ฉันคิดว่าเนื่องจากข้อความทั้งหมดถูกเผยแพร่ไปยังวัตถุทั้งหมด (เป็นจริงหรือไม่) วัตถุใด ๆ ก็ควรเพิกเฉยต่อข้อความใด ๆ ที่ไม่ได้มีไว้สำหรับข้อความนั้น   -  person n. 1.8e9-where's-my-share m.    schedule 23.05.2013
comment
ไม่ ข้อความจะถูกส่งไปยังผู้รับเฉพาะ ยังคงเป็นตัวเลือกในการเพิกเฉยต่อข้อความที่ไม่ถูกต้อง ฉันแค่คิดว่าฉันอาจจะทำได้ดีกว่านี้   -  person martingw    schedule 23.05.2013
comment
คุณสามารถใช้ข้อจำกัดกับตระกูลประเภทหรือแม้แต่คลาสประเภท (หลายพารามิเตอร์) ได้ แต่จะไม่ช่วยให้คุณมีฟังก์ชันการจัดส่งเพียงฟังก์ชันเดียวในการกำหนดเส้นทางข้อความทั้งหมดของคุณ คุณยังคงต้องการผลรวมของข้อความและออบเจ็กต์ทั้งหมด เนื่องจากคุณไม่สามารถมีคอลเลกชันที่ต่างกันได้ คุณสามารถใช้เทคนิคประเภทเพิ่มเติมเพื่อทำให้การสร้างสิ่งเหล่านี้ดูดี แต่ท้ายที่สุดแล้ว คุณจะต้องใช้ข้อจำกัดประเภทบางอย่างเพื่อรับการตรวจสอบเวลาคอมไพล์สำหรับการส่งข้อความ และประเภทผลรวมบางประเภทสำหรับการประมวลผลข้อความแบบรวมศูนย์   -  person yiding    schedule 23.05.2013
comment
เมื่อกำหนด [Msg] กรอบงานจะรู้ได้อย่างไรว่าข้อความใดไปที่วัตถุใด   -  person n. 1.8e9-where's-my-share m.    schedule 23.05.2013
comment
@nm: การกำหนดเส้นทางข้อความไม่ได้เป็นส่วนหนึ่งของ Yampa แต่ละวัตถุมี id ข้อความจะใช้ id และฟังก์ชันการกำหนดเส้นทางจะแยกแยะว่าใครได้รับข้อความใด   -  person martingw    schedule 23.05.2013
comment
ตกลง เพื่อให้คุณมีแผนบางประเภทจาก ID ไปยังวัตถุ และมีฟังก์ชันที่นำไปใช้ ฟังก์ชันนั้นล้มเหลวไม่ได้หากคุณส่ง ID ที่ไม่ถูกต้องไปใช่หรือไม่ ซึ่งไม่แตกต่างจากการจับคู่รูปแบบที่ล้มเหลวมากนัก มีการตรวจสอบรันไทม์ที่ดูเหมือนจะหลีกเลี่ยงไม่ได้ ทำไมไม่สร้างประเภทวัตถุตรวจสอบฟังก์ชันเดียวกันด้วย?   -  person n. 1.8e9-where's-my-share m.    schedule 23.05.2013
comment
@yiding: ฉันเกรงว่าคุณอาจจะพูดถูกว่านี่ไม่เหมาะกับสิ่งที่การพิมพ์แบบคงที่สามารถทำได้ ...   -  person martingw    schedule 30.05.2013


คำตอบ (2)


ตั้งแต่แรกเห็น ปัญหาสำคัญประการหนึ่งเกี่ยวกับการออกแบบของคุณโดดเด่น: คุณรวมเอนทิตีที่แตกต่างกันโดยสิ้นเชิง Ball, Player และ Game ไว้ภายใต้ประเภทเดียว หากคุณต้องการประเภทสหภาพเหนือเอนทิตีเหล่านั้น ให้ดำเนินการแบบเดียวกับที่คุณมีกับข้อความโดยแยกประเภทออกจากกัน เช่น:

data AnyObject = AnyObjectBall Ball
               | AnyObjectPlayer Player
               | AnyObjectGame Game

ด้วยวิธีนี้คุณจะสามารถแสดงทั้งฟังก์ชันเฉพาะ (Ball -> BallMsg -> ...) และฟังก์ชันทั่วไป (AnyObject -> AnyMsg -> ...)

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

class Signal object message where
  signal :: SF (GameInput, [message]) (object, [(Id, message)])

data Ball = Ball Id Pos Velo
data BallMsg = BallMsgGained | BallMsgLost
instance Signal Ball BallMsg where
  -- ...

-- so on for Player and Game
person Nikita Volkov    schedule 25.05.2013
comment
ขอบคุณ มันควรจะเป็นบางอย่างตามบรรทัดเหล่านี้ อย่างไรก็ตาม จำเป็นต้องมีการพึ่งพาการทำงานหรือความสัมพันธ์ในครอบครัวประเภทระหว่างวัตถุและข้อความ และฉันคิดว่าฉันต้องจัดเรียงข้อความกับวัตถุที่รับในผลลัพธ์ของ SF มันอาจจะยังใช้งานไม่ได้หรือดูงุ่มง่ามมากนัก เพราะท้ายที่สุดแล้ว การสร้างและส่งข้อความจะมีลักษณะแบบไดนามิกที่อาจไม่เหมาะกับการพิมพ์แบบคงที่อยู่แล้ว หากฉันจัดการบางสิ่งบางอย่าง ฉันจะแบ่งปันที่นี่! - person martingw; 30.05.2013
comment
@martingw หากมีการกำหนดประเภทของข้อความหรืออ็อบเจ็กต์ในรันไทม์ ไม่มี fundeps, type-families หรือคุณลักษณะการเขียนโปรแกรมระดับประเภทอื่นใดที่สามารถช่วยคุณได้ ในสถานการณ์นี้ ฉันขอแนะนำให้ใช้การจับคู่รูปแบบกับฟังก์ชันประเภท AnyObject -> AnyMsg -> ... - person Nikita Volkov; 30.05.2013
comment
ฉันคิดว่ามีโอกาสที่จะพิมพ์คงที่: ตัวอย่างเช่น ลูกบอล SF วัดตำแหน่งลูกบอลอย่างต่อเนื่อง และหากลูกบอลอยู่นอกขอบเขต มันอาจส่งข้อความนอกขอบเขตไปยังเกม SF และคุณไม่ได้ อยู่ในความครอบครองอีกต่อไปข้อความถึงผู้เล่นที่มีลูกบอลอยู่ในปัจจุบัน สมมติว่า ID ของเกมคือ 2 และ ID ผู้เล่นคือ 4 จากนั้น Ball SF จะส่งกลับรายการ [(2, OutOfBounds), (4, DropPossession)] และถ้าผมผสมตัวเลขโปรแกรมจะพัง มันอาจทำกับบางอย่างเช่น [(Game, OutOfBounds), (Player, DropPossession)] โดยมี fundep ระหว่างประเภท Object และ Message... - person martingw; 30.05.2013
comment
@martingw แล้วแค่ขยาย AnyMsg ประเภทเพื่อรวมข้อมูลที่คุณใส่ลงในทูเพิล (id หรืออะไรก็ตาม)? ฉันหมายถึง data AnyMsg = ABallMsg Id BallMsg | ... และใช้เพียง AnyMsg แทน (Id, AnyMsg) - person Nikita Volkov; 30.05.2013
comment
Nikita สิ่งต่อไปนี้ในข้อเสนอของคุณที่ไม่เหมาะกับปัญหาของฉัน: คุณมีข้อความตัวแปรประเภทในอาร์กิวเมนต์แรกและในส่วนที่สองของ SF ซึ่งหมายความว่าพวกมันเท่ากัน แต่ประเภทของข้อความแรกนั้นขึ้นอยู่กับวัตถุโดยตรง (Ball SF สามารถประมวลผลได้เฉพาะข้อความลูกบอลเท่านั้น) ในขณะที่ข้อความที่สองสามารถเป็นข้อความไปยังวัตถุประเภทใดก็ได้ ดังนั้น ฉันพบว่าคุณต้องการเงินทุนเพื่อจัดการกับปัญหาแรก และรวบรวมวัตถุและข้อความเพื่อจัดการปัญหาที่สอง - person martingw; 30.05.2013

การอ่านกระดาษอาร์เคดของ yampa ดูเหมือนว่าคุณมีฟังก์ชัน route ที่ดึงมาจากตัวอย่างของพวกเขา

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

data BallMsg = ...
data PlayerMsg = ...
data GameMsg = ...

data AnyMsg = ABallMsg BallMsg
            | APlayerMsg PlayerMsg
            | AGameMsg GameMsg

ตอนนี้ route ใช้งานได้กับเครื่องแบบ AnyMsg แต่จะจัดส่งไปยังปลายทางที่ถูกต้องโดยขึ้นอยู่กับเนื้อหา

person sclv    schedule 24.05.2013
comment
ขอบคุณสำหรับข้อมูล! รหัสของฉันได้รับการจัดระเบียบแล้วตามที่คุณแนะนำ ปัญหาคือ ฉันไม่ได้รับตัวตรวจสอบประเภทเพื่อป้องกันการรวมวัตถุ / ข้อความที่ผิดกฎหมายในเวลารวบรวม ฉันคิดว่าฉันต้องเรียบเรียงคำถามใหม่ ฉันเห็นว่ามันไม่ชัดเจนว่าฉันต้องการอะไร - person martingw; 25.05.2013