Jak działa ten zaciemniony kod Haskella?

90

Czytając https://en.uncyclopedia.co/wiki/Haskell (i ignorując wszystkie „obraźliwe” rzeczy), natknąłem się na następujący fragment zaciemnionego kodu:

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

Kiedy uruchamiam ten fragment kodu w ghci(po zaimportowaniu Data.Functioni Control.Applicative), ghciwyświetla listę wszystkich potęg 2.

Jak działa ten fragment kodu?

Alexandros
źródło
3
Zastanawiam się, czy odpowiedź byłaby czymś arystokratycznie obraźliwym ... jeśli to prawda, ironicznym, biorąc pod uwagę twoje wysiłki, aby uniknąć wulgarności.
Meredith,
31
Czego próbowałeś? Oczywistymi rzeczami do wypróbowania są (a) usunięcie komentarza, (b) przeformatowanie / przeformatowanie kodu, (c) ustalenie, które wystąpienia Functor / Applicative / Monad są używane (prawdopodobnie wszystkie listy, ale nie zakładaj ... . nic nie powstrzymałoby wystarczająco obłąkanego programisty przed użyciem pięciu różnych instancji Monady w jednej linii kodu), (d) uprościć tak bardzo, jak tylko możesz. Następnie zobacz, z czym zostało.
dave4420
10
Haskell to zdecydowanie mój ulubiony język programowania, ale mimo to niecyclopedia.wikia.com/wiki/Haskell tak bardzo mnie rozśmieszył!
AndrewC
5
Naprawdę denerwuje mnie, gdy ktoś znajduje najbardziej nieuzasadniony fragment kodu, jaki może znaleźć w języku XYZ, a następnie stwierdza, że ​​„praktycznie niemożliwe jest napisanie czytelnego kodu w języku XYZ”. Ale to tylko ja ...
MathematicalOrchid

Odpowiedzi:

218

Na początek mamy cudowną definicję

x = 1 : map (2*) x

co samo w sobie jest nieco zagmatwane, jeśli nigdy wcześniej go nie widziałeś. W każdym razie jest to dość standardowa sztuczka lenistwa i rekurencji. Teraz pozbędziemy się jawnej rekurencji przy użyciu fixi ify bez punktów.

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

Następną rzeczą, którą zamierzamy zrobić, jest rozszerzenie :sekcji i mapniepotrzebnie skomplikowane.

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

Cóż, teraz mamy dwie kopie tej stałej 1. To nigdy się nie uda, więc użyjemy aplikacji czytnika, aby to zduplikować. Również kompozycja funkcji to trochę bzdura, więc zastąpmy to (<$>)gdziekolwiek się da.

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

Dalej: to wezwanie mapjest zbyt czytelne. Ale nie ma się czego bać: możemy wykorzystać prawa monady, aby ją nieco rozszerzyć. W szczególności fmap f x = x >>= return . ftzw

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

Możemy wskazać-free-IFY wymienić (.)z (<$>), a następnie dodać kilka odcinków fałszywe:

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

Zastępując to równanie w naszym poprzednim kroku:

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

Na koniec łamiesz spację i tworzysz wspaniałe końcowe równanie

x=fix(((<$>)<$>(:)<*>((<$>((:[])<$>))(=<<)<$>(*)<$>(*2)))1)
Daniel Wagner
źródło
4
Zostawiłeś {- thor's mother -}!
Simon Shine,
13

Pisałem długą odpowiedź z pełnym przebiegiem moich dzienników IRC z eksperymentów prowadzących do ostatecznego kodu (to było na początku 2008 roku), ale przypadkowo cały tekst :) Jednak nie tak duża strata - dla w większości analizy Daniela są trafne.

Oto, od czego zacząłem:

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

Różnice sprowadzają się głównie do kolejności przeprowadzania refaktoryzacji.

  • Zamiast tego x = 1 : map (2*) xzacząłem od 2 : map ...i zachowałem te początkowe 2 aż do ostatniej wersji, w której wcisnąłem a (*2)i zmieniłem $2na końcu na $1. Krok „uczyń mapę niepotrzebnie złożoną” nie nastąpił (tak wcześnie).
  • Użyłem liftM2 zamiast liftA2
  • Funkcja zaciemniona mapzostała wprowadzona przed zastąpieniem liftM2 kombinatorami Applicative. Wtedy też zniknęły wszystkie przestrzenie.
  • Nawet moja „ostateczna” wersja miała jeszcze wiele .funkcji do komponowania. Zastąpienie tych wszystkich <$>najwyraźniej wydarzyło się jakiś czas w miesiącach między tym a niecyklopedią.

Przy okazji, oto zaktualizowana wersja, która nie wymienia już numeru 2:

fix$(<$>)<$>(:)<*>((<$>((:[{- Jörð -}])<$>))(=<<)<$>(*)<$>(>>=)(+)($))$1
olsner
źródło
10
Czy pominięcie słowa „skreślony” w pierwszym akapicie jest zamierzone? Jeśli tak, to moja głowa do pana.
Jake Brownson,
3
@JakeBrownson To popularny idiom internetowy , chociaż nie jestem też pewien, czy był to celowy.
Lambda Fairy,
1

Obie odpowiedzi wywodzą zaciemniony fragment kodu z krótkiego oryginału podanego nieoczekiwanie, ale pytanie w rzeczywistości dotyczy tego, w jaki sposób długi zaciemniony kod wykonuje swoją pracę.

Oto jak:

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

Oto ( (\ x -> [2*x]) =<<) = (>>= (\ x -> [2*x])) = concatMap (\ x -> [2*x]) = map (2*)funkcja, więc znowu <$> = .:

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

są wszystkie potęgi 2 , w porządku rosnącym.


To używa

  • A <$> B <*> C $ x = liftA2 A B C xa ponieważ liftA2 A B Cjest stosowany do xswojej funkcji, to oznacza jako funkcję liftA2 A B C x = A (B x) (C x).
  • (f `op` g) = op f g = (f `op`) g = (`op` g) f są trzema prawami sekcji operatora
  • >>=jest monadycznym wiązaniem, a ponieważ (`op` g) f = op f gi typy są

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

    według zastosowania typu i podstawienia widzimy, że dana monada jest []dla której (>>= g) = concatMap g.

  • concatMap (\ x -> [2*x]) xs jest uproszczony jako

    concat $ map (\ x -> [2*x]) 
    =
    concat $ [ [2*x] | x <- xs]
    =
             [  2*x  | x <- xs]
    =
             map (\ x ->  2*x )
    
  • iz definicji

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

    gdzie

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

    po to aby

    ((2*)^n) 1  =  ((2*) . (2*) .  ...  . (2*)) 1
                =    2*  (  2*  (  ...  (  2*   1 )...)) 
                =    2^n   ,  for n in [0..]
    
Will Ness
źródło