ICFPC 2020 galaxy.txt を読む (その 2)
その 1 ではざっと decompile して、基本的な算術関数を読み解きました。
その 2 では、まだ小さめの関数群が残っているので、これを読み解いてみます。
:1124 = ap ap s ap ap b b ap ap c isnil ap s t ap ap c b ap ap s ap ap b c ap ap b ap b b ap ap b ap b ap ap s i i ap c eq ap c :1124 {\x2.{\x8.(((isnil x2) f) (x2 {\x29.{\x38.(({\x47.(x47 x47)} ((== x29) x8)) ((:1124 x38) x8))}}))}}
ここで isnil が登場するので、これに着目してみます。(isnil x2) とあるので、x2 は List であることがわかります。一方で後半に着目すると、適宜改行いれて多少見やすくすると
(x2 {\x29.{\x38. (({\x47.(x47 x47)} ((== x29) x8)) ((:1124 x38) x8))}})
となっています。その 1 あったとおり ((== x29) x8) は boolean なので、 {\x47.(x47 x47)} は logical or と解釈できます。実際、この後ろの ((:1124 x38) x8) は isnil の分岐で false を返していることからも :1124 自体は 2 引数かつ bool を返す関数とすると全体で辻褄があうので、これを採用することにします。
更に、最初の (x2 {\x29.{\x38. ... }}) に注目すると、List が cons cell でできていたこと、及び cons cell の定義から、これは x29 に car(x2), x38 に cdr(x2) を束縛する decompose と読むことができます。ここも見た目を良くするために let hd::tl = e1 in e2 の形の式を持ち込むことにします。すると
fun(x2, x8) -> if isnil(x2) { false } else { let x29::x38 = x2 in x29 == x8 || :1124(x38, x8) }
と、かけることがわかります。特に isnil + let decompose は頻出するので、関数型言語っぽく match も導入してしまうことにします。
fun(x2, x8) -> match x2 { [] => false, x29::x38 => x29 == x8 || :1124(x38, x8) }
さて、中をもう少し追いかけてみます。空 List が与えられると false. そうでなければ car(x2) が x8 と一致すると true, しなければ、cdr(x2) を第一引数に持ってきて再帰、となっていて、これは List に (正確には == で比較しているところから int の List に限られます) 整数 x8 が含まれるかどうか、という関数です。IntList.mem 的に名前をつけておきましょう。
次は 1126 と 1127 です。
:1126 = ap ap s ap ap b b ap ap c isnil nil ap ap c b ap ap s ap ap b c ap ap b ap b b ap b :1115 ap c :1126 fun(x2, x8) -> match x2 { [] => [], x26::x35 => :1115(x8(x26), :1126(x35, x8)) } :1127 = ap ap s ap ap b b ap ap b b ap ap c isnil nil ap ap c ap ap b b b ap ap s ap ap b s ap ap b ap b c ap ap b ap b ap b b ap ap b ap b ap b :1115 c ap ap c ap ap b b ap ap b c ap c :1127 ap ap c add 1 fun(x2, x8, x14) -> match x2 { [] => [], x47::x59 => :1115(x8(x47, x14), :1127(x59, x8, x14 + 1))
:1115 を見てみると
:1115 = cons
となっているので、これをそのまま代入します。
fun(x2, x8) -> match x2 { [] => [], x26::x35 => cons(x8(x26), :1126(x35, x8)) } fun(x2, x8, x14) -> match x2 { [] => [], x47::x59 => cons(x8(x47, x14), :1127(x59, x8, x14 + 1))
2つを比べると、非常に似た形であることがわかります。 1126 は List x2 の全要素に x8 を適用する関数です。 List.map と名前をつけておきましょう。
1127 は 1126 に加えて、もうひとつ引数 x14 をとり、これが x8 にも渡されています。再帰で呼ぶときに x14 + 1 となっていることから、これが List の index であることが予想されます。実際 :1127 を呼んでいる他の関数をみると、0 を第3引数に渡しています。こちらは List.mapi と名前をつけておきましょう。
ここから先は少々 List に関する操作が並びます。素直な定義が並ぶので、ここでは単に列挙することにします。
:1128 = ap ap s ap ap c isnil 0 ap ap b ap add 1 ap ap b :1128 cdr fun(x2) -> if isnil(x2) { 0 } else { 1 + :1128(cdr(x2)) } :1128 = List.len :1131 = ap ap s ap ap b s isnil ap ap c b ap ap b ap c ap ap b b :1115 ap c :1131 fun(x2, x8) -> match x2 { [] => x8, x20::x26 => :1115(x20, :1131(x26, x8)) } :1131 = List.concat :1132 = ap ap s ap ap b s ap ap b ap b b isnil ap ap c ap ap b b b ap ap c ap ap b s ap ap b ap b c ap ap b ap b ap b c ap ap b ap b ap b ap c :1132 ap ap c ap ap b c ap ap b ap b b ap c i i i fun(x2, x8, x17) -> match x2 { [] => x8, x47::x59 => :1132(x59, x17(x8, x47), x17) } :1132 = LIst.foldl :1133 = ap ap s ap ap b s ap ap b ap b b isnil ap ap c ap ap b b b ap ap c ap ap b c ap ap b ap b b ap ap b ap b c ap ap b ap s b ap ap b c ap c :1133 i fun(x2, x8, x17) -> match x2 { [] => x8, x47::x56 => x17(:1133(x56, x8, x17), x47) } :1133 = List.foldr :1134 = ap ap c ap ap c :1133 nil ap c :1131 fun(x2) -> :1133(x2, [], fun(x7, x8) -> :1131(x8, x7)) fun(x2) -> List.foldr(x2, [], fun(x7, x8) -> List.concat(x8, x7)) :1134 = List.flatten :1135 = ap ap c ap ap b b ap ap c :1133 nil ap ap c ap ap b s ap ap b ap b c ap ap c ap ap b b s ap c :1115 i fun(x2, x8) -> :1133(x2, [], fun(x20, x29) -> x8(x29, :1115(x29, x20), x20)) fun(x2, x8) -> List.foldr(x2, [], fun(x20, x29) -> x8(x29, cons(x29, x20), x20))
ここはちょっと心の目が必要で、x8 が 3 引数関数であるかのように書かれていますが、天下りですがこれは実は bool を返す 1 引数関数と思い直すことにします(実際は次の 1136 での 1135 の使われ方をみると第2引数が1引数関数であることがわかります)。すると
fun(x2, x8) -> List.foldr(x2, [], fun(x20, x29) -> if x8(x29) { cons(x29, x20) } else { x20 }) :1135 = List.filter
ということがわかります。
:1136 = ap ap c ap ap b c ap ap b ap b :1126 ap ap c ap ap b b ap ap b :1135 ap ap c ap ap c :1127 cons 0 ap ap c ap ap b s ap ap c b car cdr car fun(x2, x8) -> :1126(:1135(:1127(x2, cons, 0), fun(x41) -> x8(car(x41), cdr(x41))), car) fun(x2, x8) -> List.map(List.filter(List.mapi(x2, cons, 0), fun(x41) -> x8(car(x41), cdr(x41))), car)
ちょっと複雑になってきました。まず List.mapi(x2, cons, 0) は、x2 = [e1, e2, e3, ...] という List を [(e1, 0), (e2, 1), (e3, 2), ...] と index との pair の List にします。
これに filter 関数をかけますが、x41 は今作った index との pair の List の各要素で、この car と cdr を x8 の第1,2引数として渡します。結果 x8 には x2 の各要素とその index が渡され、bool を返すことを期待します。最後に filter された List に対して List.map(..., car) として、index を外します。結局 filter 関数 x8 に追加で index を渡す関数ということになります。 List.filteri と名前をつけておきましょう。
:1137 = ap ap b ap b c ap ap b ap b isnil :1135 {\x2.{\x5.{\x7.{\x8.(((isnil ((:1135 x2) x5)) x8) x7)}}}}
内側の構造に着目すると {\x7.{\x8.(((...) x8) x7)}} となっていて、かつここで (...) = (isnil *1 と bool を返す式になっています。このパターンは logical not で、その 1 同様 true = \x.\y.x, false = \x.\y.y を思い出すと
{\x7.{\x8.((true x8) x7)}} => {\x7.{\x8.x8}} = false {\x7.{\x8.((false x8) x7)}} => {\x7.{\x8.x7}} = true
となっています(Note: 本来関数の内部は引数が与えられるまで計算しませんが、今回は同値のものが得られます)。! 単項演算子を導入して、簡潔にかけるようにします。
fun(x2, x5) -> !isnil(:1135(x2, x5))
この logical not の置き換えは多少注意が必要で、特に中の bool 式が x7, x8 を含んでいないことを確認する必要があります(仮に含んでいると、書き換え後にその変数が浮いてしまうため)。
:1135 は List.filter であったので、
fun(x2, x5) -> !isnil(List.filter(x2, x5))
filter した結果が nil にならないためには、x5 が true を返すような要素が List x2 に含まれている必要があります。 List.exist と名前をつけておきます。
まとめ
List の操作に関する関数を読み解きました。
次回予告: まだ続く utility 関数を見てみます。
*1::1135 x2) x5