Ho trovato una soluzione che utilizza il sistema di tipi Haskell. Ho cercato su Google una soluzione esistente al problema a livello di valore , l'ho modificato un po 'e poi l'ho portato al livello del tipo. Ci è voluto molto reinventare. Ho anche dovuto abilitare un sacco di estensioni GHC.
Innanzitutto, poiché gli interi non sono ammessi a livello di tipo, ho dovuto reinventare ancora una volta i numeri naturali, questa volta come tipi:
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
L'algoritmo che ho adattato rende aggiunte e sottrazioni ai naturali, quindi ho dovuto reinventare anche queste. Le funzioni a livello di tipo sono definite ricorrendo a classi di tipo. Ciò richiede le estensioni per più classi di tipi di parametri e dipendenze funzionali. Le classi di tipi non possono "restituire valori", quindi per questo utilizziamo un parametro aggiuntivo, in modo simile a 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
La ricorsione è implementata con asserzioni di classe, quindi la sintassi sembra un po 'arretrata.
Il prossimo erano i booleani:
data True -- type that represents truth
data False -- type that represents falsehood
E una funzione per fare confronti di disuguaglianza:
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
E liste ...
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
mancano anche a livello di tipo ...
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
E con ciò, tutte le macchine di supporto che ho usato erano a posto. È ora di affrontare il problema stesso!
Iniziare con una funzione per verificare se l'aggiunta di una regina a una scheda esistente è 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)]
Notare l'uso delle asserzioni di classe per ottenere risultati intermedi. Poiché i valori restituiti sono in realtà un parametro aggiuntivo, non possiamo semplicemente chiamare le asserzioni direttamente l'una dall'altra. Ancora una volta, se hai usato PROLOG prima di trovare questo stile un po 'familiare.
Dopo aver apportato alcune modifiche per rimuovere la necessità di lambdas (che avrei potuto implementare, ma ho deciso di partire per un altro giorno), ecco come appariva la soluzione originale:
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
è una funzione di ordine superiore. Pensavo che l'implementazione di meta-funzioni di ordine superiore sarebbe stata una seccatura (di nuovo i lambda), quindi ho appena optato per una soluzione più semplice: poiché so quali funzioni verranno mappate, posso implementare versioni specializzate map
per ciascuna, in modo che quelle non lo siano funzioni di ordine superiore.
-- 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)
E l'ultima meta-funzione può essere scritta ora:
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
Tutto ciò che resta è una specie di driver per convincere i macchinari di controllo del tipo a elaborare le soluzioni.
-- dummy value of type Eight
eight = undefined :: Eight
-- dummy function that asserts the Queens class
queens :: Queens n r => n -> r
queens = const undefined
Questo meta-programma dovrebbe essere eseguito sul controllo dei tipi, quindi si può accendere ghci
e chiedere il tipo di queens eight
:
> :t queens eight
Questo supererà il limite di ricorsione predefinito piuttosto velocemente (è un misero 20). Per aumentare questo limite, dobbiamo invocare ghci
con l' -fcontext-stack=N
opzione, dove N
è la profondità dello stack desiderata (N = 1000 e quindici minuti non sono sufficienti). Non ho ancora visto questa corsa fino al completamento, in quanto richiede molto tempo, ma sono riuscito a correre fino a queens four
.
Esiste un programma completo su ideone con alcuni macchinari per la stampa piuttosto dei tipi di risultati, ma queens two
può funzionare solo senza superare i limiti :(