Come funziona questo pezzo di codice Haskell offuscato?


91

Durante la lettura di https://en.unciclopedia.co/wiki/Haskell (e ignorando tutte le cose "offensive"), mi sono imbattuto nel seguente pezzo di codice offuscato:

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

Quando eseguo quel pezzo di codice in ghci(dopo l'importazione Data.Functione Control.Applicative), ghcistampa l'elenco di tutte le potenze di 2.

Come funziona questo pezzo di codice?


3
Mi chiedo se la risposta sarebbe qualcosa di arrogantemente offensivo ... se vero, ironico considerando i tuoi sforzi per evitare la volgarità.
Meredith

31
Cosa hai provato? Le cose ovvie da provare sono (a) rimuovere il commento, (b) riformattare / reindirizzare il codice, (c) capire quali istanze di Functor / Applicative / Monad vengono utilizzate (probabilmente tutte le liste, ma non dare per scontato .. . niente impedirebbe a un programmatore sufficientemente demente di usare cinque differenti istanze di Monad in una singola riga di codice), (d) semplificare il più possibile. Quindi guarda cosa ti rimane.
dave4420

10
Haskell è di gran lunga il mio linguaggio di programmazione preferito, ma ciononostante unciclopedia.wikia.com/wiki/Haskell mi ha fatto ridere così tanto!
AndrewC


5
Mi infastidisce davvero quando qualcuno trova il frammento di codice più gratuitamente criptico che riesce a trovare nel linguaggio XYZ, e poi afferma come fatto che è "virtualmente impossibile scrivere codice leggibile nel linguaggio XYZ". Ma sono solo io ...
MathematicalOrchid

Risposte:


219

Per cominciare, abbiamo la bella definizione

x = 1 : map (2*) x

che di per sé è un po 'sconvolgente se non l'hai mai visto prima. Ad ogni modo è un trucco abbastanza standard di pigrizia e ricorsione. Ora, ci libereremo della ricorsione esplicita usando fixe point-free-ify.

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

La prossima cosa che faremo è espandere la :sezione e rendere mapinutilmente complesso.

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

Bene, ora abbiamo due copie di quella costante 1. Non funzionerà mai, quindi useremo l'applicativo del lettore per deduplicarlo. Inoltre, la composizione delle funzioni è un po 'spazzatura, quindi sostituiamola con quella (<$>)che possiamo.

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

Il prossimo: quella chiamata a mapè troppo leggibile. Ma non c'è nulla da temere: possiamo usare le leggi della monade per espanderlo un po '. In particolare fmap f x = x >>= return . f, quindi

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

Possiamo point-free-ify, sostituire (.)con (<$>)e quindi aggiungere alcune sezioni spurie:

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

Sostituendo questa equazione nel nostro passaggio precedente:

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

Infine, rompi la barra spaziatrice e produci la meravigliosa equazione finale

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

4
Hai lasciato fuori {- thor's mother -}!
Simon Shine

14

Stavo scrivendo una lunga risposta con un'analisi completa dei miei log IRC degli esperimenti che hanno portato al codice finale (questo era all'inizio del 2008), ma ho accidentalmente tutto il testo :) Non è stata una grande perdita però - per il la maggior parte dell'analisi di Daniel è perfetta.

Ecco cosa ho iniziato:

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 *))

Le differenze dipendono principalmente dall'ordine in cui sono avvenuti i refactoring.

  • Invece di x = 1 : map (2*) xiniziare con 2 : map ..., e ho mantenuto quel 2 iniziale fino all'ultima versione, dove ho inserito un (*2)e ho cambiato il $2alla fine in $1. Il passaggio "rendere la mappa inutilmente complessa" non è accaduto (così presto).
  • Ho usato liftM2 invece di liftA2
  • La mapfunzione offuscata è stata inserita prima di sostituire liftM2 con combinatori applicativi. Questo è anche quando tutti gli spazi sono scomparsi.
  • Anche la mia versione "finale" aveva molte .funzioni avanzate per la composizione. La sostituzione di tutti quelli con <$>apparentemente è avvenuta qualche tempo nei mesi tra quello e l'enciclopedia.

A proposito, ecco una versione aggiornata che non menziona più il numero 2:

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

10
L'omissione della parola "cancellato" nel primo paragrafo è intenzionale? Se è così, tanto di cappello a lei, signore.
Jake Brownson

3
@JakeBrownson È un linguaggio comune di Internet , anche se non sono sicuro che sia stato apposta.
Lambda Fairy

1

Entrambe le risposte derivano lo snippet di codice offuscato dal breve originale dato di punto in bianco, ma la domanda in realtà chiede come fa il lungo codice offuscato a svolgere il suo lavoro.

Ecco come:

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]  )  =<<)

Ecco ( (\ x -> [2*x]) =<<) = (>>= (\ x -> [2*x])) = concatMap (\ x -> [2*x]) = map (2*)una funzione, quindi di nuovo <$> = .:

= 
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..]]

sono tutte le potenze di 2 , in ordine crescente.


Questo utilizza

  • A <$> B <*> C $ x = liftA2 A B C xe poiché liftA2 A B Cè applicato a xuna funzione, significa come funzione liftA2 A B C x = A (B x) (C x).
  • (f `op` g) = op f g = (f `op`) g = (`op` g) f sono le tre leggi delle sezioni operatore
  • >>=è monadic bind, e da allora (`op` g) f = op f ge i tipi sono

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

    per tipo di applicazione e sostituzione vediamo che la monade in questione è []per quale (>>= g) = concatMap g.

  • concatMap (\ x -> [2*x]) xs è semplificato come

    concat $ map (\ x -> [2*x]) 
    =
    concat $ [ [2*x] | x <- xs]
    =
             [  2*x  | x <- xs]
    =
             map (\ x ->  2*x )
  • e per definizione,

    (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..]]

    dove

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

    così che

    ((2*)^n) 1  =  ((2*) . (2*) .  ...  . (2*)) 1
                =    2*  (  2*  (  ...  (  2*   1 )...)) 
                =    2^n   ,  for n in [0..]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.