Comment fonctionne ce morceau de code Haskell obscurci?

91

En lisant https://en.uncyclopedia.co/wiki/Haskell (et en ignorant tous les trucs "offensants"), 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 l'importation Data.Functionet Control.Applicative), ghciimprime la liste de toutes les puissances de 2.

Comment fonctionne ce morceau de code?

Alexandros
la source
3
Je me demande si la réponse serait quelque chose d'offensif orgueilleux ... si c'est vrai, ironique compte tenu de vos efforts pour éviter la vulgarité.
Meredith
31
Qu'avez-vous essayé? Les choses évidentes à essayer sont (a) supprimer le commentaire, (b) reformater / réindenter le code, (c) déterminer quelles instances de Functor / Applicative / Monad sont utilisées (probablement toutes les listes, mais ne supposez pas ... Rien n'empêcherait un programmeur suffisamment dément d'utiliser cinq instances différentes de Monad dans une seule ligne de code), (d) simplifier autant que vous le pouvez. Ensuite, voyez ce qu'il vous reste.
dave4420
10
Haskell est de loin mon langage de programmation préféré, mais néanmoins uncyclopedia.wikia.com/wiki/Haskell m'a fait tellement rire!
AndrewC
5
Cela me dérange vraiment quand quelqu'un trouve le fragment de code le plus gratuit et cryptique qu'il puisse trouver dans le langage XYZ, et affirme ensuite comme fait qu'il est "pratiquement impossible d'écrire du code lisible dans le langage XYZ". Mais c'est juste moi ...
MathematicalOrchid

Réponses:

219

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 un truc assez classique 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'élargir la :section et de rendre la partie mapinutilement complexe.

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

Eh bien, nous avons maintenant deux copies de cette constante 1. Cela ne fonctionnera jamais, nous utiliserons donc l'applicatif du lecteur pour dédupliquer cela. De plus, la composition des fonctions est un peu nul, alors remplaçons cela par (<$>)là où nous le pouvons.

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

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

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

Nous pouvons sans point-ify, remplacer (.)par (<$>), puis ajouter 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)
Daniel Wagner
la source
4
Vous avez laissé de côté {- thor's mother -}!
Simon Shine
14

J'écrivais une longue réponse avec un aperçu complet de mes journaux IRC des expériences menant au code final (c'était au début de 2008), mais j'ai accidentellement tout le texte :) Pas tellement de perte cependant - pour le l'analyse de Daniel est en grande partie parfaite.

Voici par quoi j'ai commencé:

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 refactorisations ont eu lieu.

  • Au lieu de cela, x = 1 : map (2*) xj'ai commencé par 2 : map ..., et j'ai gardé ce 2 initial jusqu'à la toute dernière version, où j'ai inséré un (*2)et changé le $2à la fin en $1. L'étape «rendre la carte inutilement complexe» n'a pas eu lieu (si tôt).
  • J'ai utilisé liftM2 au lieu de liftA2
  • La mapfonction masquée a été mise en place 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 .composition de fonction restante. Le remplacement de tous ceux par <$>apparemment s'est produit quelque temps dans les mois entre cela et l'encyclopédie.

BTW, voici une version mise à jour qui ne mentionne plus le numéro 2:

fix$(<$>)<$>(:)<*>((<$>((:[{- Jörð -}])<$>))(=<<)<$>(*)<$>(>>=)(+)($))$1
Olsner
la source
10
L'omission du mot «supprimé» dans le premier paragraphe est-elle intentionnelle? Si oui, mon chapeau à vous monsieur.
Jake Brownson
3
@JakeBrownson C'est un idiome Internet courant , même si je ne suis pas sûr non plus si c'était exprès.
Lambda Fairy
1

Les deux réponses dérivent l'extrait de code obscurci du court original donné à l'improviste, mais la question demande en fait comment le long code obscurci fait son travail.

Voici comment:

