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.Function
i Control.Applicative
), ghci
wyświetla listę wszystkich potęg 2.
Jak działa ten fragment kodu?
Odpowiedzi:
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
fix
i 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 imap
niepotrzebnie 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
map
jest zbyt czytelne. Ale nie ma się czego bać: możemy wykorzystać prawa monady, aby ją nieco rozszerzyć. W szczególnościfmap f x = x >>= return . f
tzwmap 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)
źródło
{- thor's mother -}
!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.
x = 1 : map (2*) x
zacząłem od2 : map ...
i zachowałem te początkowe 2 aż do ostatniej wersji, w której wcisnąłem a(*2)
i zmieniłem$2
na końcu na$1
. Krok „uczyń mapę niepotrzebnie złożoną” nie nastąpił (tak wcześnie).map
została wprowadzona przed zastąpieniem liftM2 kombinatorami Applicative. Wtedy też zniknęły wszystkie przestrzenie..
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
źródło
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 x
a ponieważliftA2 A B C
jest stosowany dox
swojej 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 g
i 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 jakoconcat $ 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..]
źródło