プログラミングは文学である。1つのアルゴリズムにいろんな表現の仕方があり、そこに芸術性を見出すことが出来る。
しかし芸術とはゴミを作ることでもあるのだ。
なにこれ
アルケミストの遊びです。ここでは敢えていろんなゴミを錬金してみましょう。
基本的に真似しちゃダメですが、「こんな書き方もできるのか」とか思って調べてみるきっかけになればと思います。
1. if
の濫用
defmodule FizzBuzz do
def say(n) do
if Integer.mod(n, 15) == 0 do
"FizzBuzz"
else
if Integer.mod(n, 3) == 0 do
"Fizz"
else
if Integer.mod(n, 5) == 0 do
"Buzz"
else
n
end
end
end
end
end
for n <- 1..30 do
IO.puts FizzBuzz.say(n)
end
Elixir で条件分岐には if
はあんまり使いません。Non-alchemist が Elixir 書くと稀によくこうなる。
2. cond
を使う
defmodule FizzBuzz do
def say(n) do
cond do
Integer.mod(n, 15) == 0 -> "FizzBuzz"
Integer.mod(n, 3) == 0 -> "Fizz"
Integer.mod(n, 5) == 0 -> "Buzz"
:else -> n
end
end
end
for n <- 1..30 do
IO.puts FizzBuzz.say(n)
end
まあギリギリ及第点いくかいかないかくらいですかね。 :else
がチャームポイント。
3. case
で guard に rem
を使う
defmodule FizzBuzz do
def say(n) do
case n do
n when rem(n, 15) == 0 -> "FizzBuzz"
n when rem(n, 3) == 0 -> "Fizz"
n when rem(n, 5) == 0 -> "Buzz"
n -> n
end
end
end
for n <- 1..30 do
IO.puts FizzBuzz.say(n)
end
結構Elixirっぽくなってきました。ですがこれは冗長です。
(今回 n
は pos_integer
なので、Integer.mod/2
と rem/2
の違いは問題になりません)
4. 複数句による関数定義でパターンマッチ
defmodule FizzBuzz do
def say(n) when rem(n, 15) == 0, do: "FizzBuzz"
def say(n) when rem(n, 3) == 0, do: "Fizz"
def say(n) when rem(n, 5) == 0, do: "Buzz"
def say(n), do: n
end
for n <- 1..30 do
IO.puts FizzBuzz.say(n)
end
最後の def
をalignすべきかどうかが迷いどころ。
5. for
ではなく Enum.each/2
defmodule FizzBuzz do
def say(n) when rem(n, 15) == 0, do: "FizzBuzz"
def say(n) when rem(n, 3) == 0, do: "Fizz"
def say(n) when rem(n, 5) == 0, do: "Buzz"
def say(n), do: n
end
Enum.each(1..30, fn n ->
IO.puts FizzBuzz.say(n)
end)
別に30個の :ok
を並べたいわけではないので Enum.each/2
を使います。
小休止
ここまで来てやっとElixirとしてまともなコードになったような気がします。
此処からが本番ですよ。
6. defmodule
しない
fizz_buzz = fn
n when rem(n, 15) == 0 -> "FizzBuzz"
n when rem(n, 3) == 0 -> "Fizz"
n when rem(n, 5) == 0 -> "Buzz"
n -> n
end
Enum.each(1..30, fn n ->
IO.puts fizz_buzz.(n)
end)
fn
の引数部でもパターンマッチが出来て、複数の枝を扱うことが出来ます。
fn
で作った関数を呼ぶときは fizz_buzz.(n)
というふうに .
が必要になります。
7. 複数句による関数定義でたらい回す
defmodule FizzBuzz do
def say(n), do: say(n, rem(n, 3))
def say(n, 0), do: say(n, "Fizz", rem(n, 5))
def say(n, _), do: say(n, "", rem(n, 5))
def say(_, fizz, 0), do: fizz <> "Buzz"
def say(n, "", _), do: n
def say(_, fizz, _), do: fizz
end
Enum.each(1..30, fn n ->
IO.puts FizzBuzz.say(n)
end)
なにも全部同じ名前にしなくても…とは思いますが、きっと私なりの美学がそこにあるのでしょう。
8. Stream.unfold/2
を使う
Stream.unfold({1, 1, 1}, fn
{n, 3, 5} -> {"FizzBuzz", {n + 1, 1, 1 }}
{n, 3, b} -> {"Fizz", {n + 1, 1, b + 1}}
{n, f, 5} -> {"Buzz", {n + 1, f + 1, 1 }}
{n, f, b} -> {n, {n + 1, f + 1, b + 1}}
end) \
|> Stream.take(30) \
|> Enum.each(&IO.puts/1)
(そのままiexにコピペして実行できるように \
を入れてます)
ガラッとアプローチを変えて、FizzBuzzストリームを作るという方針にしてます。{n, f, b}
がaccumulatorになっていて、3つとも1ずつ増やしてf
が3
になったら"Fizz"
、b
が5
になったら"Buzz"
、という具合です。
Enum.take/2
ではなく Stream.take/2
を使ってるのもポイント。
9. Eitherっぽいものを作って香ばしく
# Not Either but chemistrical
defmodule Ether do
defstruct [
which: :left,
value: nil,
]
@type t :: %__MODULE__{
which: :right | :left,
value: any,
}
def right(val), do: %__MODULE__{which: :right, value: val}
def left(val), do: %__MODULE__{which: :left, value: val}
def lift2(
fun,
%__MODULE__{which: :right, value: v1},
%__MODULE__{which: :right, value: v2}
), do: %__MODULE__{which: :right, value: fun.(v1, v2)}
def lift2(
_,
%__MODULE__{which: :right, value: v1},
%__MODULE__{which: :left}
), do: %__MODULE__{which: :left, value: v1}
def lift2(
_,
%__MODULE__{which: :left},
%__MODULE__{which: :right, value: v2}
), do: %__MODULE__{which: :left, value: v2}
def lift2(
_,
%__MODULE__{which: :left, value: v1},
%__MODULE__{which: :left}
), do: %__MODULE__{which: :left, value: v1}
end
defimpl String.Chars, for: Ether do
def to_string(%Ether{value: val}), do: Kernel.to_string(val)
end
defmodule FizzBuzz do
def say(n) do
Ether.lift2(&<>/2, fizz(n), buzz(n))
end
defp fizz(n) when rem(n, 3) == 0, do: Ether.right("Fizz")
defp fizz(n), do: Ether.left(n)
defp buzz(n) when rem(n, 5) == 0, do: Ether.right("Buzz")
defp buzz(n), do: Ether.left(n)
end
Enum.each(1..30, fn n ->
IO.puts FizzBuzz.say(n)
end)
lift2
が本当に lift2
になってるかはちょっと怪しいです。どこで改行するかも悩みどころ。
IO.puts/1
に直接渡すために String.Chars
プロトコルを実装しています。そのために defstruct
。
前半はただの準備なので、defmodule FizzBuzz do
からが実際のコードと思うと結構きれいに見えます(断定)。
10. (それっぽいタイトル)
# あなたの錬金したゴミ、あるいはアート