JuliaでFizzBuzzいろいろ
FizzBuzzコードを幾つか提示しつつJuliaの紹介をします。
大体VERSION == v"1.8.5"
を想定しています。
今回は、関数とは?型とは?とかを気にしない縛りです。
命令型
for n in 1:30
if n % 3 == 0 && n % 5 == 0
println("FizzBuzz")
elseif n % 3 == 0
println("Fizz")
elseif n % 5 == 0
println("Buzz")
else
println(n)
end
end
Juliaらしくないスタイルでも書けるのがJuliaらしいところです。
型を意識しなくても書けます。
命令型でも書けます。
グローバルにダラダラ書けます。
数や文字列はモヤっとした概念のままでも扱えます。むしろそこが重要だったりします。
1:30
は、「1から30までの整数を順に並べたもの」を意味し、配列[1,2,...,30]
と「同じ」ものを意味します。
関数定義 と dot記法
function fizzbuzz(n)
if n % 15 == 0
"FizzBuzz"
elseif n % 3 == 0
"Fizz"
elseif n % 5 == 0
"Buzz"
else
n
end
end
println.(fizzbuzz.(1:30));
if
文は条件に合う節を評価して値を返します。1
今回返される値は、"FizzBuzz"、"Fizz"、"Buzz"のどれかか、n
の値です。
if
文がfizzbuzz
実装の最後の文なので、if
文を評価した値がfizzbuzz
関数の返す値となります。
最終行で関数名の後にドット(.
)がついているのは関数や演算子に普遍的に使用できるドット記法で、作用する相手の各要素に関数を適用します。
1:30
は配列[1,2,...,30]
に置き換えた時と同じ結果になるように扱われます。
fizzbuzz.(1:30)
は[fizzbuzz(1),fizzbuzz(2),...,fizzbuzz(30)]
と同じ。
これが評価されて[1,2,...,"FizzBuzz"]
となります。
println.(fizzbuzz.(1:30))
は[println(1),println(2),...,println("FizzBuzz")]
と同じ。
結果として、1から30までの数値をfizzbuzz
に適用した結果が順に表示されます。
ドット記法により、「xs
の要素のそれぞれにf
を適用する」ということをf.(xs)
という非常に簡潔な記法で表現できます。
この「同じ」はJuliaでは深い意味を持ちます。
5.0 / 2.0
が2.5
であることには異論はないと思います。ゆえに、
5 / 2
は2
ではなく2.5
である必要があります。関数名は単純な識別子(identifier)ではなく、作用の象徴(symbol)です。
数学的表記 と @.
マクロ
fizzbuzz(n) = let s = ""
if n % 3 == 0 s *= "Fizz" end
if n % 5 == 0 s *= "Buzz" end
s != "" ? s : n
end
@. println(fizzbuzz(1:30));
関数定義はf(x,y)=x+y
のように数学的表記もできます。
let ... end
で囲むことにより、ここではs
がlet
文の中でのみ有効な変数となっています。
今回if
文は区切りなしで書いていますが有効です。Juliaの構文解析は好意的で、一意的な解釈しかできないのであれば改行やセミコロンは必須ではありません。
三項演算子も使えます。A ? B : C
はif A B else C
と等価です。
@.
はマクロで、その続きの式を評価前に.
付きに可能な限り変換します。
ここでは1から30までの整数一つ一つに対してfizzbuzz
とprintln
を順番に適用しています。
Juliaでは「+
は交換法則は成り立つが*
は成り立たない」という立場をとっています。組込みで行列を扱っているので筋は通っています。
なので、文字列の結合を表す演算子には*
が採用されています。
文字列に関する*
の単位元は""
です(""*"abc" == "abc"*"" == "abc"
)
匿名関数 と パイプライン演算子
fizzbuzz = n ->
n % 15 == 0 ? "FizzBuzz" :
n % 3 == 0 ? "Fizz" :
n % 5 == 0 ? "Buzz" :
n
1:30 .|> fizzbuzz .|> println;
-
fizzbuzz = ...
で、fizzbuzz
という変数に関数を代入しています-
x -> ...
の形で、関数に命名せずに関数を表現できます(匿名関数)。今回は結局変数に代入してますが。- 三項演算子は右結合で繋げていけます
a ? b : c ? d : e == a ? b : (c ? d : e)
ということ。2
行が:
で終わっていると未完結なので、Juliaは続きを次の行に求めます。 -
n
のところで構文が完結します
- 三項演算子は右結合で繋げていけます
- 入れ子になった三項演算子が完結したので匿名関数の式も完結します
-
- 匿名関数の式が完結したので
fizzbuzz
への代入式も完結します
|>
はパイプライン演算子で、x |> f |> g
はg(f(x))
と等価です。
|>
にも.
をつけることができ、xs .|> f .|> g
はg.(f.(xs))
と等価になります。
パイプライン演算子により、「x
にf
を作用させ、次にg
に作用させ…」という時系列順に記述することが可能になり、また括弧の数を減らすことができます。
部分配列 と ブロードキャスト
function fizzbuzzseq(n)
output = Vector{Any}(1:n)
output[ 3: 3:end] .= "Fizz"
output[ 5: 5:end] .= "Buzz"
output[15:15:end] .= "FizzBuzz"
output
end
fizzbuzzseq(30) .|> println;
ちょっと型に触れてしまいました。
Vector{Any}
は「何でも入る一次元配列」の型です。
「1:n
の表すものをVector
形式でいい感じに表してください」とVector
さんにお願いするとVector
さんはいい感じに定義されているので[1,2,...,n]
という配列にして返してくれます。
Vector(1:n)
と書くとVector
さんは「整数限定の方が処理速度が速くて最適!」と整数以外お断りの配列を返してくれますが、Vector{Any}(1:n)
とすると不本意ながらも何でもありの配列を返してくれます。
output[3:3:end]
は「output
配列の第3要素から3つおきに最後まで抽出した配列(部分配列)」を意味します。
output[3:3:30] .= "Fizz"
という文について見てみます。
output[3:3:30]
は長さ10の一次元配列、"Fizz"なら単一要素の0次元配列であり、次元や要素数が異なっています。
そこで、"Fizz"を長さ10の一次元配列["Fizz","Fizz",...,"Fizz"]
として解釈して「同じ」形であるとみなし、その上でそれぞれの要素に=
を適用します。
それで、output[3]
、output[6]
、...に"Fizz"
が代入されます。
これはPythonのnumpyとかでお馴染みの「ブロードキャスト」です。
インストール方法
気軽に試すには、公式ページのダウンロード画面(https://julialang.org/downloads/)からWindowsなら"portable"、Macなら".dmg"と書いてあるファイルを利用するのが良いと思います。
コマンドラインに慣れていれば、juliaupという方法も同じページで提供されています。
私はMacで(asdf)https://asdf-vm.comを使用しています。
関連書籍・サイト
↑公式サイトです。
↑公式サイトのドキュメントです。
ただの仕様書ではなく、設計思想が書かれています。特にManualの前半部分は読むと理解がかなり深まります。日本語訳のサイトもいくつかあるのですが、日本語では表現しづらい言葉の係り具合があるのでできれば原文で読みたいところです。
↑しかし言葉の壁が立ちはだかっていたところ、3月に新刊が出ました。Juliaをとりあえず使い始めるところから、Juliaらしい書き方、発展的な内容にも触れており、読む価値があると思います。
続き
まだほとんどエントランスなので、続きを書く予定です。