Bisakah pemeriksa tipe membantu saya di sini? Dengan tipe keluarga, mungkin?

Jadi saya menulis permainan sepak bola kecil ini untuk beberapa waktu sekarang, dan ada satu hal yang mengganggu saya sejak awal. Game ini mengikuti Yampa Arcade polanya, jadi ada tipe penjumlahan untuk "objek" di dalam game:

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

Objek bereaksi terhadap pesan, jadi ada tipe penjumlahan lainnya:

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

Kerangka kerja Yampa bergantung pada apa yang disebut fungsi sinyal. Dalam kasus kami, terdapat fungsi sinyal untuk perilaku bola, pemain, dan permainan. Disederhanakan secara kasar:

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

Jadi mis. ballObj mengambil fungsi yang menghasilkan GameInput (sentuhan tombol, status permainan, ...) dan daftar pesan khusus untuk bola pada waktu tertentu, dan mengembalikan fungsi yang menghasilkan status bola dan pesannya ke objek lain (bola , permainan, pemain) pada waktu tertentu. Di Yampa, tanda tangan tipe sebenarnya terlihat sedikit lebih bagus:

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

Tanda tangan tipe seragam ini penting untuk kerangka Yampa: (sekali lagi, disederhanakan dengan sangat kasar) ia membangun fungsi sinyal besar dari daftar fungsi sinyal 11 + 11 (pemain) + 1 (bola) + 1 (permainan) dengan tipe yang sama (melalui dpSwitch) yang kemudian dijalankan (melalui pengaktifan kembali).

Jadi sekarang, apa yang mengganggu saya: Masuk akal untuk mengirim BallMsg ke Ball, atau PlayerMsg ke Player. Jika seseorang mengirim misalnya GameMsg ke Ball, programnya akan crash. Apakah tidak ada cara untuk menempatkan pemeriksa tipe untuk menghindari hal ini? Saya baru-baru ini membaca Pokemon yang bagus ini posting tentang keluarga tipe, dan sepertinya ada beberapa analogi. Jadi mungkin ini bisa menjadi titik awal:

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
(...)

Sekarang, fungsi SF mungkin terlihat seperti ini:

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

Apakah ini akan membawaku kemana saja?


person martingw    schedule 22.05.2013    source sumber
comment
Santai. Program Haskell tidak bisa crash karena kesalahan ketik (kecuali Anda menggunakan kode yang tidak aman atau asing). Apakah Anda memiliki kode kerja yang menunjukkan masalahnya?   -  person n. 1.8e9-where's-my-share m.    schedule 23.05.2013
comment
Itu macet karena pola yang tidak lengkap dalam fungsi proses pesan.   -  person martingw    schedule 23.05.2013
comment
Ah, kecelakaan seperti itu.   -  person n. 1.8e9-where's-my-share m.    schedule 23.05.2013
comment
Saya pikir karena semua pesan disiarkan ke semua objek (apakah ini benar?), objek apa pun harus mengabaikan pesan apa pun yang tidak dimaksudkan untuk itu.   -  person n. 1.8e9-where's-my-share m.    schedule 23.05.2013
comment
Tidak, pesan dikirim ke penerima tertentu. Ini masih merupakan pilihan untuk mengabaikan pesan yang tidak valid, saya hanya berpikir bahwa saya mungkin melakukan yang lebih baik.   -  person martingw    schedule 23.05.2013
comment
Anda dapat menerapkan batasan dengan kelompok tipe atau bahkan kelas tipe (multi-param), tetapi itu tidak akan membantu Anda jika memiliki fungsi pengiriman tunggal untuk merutekan semua pesan Anda. Anda tetap memerlukan tipe penjumlahan semua pesan dan objek, karena Anda tidak dapat memiliki koleksi yang heterogen. Anda dapat menggunakan trik tipe tambahan untuk membuat pembuatan hal-hal ini terlihat bagus, namun pada akhirnya Anda akan menggunakan beberapa batasan tipe untuk mendapatkan pemeriksaan waktu kompilasi untuk pengiriman pesan, dan beberapa tipe penjumlahan untuk pemrosesan pesan terpusat.   -  person yiding    schedule 23.05.2013
comment
Diberikan [Msg], bagaimana kerangka kerja mengetahui pesan mana yang masuk ke objek mana?   -  person n. 1.8e9-where's-my-share m.    schedule 23.05.2013
comment
@nm: perutean pesan bukan bagian dari Yampa, setiap objek memiliki id, pesan mengambil id dan fungsi perutean memilah siapa yang mendapat pesan mana.   -  person martingw    schedule 23.05.2013
comment
Oke, jadi Anda memiliki semacam peta dari ID ke objek, dan fungsi yang mengimplementasikannya. Tidak bisakah fungsi itu gagal jika Anda memberikan ID yang buruk ke dalamnya? Ini tidak jauh berbeda dengan pencocokan pola yang gagal. Ada pemeriksaan runtime yang tampaknya tidak dapat dihindari. Mengapa tidak membuat fungsi yang sama memeriksa tipe objek juga?   -  person n. 1.8e9-where's-my-share m.    schedule 23.05.2013
comment
@yiding: Saya khawatir Anda mungkin benar, bahwa ini tidak cocok untuk apa yang dapat dilakukan pengetikan statis...   -  person martingw    schedule 30.05.2013


