Je suis venu avec une solution qui utilise le système de type Haskell. J'ai cherché un peu une solution existante au problème au niveau de la valeur , puis je l'ai modifié un peu, puis je l'ai porté au niveau du type. Il a fallu beaucoup de réinventer. Je devais également activer plusieurs extensions GHC.
Premièrement, comme les entiers ne sont pas autorisés au niveau des types, je devais réinventer les nombres naturels une fois de plus, cette fois en tant que types:
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'algorithme que j'ai adapté effectue des additions et des soustractions sur les naturels, donc je devais les réinventer également. Les fonctions au niveau du type sont définies avec recours à des classes de types. Cela nécessite les extensions pour plusieurs classes de types de paramètres et dépendances fonctionnelles. Les classes de types ne peuvent pas "renvoyer de valeurs", nous utilisons donc un paramètre supplémentaire pour cela, d'une manière similaire à 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 récursivité est implémentée avec des assertions de classe, la syntaxe est donc un peu en arrière.
Viennent ensuite les booléens:
data True -- type that represents truth
data False -- type that represents falsehood
Et une fonction pour faire des comparaisons d'inégalité:
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
Et des listes ...
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 sont également absents au niveau du type ...
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
Et avec cela, toutes les machines de soutien que j'ai utilisées étaient en place. Il est temps de s'attaquer au problème lui-même!
Commençant par une fonction pour tester si l'ajout d'une reine à un tableau existant est correct:
-- 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)]
Notez l'utilisation d'assertions de classe pour obtenir des résultats intermédiaires. Comme les valeurs de retour sont en réalité un paramètre supplémentaire, nous ne pouvons pas appeler les assertions directement les unes des autres. Encore une fois, si vous avez déjà utilisé PROLOG, vous trouverez peut-être ce style un peu familier.
Après avoir apporté quelques modifications pour supprimer le besoin de lambdas (que j’aurais pu mettre en œuvre, mais j’ai décidé de partir un autre jour), voici à quoi ressemblait la solution initiale:
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
est une fonction d'ordre supérieur. Je pensais que mettre en œuvre des méta-fonctions d'ordre supérieur serait trop compliqué (encore une fois, les lambdas). Je me suis donc tourné vers une solution plus simple: puisque je sais quelles fonctions seront mappées, je peux implémenter des versions spécialisées map
pour chacune, de sorte que ce ne sont pas fonctions d'ordre supérieur.
-- 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)
Et la dernière méta-fonction peut être écrite maintenant:
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
Il ne reste plus qu’une sorte de moteur pour convaincre les vérificateurs de types de trouver les solutions.
-- dummy value of type Eight
eight = undefined :: Eight
-- dummy function that asserts the Queens class
queens :: Queens n r => n -> r
queens = const undefined
Ce méta-programme est supposé s'exécuter sur le vérificateur de types, on peut donc lancer ghci
et demander le type de queens eight
:
> :t queens eight
Cela dépassera assez rapidement la limite de récursivité par défaut (c'est un maigre 20). Pour augmenter cette limite, nous devons appeler ghci
avec l' -fcontext-stack=N
option, où N
est la profondeur de pile souhaitée (N = 1000 et quinze minutes ne sont pas suffisants). Je n'ai pas encore vu ce processus aboutir, car cela prend beaucoup de temps, mais j'ai réussi à le faire queens four
.
Il existe un programme complet sur ideone avec quelques machines pour imprimer les types de résultats, mais il ne queens two
peut fonctionner que sans dépasser les limites :(