web-dev-qa-db-fra.com

Comment fonctionne ce morceau de code Haskell obscurci?

En lisant http://uncyclopedia.wikia.com/wiki/Haskell (et en ignorant toutes les choses "offensantes"), je suis tombé sur le morceau de code obscurci suivant:

fix$(<$>)<$>(:)<*>((<$>((:[{- thor's mother -}])<$>))(=<<)<$>(*)<$>(*2))$1

Lorsque j'exécute ce morceau de code dans ghci (après avoir importé Data.Function et Control.Applicative), ghci affiche la liste de toutes les puissances de 2.

Comment fonctionne ce morceau de code?

88
Alexandros

Pour commencer, nous avons la belle définition

x = 1 : map (2*) x

ce qui en soi est un peu hallucinant si vous ne l'avez jamais vu auparavant. Quoi qu'il en soit, c'est une astuce assez standard de paresse et de récursivité. Maintenant, nous allons nous débarrasser de la récursivité explicite en utilisant fix et point-free-ify.

x = fix (\vs -> 1 : map (2*) vs)
x = fix ((1:) . map (2*))

La prochaine chose que nous allons faire est d'étendre le : section et rendre la map inutilement complexe.

x = fix ((:) 1 . (map . (*) . (*2)) 1)

Eh bien, nous avons maintenant deux copies de cette constante 1. Cela ne fera jamais l'affaire, nous allons donc utiliser le lecteur applicatif pour dédoublonner cela. De plus, la composition des fonctions est un peu détraquée, alors remplaçons-la par (<$>) partout où nous le pouvons.

x = fix (liftA2 (.) (:) (map . (*) . (*2)) 1)
x = fix (((.) <$> (:) <*> (map . (*) . (*2))) 1)
x = fix (((<$>) <$> (:) <*> (map <$> (*) <$> (*2))) 1)

Ensuite: cet appel à map est beaucoup trop lisible. Mais il n'y a rien à craindre: on peut utiliser les lois de la monade pour l'étendre un peu. En particulier, fmap f x = x >>= return . f, donc

map f x = x >>= return . f
map f x = ((:[]) <$> f) =<< x

Nous pouvons pointer-libre-ify, remplacer (.) avec (<$>), puis ajoutez des sections parasites:

map = (=<<) . ((:[]) <$>)
map = (=<<) <$> ((:[]) <$>)
map = (<$> ((:[]) <$>)) (=<<)

En remplaçant cette équation dans notre étape précédente:

x = fix (((<$>) <$> (:) <*> ((<$> ((:[]) <$>)) (=<<) <$> (*) <$> (*2))) 1)

Enfin, vous cassez votre barre d'espace et produisez la merveilleuse équation finale

x=fix(((<$>)<$>(:)<*>((<$>((:[])<$>))(=<<)<$>(*)<$>(*2)))1)
211
Daniel Wagner

J'écrivais une longue réponse avec un parcours complet de mes IRC journaux des expériences menant au code final (c'était au début de 2008), mais j'ai accidentellement tout le texte :) Non autant de perte - pour la plupart, l'analyse de Daniel est parfaite.

Voici ce que j'ai commencé avec:

Jan 25 23:47:23 <olsner>        @pl let q = 2 : map (2*) q in q
Jan 25 23:47:23 <lambdabot>     fix ((2 :) . map (2 *))

Les différences se résument principalement à l'ordre dans lequel les restructurations ont eu lieu.

  • Au lieu de x = 1 : map (2*) x j'ai commencé avec 2 : map ..., Et j'ai gardé ce 2 initial jusqu'à la toute dernière version, où j'ai pressé un (*2) Et changé le $2 À la fin en $1. L'étape "rendre la carte inutilement complexe" ne s'est pas produite (si tôt).
  • J'ai utilisé liftM2 au lieu de liftA2
  • La fonction obscurcie map a été ajoutée avant de remplacer liftM2 par des combinateurs applicatifs. C'est aussi là que tous les espaces ont disparu.
  • Même ma version "finale" avait beaucoup de . Pour la composition des fonctions. Le remplacement de tous ceux par <$> S'est apparemment produit au cours des mois entre cela et l'encyclopédie.

BTW, voici une version mise à jour qui ne mentionne plus le nombre 2:

fix$(<$>)<$>(:)<*>((<$>((:[{- Jörð -}])<$>))(=<<)<$>(*)<$>(>>=)(+)($))$1
14
olsner