Jawaban (2)


Dari pandangan pertama, ada satu masalah besar dengan desain Anda yang menonjol: Anda menyatukan entitas yang benar-benar berbeda Ball, Player dan Game dalam satu tipe. Jika Anda memerlukan tipe gabungan pada entitas tersebut, lakukan cara yang sama dengan pesan dengan menjadikannya tipe terpisah, yaitu:

data AnyObject = AnyObjectBall Ball
               | AnyObjectPlayer Player
               | AnyObjectGame Game

Dengan cara ini Anda dapat mengekspresikan fungsi spesifik (Ball -> BallMsg -> ...) dan fungsi umum (AnyObject -> AnyMsg -> ...).

Tetapi jika saya memahami masalah Anda dengan benar, saya rasa saya punya solusi untuk Anda yang tidak memerlukan tipe gabungan:

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
Terima kasih, seharusnya ada sesuatu seperti ini. Namun, perlu ada semacam ketergantungan fungsional atau hubungan keluarga tipe antara objek dan pesan, dan saya rasa saya perlu menggabungkan pesan dengan objek penerima di hasil SF. Ini mungkin masih tidak berfungsi atau terlihat sangat canggung, karena pada akhirnya pembuatan dan pengiriman pesan bersifat dinamis yang mungkin tidak cocok dengan pengetikan statis. Jika saya pernah mengelola sesuatu, saya akan membagikannya di sini! - person martingw; 30.05.2013
comment
@martingw Jika jenis pesan atau objek ditentukan saat runtime, maka tidak ada fundep, keluarga tipe, atau fitur pemrograman tingkat tipe lainnya yang dapat membantu Anda. Dalam skenario ini saya menyarankan untuk tetap menggunakan pencocokan pola dengan fungsi tipe AnyObject -> AnyMsg -> .... - person Nikita Volkov; 30.05.2013
comment
Saya pikir ada kemungkinan untuk beberapa pengetikan statis: Misalnya, bola SF terus-menerus mengukur posisi bola, dan jika bola berada di luar batas, itu mungkin mengirimkan pesan di luar batas ke permainan SF, dan Anda tidak dalam penguasaan bola lagi pesan kepada pemain yang sedang menguasai bola. Katakanlah ID Game adalah 2 dan ID pemain adalah 4, maka Ball SF mengembalikan daftar [(2, OutOfBounds), (4, DropPossession)]. Dan jika saya mencampur angka-angkanya, programnya akan crash. Ini mungkin dilakukan dengan sesuatu seperti [(Game, OutOfBounds), (Player, DropPossession)] dengan fundep antara tipe Objek dan Pesan... - person martingw; 30.05.2013
comment
@martingw Bagaimana dengan memperluas tipe AnyMsg untuk memasukkan info yang Anda masukkan ke dalam Tuple (id atau apa pun)? Maksud saya data AnyMsg = ABallMsg Id BallMsg | ... dan gunakan AnyMsg saja, bukan (Id, AnyMsg). - person Nikita Volkov; 30.05.2013
comment
Nikita, proposal Anda berikut ini yang tidak sesuai dengan masalah saya: Anda memiliki pesan variabel tipe di argumen pertama dan di argumen kedua SF, menyiratkan keduanya sama. Namun jenis pesan pertama secara langsung bergantung pada objeknya (Ball SF hanya dapat memproses pesan bola saja), sedangkan pesan kedua dapat berupa pesan ke objek apa pun. Oleh karena itu, menurut saya Anda memerlukan dana untuk mengatasi masalah pertama, dan mengumpulkan objek dan pesan untuk mengatasi masalah kedua. - person martingw; 30.05.2013

Membaca sekilas kertas yampa arcade sepertinya Anda memiliki fungsi route yang diambil dari contohnya.

Saran saya adalah Anda mengubah route sehingga tidak mengambil satu daftar objek, melainkan satu objek permainan, satu objek bola, dan kumpulan objek pemain. Lalu miliki

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

data AnyMsg = ABallMsg BallMsg
            | APlayerMsg PlayerMsg
            | AGameMsg GameMsg

Sekarang route bekerja dengan seragam AnyMsg tetapi mengirimkannya ke tujuan yang benar tergantung pada isinya.

person sclv    schedule 24.05.2013
comment
Terima kasih atas masukannya! Kode saya sudah diatur seperti yang Anda sarankan, masalahnya adalah saya tidak mendapatkan pemeriksa tipe untuk mencegah kombinasi objek/pesan ilegal pada waktu kompilasi. Saya rasa saya harus mengulangi pertanyaan saya, saya melihat tidak begitu jelas apa yang saya inginkan. - person martingw; 25.05.2013