Ich habe eine Lösung gefunden, die das Haskell-System verwendet. Ich habe ein wenig nach einer vorhandenen Lösung für das Problem auf der Wertebene gegoogelt , sie ein wenig geändert und sie dann auf die Typebene angehoben. Es bedurfte einer Menge Neuerfindungen. Ich musste auch eine Reihe von GHC-Erweiterungen aktivieren.
Erstens, da Ganzzahlen auf Typebene nicht zulässig sind, musste ich die natürlichen Zahlen noch einmal neu erfinden, diesmal als Typen:
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
Der Algorithmus, den ich angepasst habe, addiert und subtrahiert Naturals, daher musste ich diese ebenfalls neu erfinden. Funktionen auf Typebene werden mit Rückgriff auf Typklassen definiert. Dies erfordert die Erweiterungen für mehrere Parametertypklassen und funktionale Abhängigkeiten. Typklassen können keine "Werte zurückgeben", daher verwenden wir einen zusätzlichen Parameter, ähnlich wie bei 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
Die Rekursion wird mit Zusicherungen von Klassen implementiert, sodass die Syntax etwas rückständig wirkt.
Als nächstes waren Booleaner:
data True -- type that represents truth
data False -- type that represents falsehood
Und eine Funktion zum Vergleichen von Ungleichungen:
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
Und Listen ...
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 fehlen auch auf der Typebene ...
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
Und damit waren alle unterstützenden Maschinen, die ich benutzte, an Ort und Stelle. Zeit, das Problem selbst anzugehen!
Beginnen Sie mit einer Funktion, um zu testen, ob das Hinzufügen einer Dame zu einem vorhandenen Board in Ordnung ist:
-- 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)]
Beachten Sie die Verwendung von Class Assertions, um Zwischenergebnisse zu erhalten. Da die Rückgabewerte tatsächlich ein zusätzlicher Parameter sind, können wir die Zusicherungen nicht einfach direkt voneinander aufrufen. Wenn Sie PROLOG bereits verwendet haben, ist Ihnen dieser Stil möglicherweise ein wenig vertraut.
Nachdem ich einige Änderungen vorgenommen hatte, um die Notwendigkeit von Lambdas zu beseitigen (die ich hätte implementieren können, aber ich beschloss, für einen anderen Tag zu gehen), sah die ursprüngliche Lösung folgendermaßen aus:
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
ist eine Funktion höherer Ordnung. Ich dachte, das Implementieren von Meta-Funktionen höherer Ordnung wäre zu mühsam (wieder Lambdas), also entschied ich mich für eine einfachere Lösung: Da ich weiß, welche Funktionen abgebildet werden, kann ich map
für jede spezielle Versionen implementieren , so dass dies nicht der Fall ist Funktionen höherer Ordnung.
-- 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)
Und die letzte Meta-Funktion kann jetzt geschrieben werden:
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
Alles, was bleibt, ist eine Art Fahrer, der die Maschinen zur Typprüfung überredet, die Lösungen zu erarbeiten.
-- dummy value of type Eight
eight = undefined :: Eight
-- dummy function that asserts the Queens class
queens :: Queens n r => n -> r
queens = const undefined
Dieses Metaprogramm soll auf dem Type Checker laufen, so dass man starten und nach dem Typ ghci
fragen kann queens eight
:
> :t queens eight
Dies wird die Standard-Rekursionsgrenze ziemlich schnell überschreiten (es ist eine dürftige 20). Um dieses Limit zu erhöhen, müssen wir ghci
mit der -fcontext-stack=N
Option aufrufen , wo N
die gewünschte Stapeltiefe ist (N = 1000 und 15 Minuten sind nicht genug). Ich habe diesen Lauf noch nicht vollständig gesehen, da er sehr lange dauert, aber ich habe es geschafft, ihn zu erreichen queens four
.
Es gibt ein volles Programm auf ideone mit einigen Maschinen zum hübschen Drucken der Ergebnistypen, aber es queens two
kann nur ausgeführt werden, ohne die Grenzen zu überschreiten :(