fix$(<$>)<$>(:)<*>((<$>((:[{- thor's mother -}])<$>))(=<<)<$>(*)<$>(*2))$1 
= {- add spaces, remove comment -}
fix $ (<$>) <$> (:) <*> ( (<$> ((:[]) <$>) ) (=<<)  <$>  (*)  <$>  (*2) ) $ 1 
--                      \__\______________/_____________________________/
= {-    A   <$> B   <*> C                          $ x   =   A (B x) (C x) -}
fix $ (<$>) (1 :)     ( ( (<$> ((:[]) <$>) ) (=<<)  <$>  (*)  <$>  (*2) ) 1 )
--                      \__\______________/____________________________/
= {- op f g = (f `op` g) ; (`op` g) f = (f `op` g) -}
fix $ (1 :) <$>  ( (((=<<) <$> ((:[]) <$>) )        <$>  (*)  <$>  (*2) ) 1 )
--                  \\____________________/____________________________/
= {- <$> is left associative anyway -}
fix $ (1 :) <$>  ( ( (=<<) <$> ((:[]) <$>)          <$>  (*)  <$>  (*2) ) 1 )
--                  \__________________________________________________/
= {- A <$> foo = A . foo when foo is a function -}
fix $ (1 :) <$>  ( ( (=<<) <$> ((:[]) <$>)           .   (*)   .   (*2) ) 1 )
--                  \__________________________________________________/
= {- ((:[]) <$>) = (<$>) (:[]) = fmap (:[])  is a function -}
fix $ (1 :) <$>  ( ( (=<<)  .  ((:[]) <$>)           .   (*)   .   (*2) ) 1 )
--                  \__________________________________________________/
= {- (a . b . c . d) x = a (b (c (d x))) -}
fix $ (1 :) <$>      (=<<)  (  ((:[]) <$>)           (   (*)   (   (*2)   1 )))
= {- (`op` y) x = (x `op` y) -}
fix $ (1 :) <$>      (=<<)  (  ((:[]) <$>)           (   (*)   2             ))
= {- op x = (x `op`) -}
fix $ (1 :) <$>      (=<<)  (  ((:[]) <$>)              (2*)                  )
= {-  (f `op`) g = (f `op` g) -}
fix $ (1 :) <$>      (=<<)  (   (:[]) <$>               (2*)                  )
= {-  A <$> foo = A . foo when foo is a function -}
fix $ (1 :) <$>      (=<<)  (   (:[])  .                (2*)                  )
= {-  (f . g) = (\ x -> f (g x)) -}
fix $ (1 :) <$>      (=<<)  (\ x -> [2*x]  )
= {- op f = (f `op`)  -}
fix $ (1 :) <$>           ( (\ x -> [2*x]  )  =<<)

Voici ( (\ x -> [2*x]) =<<) = (>>= (\ x -> [2*x])) = concatMap (\ x -> [2*x]) = map (2*)une fonction, donc encore une fois <$> = .:

= 
fix $ (1 :)  .  map (2*)
= {- substitute the definition of fix -}
let xs = (1 :) . map (2*) $ xs in xs
=
let xs = 1 : [ 2*x | x <- xs] in xs
= {- xs = 1 : ys -}
let ys =     [ 2*x | x <- 1:ys] in 1:ys
= {- ys = 2 : zs -}
let zs =     [ 2*x | x <- 2:zs] in 1:2:zs
= {- zs = 4 : ws -}
let ws =     [ 2*x | x <- 4:ws] in 1:2:4:ws
=
iterate (2*) 1
= 
[2^n | n <- [0..]]

sont toutes les puissances de 2 , par ordre croissant.


Cela utilise

  • A <$> B <*> C $ x = liftA2 A B C xet puisque liftA2 A B Cc'est appliqué à xc'est une fonction, une fonction que cela signifie liftA2 A B C x = A (B x) (C x).
  • (f `op` g) = op f g = (f `op`) g = (`op` g) f sont les trois lois des sections opérateur
  • >>=est une liaison monadique, et depuis (`op` g) f = op f get les types sont

    (>>=)                :: Monad m => m a -> (a -> m b ) -> m b
    (\ x -> [2*x])       :: Num t   =>         t -> [ t]
    (>>= (\ x -> [2*x])) :: Num t   => [ t]               -> [ t]

    par type application et substitution on voit que la monade en question est []pour qui (>>= g) = concatMap g.

  • concatMap (\ x -> [2*x]) xs est simplifié comme

    concat $ map (\ x -> [2*x]) 
    =
    concat $ [ [2*x] | x <- xs]
    =
             [  2*x  | x <- xs]
    =
             map (\ x ->  2*x )
  • et par définition,

    (f . g) x  =  f (g x)
    
    fix f  =  let x = f x in x
    
    iterate f x  =  x : iterate f (f x)
                 =  x : let y = f x in 
                        y : iterate f (f y)
                 =  x : let y = f x in 
                        y : let z = f y in 
                            z : iterate f (f z)
                 = ...
                 = [ (f^n) x | n <- [0..]]

            f^n  =  f  .  f  .  ...  . f
            --     \_____n_times _______/

    pour que

    ((2*)^n) 1  =  ((2*) . (2*) .  ...  . (2*)) 1
                =    2*  (  2*  (  ...  (  2*   1 )...)) 
                =    2^n   ,  for n in [0..]
Will Ness
la source