この難読化されたHaskellコードはどのように機能しますか?


91

https://en.uncyclopedia.co/wiki/Haskellを読んで(そして「攻撃的」なものをすべて無視して)、私は次の難読化されたコードに出会いました。

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

私はコードのその部分を実行するとghci(インポート後Data.FunctionControl.Applicative)、ghci2のすべての権限のリストを出力します。

このコードはどのように機能しますか?


3
答えは、傲慢な攻撃的なものになるのではないかと思います...真実なら、下品さを避けるためのあなたの努力を考えると皮肉なことです。
メレディス

31
何を試しましたか?試すべき明らかなことは、(a)コメントを削除する、(b)コードを再フォーマット/再インデントする、(c)Functor / Applicative / Monadのどのインスタンスが使用されているかを調べることです(おそらくすべてのリストですが、想定しないでください。)十分に認知度の高いプログラマが1行のコードでMonadの5つの異なるインスタンスを使用することを妨げるものは何もありません。(d)できる限り単純化します。次に、残っているものを確認します。
dave4420 2012

10
Haskellは私の大好きなプログラミング言語ですが、それでもuncyclopedia.wikia.com/wiki/Haskellは私をとても笑わせました!
AndrewC、2012年


5
誰かがXYZ言語で見つけることができる最も不必要に暗号化されたコードフラグメントを見つけて、「XYZ言語で読み取り可能なコードを書くことは事実上不可能」であると断言すると、それは本当に私を困らせます。しかし、それは私だけです...
MathematicalOrchid

回答:


219

まず、素敵な定義があります

x = 1 : map (2*) x

これまでに見たことがない場合、それ自体は少し心が折れます。とにかく、それは怠惰と再帰のかなり標準的なトリックです。次に、fixとpoint-free-ify を使用して、明示的な再帰を削除します。

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

次に行うことは、:セクションを拡張して、map不必要に複雑にすることです。

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

さて、今、その定数の2つのコピーがあり1ます。それは決して行われないので、リーダーのアプリケーションを使用して重複を排除します。また、関数の構成は少しごみですので、(<$>)可能な限り置き換えます。

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

mapは、その呼び出しは読みすぎです。しかし、恐れることは何もありません。モナドの法則を使用して、少し拡張することができます。具体的には、fmap f x = x >>= return . fので、

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

ポイントフリー化して、に置き換え(.)(<$>)から、いくつかの偽のセクションを追加できます。

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

前のステップでこの方程式を代入します。

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

最後に、スペースバーを壊して素晴らしい最終方程式を作り出します

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

4
あなたは除外しました{- thor's mother -}
Simon Shine

14

最終コードに至るまでの実験のIRCログを完全に実行して長い回答を書いていました(これは2008年の初めにありました)。ほとんどの部分はダニエルの分析に基づいています。

これが私が始めたものです:

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

違いは主に、リファクタリングが行われた順序に帰着します。

  • 代わりにx = 1 : map (2*) xから始め、2 : map ...最後のバージョンまで最初の2を保持しました。最後のバージョンでは、aを圧迫し、最後のをに(*2)変更し$2ました$1。「マップを不必要に複雑にする」ステップは(それ以前には)行われませんでした。
  • liftA2の代わりにliftM2を使用しました
  • mapliftM2をApplicativeコンビネーターに置き換える前に、難読化された関数が組み込まれました。それはすべてのスペースが消えた時でもあります。
  • 私の「最終」バージョンでさえ、.機能構成のための多くが残っていました。それらのすべてを置き換えることは、<$>明らかにそれとアンサイクロペディアの間の数ヶ月の間に起こったようです。

ところで、これは数をもう言及しない更新されたバージョンです2

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

10
最初の段落の「削除」という単語の省略は意図的なものですか?もしそうなら、あなたに私の帽子を脱いでください。
Jake Brownson、2014

3
@JakeBrownsonこれは一般的なインターネットイディオムですが、意図的なものかどうかもわかりません。
ラムダフェアリー

1

どちらの回答も難読化されたコードスニペットをすぐに元の短いオリジナルから取得しますが、質問では実際に、長い難読化されたコードがどのように機能するかを尋ねます。

方法は次のとおりです。

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

ここに( (\ x -> [2*x]) =<<) = (>>= (\ x -> [2*x])) = concatMap (\ x -> [2*x]) = map (2*)関数があるので、もう一度、<$> = .

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

2のすべての累乗であり、昇順です。


これは

  • A <$> B <*> C $ x = liftA2 A B C xそして以降liftA2 A B Cに適用されx、それの機能は、それが意味機能として、ですliftA2 A B C x = A (B x) (C x)
  • (f `op` g) = op f g = (f `op`) g = (`op` g) f オペレーターセクションの3つの法則
  • >>=はモナドバインドであり、それ以降(`op` g) f = op f g、型は

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

    タイプの適用と置換によって、問題のモナドが[]どちらのものであるかがわかり(>>= g) = concatMap gます。

  • concatMap (\ x -> [2*x]) xs として単純化されます

    concat $ map (\ x -> [2*x]) 
    =
    concat $ [ [2*x] | x <- xs]
    =
             [  2*x  | x <- xs]
    =
             map (\ x ->  2*x )
    
  • そして当然のことながら

    (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 _______/
    

    そのため

    ((2*)^n) 1  =  ((2*) . (2*) .  ...  . (2*)) 1
                =    2*  (  2*  (  ...  (  2*   1 )...)) 
                =    2^n   ,  for n in [0..]
    
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.