Tôi đã đưa ra một giải pháp sử dụng hệ thống loại Haskell. Tôi đã googled một chút cho một giải pháp hiện có cho vấn đề ở cấp giá trị , thay đổi nó một chút, và sau đó nâng nó lên mức loại. Phải mất rất nhiều sáng tạo lại. Tôi cũng đã phải kích hoạt một loạt các tiện ích mở rộng GHC.
Đầu tiên, vì số nguyên không được phép ở cấp độ loại, tôi cần phải phát minh lại các số tự nhiên một lần nữa, lần này là các loại:
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
Thuật toán tôi điều chỉnh tạo ra các phép cộng và phép trừ trên tự nhiên, vì vậy tôi cũng phải phát minh lại chúng. Các hàm ở mức kiểu được định nghĩa với các lớp loại. Điều này đòi hỏi các phần mở rộng cho nhiều lớp loại tham số và các phụ thuộc chức năng. Các lớp loại không thể "trả về giá trị", vì vậy chúng tôi sử dụng một tham số phụ cho điều đó, theo cách tương tự như PRITAL.
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
Đệ quy được thực hiện với các xác nhận lớp, vì vậy cú pháp có vẻ hơi lạc hậu.
Tiếp theo là booleans:
data True -- type that represents truth
data False -- type that represents falsehood
Và một chức năng để so sánh bất bình đẳng:
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
Và danh sách ...
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 cũng bị thiếu ở cấp độ loại ...
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
Và với điều đó, tất cả các máy móc hỗ trợ tôi sử dụng đã được đưa ra. Thời gian để giải quyết vấn đề chính nó!
Bắt đầu với một chức năng để kiểm tra nếu thêm một nữ hoàng vào một bảng hiện có là 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)]
Lưu ý việc sử dụng các xác nhận lớp để có được kết quả trung gian. Bởi vì các giá trị trả về thực sự là một tham số phụ, chúng ta không thể gọi các xác nhận trực tiếp từ nhau. Một lần nữa, nếu bạn đã sử dụng PRITAL trước khi bạn có thể thấy phong cách này hơi quen thuộc.
Sau khi tôi thực hiện một vài thay đổi để loại bỏ nhu cầu về lambdas (điều mà tôi có thể thực hiện, nhưng tôi quyết định rời đi vào một ngày khác), đây là giải pháp ban đầu giống như:
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
là một hàm bậc cao hơn. Tôi nghĩ rằng việc thực hiện các hàm meta bậc cao sẽ gặp quá nhiều rắc rối (lại là lambdas) vì vậy tôi chỉ thực hiện một giải pháp đơn giản hơn: vì tôi biết các chức năng nào sẽ được ánh xạ, tôi có thể triển khai các phiên bản chuyên biệt map
cho từng chức năng, vì vậy những thứ đó không chức năng bậc cao.
-- 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)
Và hàm meta cuối cùng có thể được viết ngay bây giờ:
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
Tất cả những gì còn lại là một loại trình điều khiển để dỗ máy móc kiểm tra loại để tìm ra giải pháp.
-- dummy value of type Eight
eight = undefined :: Eight
-- dummy function that asserts the Queens class
queens :: Queens n r => n -> r
queens = const undefined
Chương trình meta này được cho là chạy trên trình kiểm tra loại, vì vậy người ta có thể kích hoạt ghci
và yêu cầu loại queens eight
:
> :t queens eight
Điều này sẽ vượt quá giới hạn đệ quy mặc định khá nhanh (đó là 20). Để tăng giới hạn này, chúng ta cần gọi ghci
với -fcontext-stack=N
tùy chọn, N
độ sâu ngăn xếp mong muốn (N = 1000 và mười lăm phút là không đủ). Tôi chưa thấy chạy này để hoàn thành, vì phải mất một thời gian rất dài, nhưng tôi đã xoay sở để chạy đến queens four
.
Có một chương trình đầy đủ về ideone với một số máy móc để in đẹp các loại kết quả, nhưng chỉ queens two
có thể chạy mà không vượt quá giới hạn :(