Saya datang dengan solusi yang menggunakan sistem tipe Haskell. Saya mencari sedikit Google untuk solusi yang ada untuk masalah di tingkat nilai , mengubahnya sedikit, dan kemudian mengangkatnya ke tingkat tipe. Butuh banyak penemuan kembali. Saya juga harus mengaktifkan banyak ekstensi GHC.
Pertama, karena bilangan bulat tidak diperbolehkan pada level tipe, saya perlu menemukan kembali bilangan alami sekali lagi, kali ini sebagai tipe:
data Zero -- type that represents zero
data S n -- type constructor that constructs the successor of another natural number
-- Some numbers shortcuts
type One = S Zero
type Two = S One
type Three = S Two
type Four = S Three
type Five = S Four
type Six = S Five
type Seven = S Six
type Eight = S Seven
Algoritma yang saya adaptasikan membuat penambahan dan pengurangan pada naturals, jadi saya harus menemukan kembali ini juga. Fungsi pada level tipe didefinisikan dengan kelas resort to type. Ini membutuhkan ekstensi untuk beberapa kelas tipe parameter dan dependensi fungsional. Tipe kelas tidak dapat "mengembalikan nilai", jadi kami menggunakan parameter tambahan untuk itu, dengan cara yang mirip dengan PROLOG.
class Add a b r | a b -> r -- last param is the result
instance Add Zero b b -- 0 + b = b
instance (Add a b r) => Add (S a) b (S r) -- S(a) + b = S(a + b)
class Sub a b r | a b -> r
instance Sub a Zero a -- a - 0 = a
instance (Sub a b r) => Sub (S a) (S b) r -- S(a) - S(b) = a - b
Rekursi diimplementasikan dengan pernyataan kelas, sehingga sintaks terlihat agak terbelakang.
Selanjutnya adalah boolean:
data True -- type that represents truth
data False -- type that represents falsehood
Dan fungsi untuk melakukan perbandingan ketidaksetaraan:
class NotEq a b r | a b -> r
instance NotEq Zero Zero False -- 0 /= 0 = False
instance NotEq (S a) Zero True -- S(a) /= 0 = True
instance NotEq Zero (S a) True -- 0 /= S(a) = True
instance (NotEq a b r) => NotEq (S a) (S b) r -- S(a) /= S(b) = a /= b
Dan daftar ...
data Nil
data h ::: t
infixr 0 :::
class Append xs ys r | xs ys -> r
instance Append Nil ys ys -- [] ++ _ = []
instance (Append xs ys rec) => Append (x ::: xs) ys (x ::: rec) -- (x:xs) ++ ys = x:(xs ++ ys)
class Concat xs r | xs -> r
instance Concat Nil Nil -- concat [] = []
instance (Concat xs rec, Append x rec r) => Concat (x ::: xs) r -- concat (x:xs) = x ++ concat xs
class And l r | l -> r
instance And Nil True -- and [] = True
instance And (False ::: t) False -- and (False:_) = False
instance (And t r) => And (True ::: t) r -- and (True:t) = and t
if
s juga hilang pada tingkat tipe ...
class Cond c t e r | c t e -> r
instance Cond True t e t -- cond True t _ = t
instance Cond False t e e -- cond False _ e = e
Dan dengan itu, semua mesin pendukung yang saya gunakan ada di tempatnya. Saatnya mengatasi masalah itu sendiri!
Dimulai dengan fungsi untuk menguji apakah menambahkan ratu ke papan yang ada sudah ok:
-- Testing if it's safe to add a queen
class Safe x b n r | x b n -> r
instance Safe x Nil n True -- safe x [] n = True
instance (Safe x y (S n) rec,
Add c n cpn, Sub c n cmn,
NotEq x c c1, NotEq x cpn c2, NotEq x cmn c3,
And (c1 ::: c2 ::: c3 ::: rec ::: Nil) r) => Safe x (c ::: y) n r
-- safe x (c:y) n = and [ x /= c , x /= c + n , x /= c - n , safe x y (n+1)]
Perhatikan penggunaan asersi kelas untuk mendapatkan hasil antara. Karena nilai pengembalian sebenarnya merupakan parameter tambahan, kami tidak bisa langsung memanggil pernyataan satu sama lain secara langsung. Sekali lagi, jika Anda telah menggunakan PROLOG sebelum Anda mungkin menemukan gaya ini agak akrab.
Setelah saya membuat beberapa perubahan untuk menghilangkan kebutuhan lambda (yang bisa saya terapkan, tetapi saya memutuskan untuk pergi untuk hari lain), inilah yang tampak seperti solusi aslinya:
queens 0 = [[]]
-- The original used the list monad. I "unrolled" bind into concat & map.
queens n = concat $ map f $ queens (n-1)
g y x = if safe x y 1 then [x:y] else []
f y = concat $ map (g y) [1..8]
map
adalah fungsi urutan yang lebih tinggi. Saya pikir menerapkan meta-fungsi tingkat tinggi akan terlalu merepotkan (sekali lagi lambda) jadi saya hanya pergi dengan solusi yang lebih sederhana: karena saya tahu fungsi apa yang akan dipetakan, saya dapat mengimplementasikan versi khusus map
untuk masing-masing, sehingga tidak fungsi tingkat tinggi.
-- Auxiliary meta-functions
class G y x r | y x -> r
instance (Safe x y One s, Cond s ((x ::: y) ::: Nil) Nil r) => G y x r
class MapG y l r | y l -> r
instance MapG y Nil Nil
instance (MapG y xs rec, G y x g) => MapG y (x ::: xs) (g ::: rec)
-- Shortcut for [1..8]
type OneToEight = One ::: Two ::: Three ::: Four ::: Five ::: Six ::: Seven ::: Eight ::: Nil
class F y r | y -> r
instance (MapG y OneToEight m, Concat m r) => F y r -- f y = concat $ map (g y) [1..8]
class MapF l r | l -> r
instance MapF Nil Nil
instance (MapF xs rec, F x f) => MapF (x ::: xs) (f ::: rec)
Dan meta-fungsi terakhir dapat ditulis sekarang:
class Queens n r | n -> r
instance Queens Zero (Nil ::: Nil)
instance (Queens n rec, MapF rec m, Concat m r) => Queens (S n) r
Yang tersisa hanyalah beberapa jenis driver untuk membujuk mesin pengecekan tipe untuk mencari solusinya.
-- dummy value of type Eight
eight = undefined :: Eight
-- dummy function that asserts the Queens class
queens :: Queens n r => n -> r
queens = const undefined
Program-meta ini seharusnya dijalankan pada pemeriksa tipe, sehingga orang dapat menjalankan ghci
dan menanyakan jenis queens eight
:
> :t queens eight
Ini akan melebihi batas rekursi default agak cepat (ini sangat sedikit 20). Untuk meningkatkan batas ini, kita perlu memanggil ghci
dengan -fcontext-stack=N
pilihan, di mana N
adalah kedalaman tumpukan yang diinginkan (N = 1000 dan lima belas menit tidak cukup). Saya belum melihat ini berjalan sampai selesai, karena itu membutuhkan waktu yang sangat lama, tetapi saya sudah berhasil berlari hingga queens four
.
Ada program lengkap tentang ideone dengan beberapa mesin untuk mencetak tipe hasil yang cantik, tetapi hanya ada yang queens two
bisa berjalan tanpa melebihi batas :(