見出し画像

Retro-gaming and so on

関数型言語脳テスト: 回答例

さて、以前挙げたこの問題の回答例を紹介しようと思う。
その前に一応、問題自体を再録しておこうか。

画像のような部分文字列を,std::vector<std::string> vec にどんどん代入するプログラムを書いてください。つまり,vec[0] は "s" で,vec[1] は "sa" になってほしいです。
なお,どんな文字列でもうまくいくようにしてください。


基本的にこのテの問題で関数型言語の人間は全く悩まないだろう。
そもそも「出力しなさい」と言う指定がどうでも良い、って思ってる。
C系の人間は「とにかく何でも出力せなアカン」と言う思い込み(より正確に言うと、出力せんと何も見えない言語なんで)で、

「ルーピングして出力するには・・・」

とか考えちゃうわけだが、関数型言語な脳味噌の人間から言わせると、これは単なる「データ生成」の問題だ。
要するに、この問題は「出力せよ」ではなくって、例に従うと、

["s", "sa", "sai", "saik", "saiko", "saikoc", "saikoca", "saikocar"]

と言うカンジの文字列のリストなりコンテナを生成しろ、と言う問題になる。このカタチのデータを生成出来れば出力はあとでどーにでもなるのだ。
C言語脳と関数型言語脳の考え方の最大の違いは恐らくこの辺だろう。C言語脳はとにかく「出力が目的」だと刷り込まれてるんだけど、関数型言語脳にとっては「出力は手段であって目的ではない」。ここが決定的に違うんだ。
だからPythonの初心者向けテキストで「何が何でも出力する」ように導こうとするC言語脳の考え方は害毒でしかない、と言ってるわけだ。彼らは「何の為にプログラミングをするのか」勘違いしている。
そしてそんなヤツが初心者用教科書を書くべきじゃないんだ。

さて、文字列Stringに対して、その一部をSubStringと言ったりする事がある。
例えば文字列"saikocar"に対して"s"、 "sa"、 "sai"、 "saik"、 "saiko"、 "saikoc"、"saikoca"は全部SubStringだ。そして文字列の先頭からSubStringを生成していって、文字列そのものになるまで幾つあるのか?と言う問いに関しても答えは簡単だろう。「文字数に一致する」が答えだ。
ここまで情報があれば、関数substringと言うまんまそのまんまの組み込み関数を持つRacketだと、ハナクソをほじりながら3秒くらいで次のような関数をでっち上げて、この問題にケリを付ける事となるだろう。

(define (foo str)
 (map (lambda (x)
    (substring str 0 x)) (range 1 (add1 (string-length str)))))

これで終わり、だ。予告どおり、3行程度のプログラムになってる。

> (foo "saikocar")
'("s" "sa" "sai" "saik" "saiko" "saikoc" "saikoca" "saikocar")
>

問題に準じたリストが生成されてるんで御の字である。他に余計な事をする必要もない。
「え?出力は?」とか言うC言語脳の人間もまだいるかもしれんが、「そんなモンどーにでもなる」としか言いようがないんだ。
例えばインタプリタで、

> (for-each (lambda (x) (display x) (newline)) (foo "saikocar"))

とかすればいいだけ、なんでわざわざ出力目当てにプログラムに仕込むような「無駄」をしなくていいんだよ。

> (for-each (lambda (x) (display x) (newline)) (foo "saikocar"))
s
sa
sai
saik
saiko
saikoc
saikoca
saikocar
>

繰り返すが関数型言語の人間は「出力がー」とか頑張んない。
「出力がー」と頑張るのはC言語脳の人間「だけ」って言って良く、それはプログラミングを学びはじめた段階から「printf塗れだった」と言う悪習の弊害でしかないんだ。

PythonにはRacketのsubstringにあたる関数がない。ただし、文字列をスライス出来る。例えば以下のように、だ。

>>> "saikocar"[0:3]
'sai'
>>>

従って、Pythonだと同じ関数fooは例によってリスト内包表記を使って次のように書ける。

def foo(s):
 return [s[0:i] for i in range(1, len(s) + 1)]

これも予告通り、二行程度で済む。
C言語脳がPythonを使った場合、forで回したり、printを仕込もうとするだろうが、全然そんな必要はない。これも「出力は後で考えれば」良く、チンタラループを書く必要なんかないんだ。
「リスト内包表記は分かりづらい」なんつーのもC言語脳の人間だけ、だ。

>>> [print("{}".format(i)) for i in foo(s)]
s
sa
sai
saik
saiko
saikoc
saikoca
saikocar
[None, None, None, None, None, None, None, None]
>>>

最後にリスト内包表記の返り値になるNoneだらけのリストが返るが無視して良い。インタプリタだと丸見えだけど、端末で走らせるとこれは表示はされないだろう。

JavaScriptだと恐らく次のようになるだろう。

function foo(str) {
 let range = n => [...Array(n).keys()];
 return range(str.length).map(function (value){
  return str.substring(0, value + 1)
 })
}

JavaScriptにはPythonで言うトコのrange関数がないようで、ローカル関数としてでっち上げないとならないし、ビミョーにオブジェクト指向が混ざってるので、関数型言語的な目では見た目が「ウゲ」と言いそうになるが、一応、さすがSchemeから生まれただけあって、関数型言語発想で書ける。
なお、JavaScript(と言うか正確にはEcmaScript)には出力がないので、これ以上は立ち入らない。あとはブラウザのAPIとか別の種の話になる。

Rubyの場合は、コードがJavaScriptとPythonの間の子のように見える。
「リスト内包表記」を持たないRubyだが、代わりにブロック(ぶっちゃけ、ラムダ式の構文糖衣だ)があって、やたら短く書け、結果行数的にはPythonといい勝負になる。

def foo(str)
 (1..str.length).map {|x| str[0, x]}
end

Rubyの場合、どうしてもendがあるんで、そういう意味では行数的には不利になるが、それでも言語のパワー自体は関数型言語のそれに見える(いや、実際はPythonよりも「キチンとした」オブジェクト指向言語だけど)。
出力も、

> puts foo("saikocar")

と後回しに出来るのがLisp系言語やPythonと同じだ。

s
sa
sai
saik
saiko
saikoc
saikoca
saikocar
>Exit code: 0

とまぁ、mapやラムダ式、あるいはそれらに類するモノがあれば異様に短く書ける、と言う例だ。

なお、オリジナルのC++だとテンプレートを使えば多分こんなカンジで書く事になる。



「関数型プログラミング的発想」で書けるは書けるが、やっぱり記述要素が多くなる、と言うのがC++のテンプレートを使った「関数型プログラミング」になる。
  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「プログラミング」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事