Quil で関数型プログラミング4:楽しいモード fun-mode?
Clojure でのクリエイティブ・コーディング環境 Quil を使って関数型プログラミングを学んでみようというこのシリーズ。
前回はループを使わずにサインカーブを描いてみました。
前回の記事はこちら。
今回は Quil コード特有の部分を少し見ていきましょう。
Clojure にも Quil にもド素人な著者が書くこの記事、勘違いや誤りに彩られていること間違いなし!
その誤りを見抜くことがあなたのスキルを伸ばしていくと肯定的に捉えてください。
みんな使ってる”楽しいモード”?
ブラウザ上で Quil を実行できる http://quil.info/ にはサンプルコードが沢山紹介されています。
これらを見てみるとコード中に
:middleware [m/fun-mode]
というのがよく出てきます。
コードを書く画面で出てくる、円が回転するおなじみのサンプルコードにも入ってます。
(ns my.core
(:require [quil.core :as q :include-macros true]
[quil.middleware :as m]))
(defn setup []
(q/frame-rate 30)
(q/color-mode :hsb)
{:color 0
:angle 0})
(defn update-state [state]
(let [{:keys [color angle]} state]
{:color (mod (+ color 0.7) 255)
:angle (mod (+ angle 0.1) q/TWO-PI)}))
(defn draw-state [state]
(q/background 240)
(q/fill (:color state) 255 255)
(let [angle (:angle state)
x (* 150 (q/cos angle))
y (* 150 (q/sin angle))]
(q/with-translation [(/ (q/width) 2)
(/ (q/height) 2)]
(q/ellipse x y 100 100))))
(q/defsketch my
:host "host"
:size [500 500]
:setup setup
:update update-state
:draw draw-state
:middleware [m/fun-mode])
みんな使ってる fun-mode。これって一体何なのでしょう?
関数型のスタイルを Quil にもたらすもの?
fun-mode の説明はこちらにありました。
Functional mode (fun mode)
楽しいの Fun じゃなくて、関数の fun だったんですね。
読んでみると『関数型のスタイルを Quil にもたらすもの』なのだそう。
え? fun-mode を使わずに書いたら関数型じゃないってこと!?
素の Quil では「状態」を扱うための特別な方法は用意されていなかったので、draw 関数内で atom を用いるなどして「状態」を扱わなければいけなかった。
fun-mode は「状態」を明示的にし、この問題を解消する。
ふ~む、なんだか便利なもののようだけど、元の不便な方法をまず体験してみないと有り難みがわからないですよね。
まずは fun-mode を使わず、let で値を変えるものを何か書いてみましょう。
(ns my.core
(:require [quil.core :as q :include-macros true]))
(defn setup []
(q/frame-rate 30)
(q/color-mode :hsb 360 100 100 100)
(q/stroke-weight 50.0))
(defn draw []
(let
[ehue (mod (q/frame-count) 360)
esat (mod (q/frame-count) 100)]
(q/background 0 0 0 100)
(q/stroke ehue 80 60 100)
(q/fill ehue esat 60 100)
(q/ellipse 250 250 200 200)))
(q/defsketch my
:host "host"
:size [500 500]
:setup setup
:draw draw)
draw 関数の中の let で ehue と esat の値を定義していますが、式に frame-count を使うことで値を変化させています。
frame-count を元に組み立てなければいけなくて、ちょっと窮屈な感じですね。
atom だとこうかな? グローバル変数みたいなもの?
(ns my.core
(:require [quil.core :as q :include-macros true]))
(defn setup []
(q/frame-rate 30)
(q/color-mode :hsb 360 100 100 100)
(q/stroke-weight 50.0)
(def ehue (atom 0.0))
(def esat (atom 0.0)))
(defn draw []
(reset! ehue (mod (+ @ehue 1.0) 360.0))
(reset! esat (mod (+ @esat 2.0) 100.0))
(q/background 0 0 0 100)
(q/stroke @ehue 60 60 100)
(q/fill @ehue @esat 60 100)
(q/ellipse 250 250 200 200))
(q/defsketch my
:host "host"
:size [500 500]
:setup setup
:draw draw)
こちらは draw 関数の中で ehue と esat に加算していっており、frame-count を使わなくても値を変化させることが出来ています。
なるほど、この方式なら作りたいものを自由に作れる気がします。
fun-mode だとこれがもっと楽に作れるのかな?
fun-mode を使うと?
fun-mode を使って書き直すとこうですかね。
(ns my.core
(:require [quil.core :as q :include-macros true]
[quil.middleware :as m]))
(defn setup []
(q/frame-rate 30)
(q/color-mode :hsb 360 100 100 100)
(q/stroke-weight 50.0)
{:ehue 0
:esat 0
})
(defn update-state [state]
(let [{:keys [ehue esat]} state]
{:ehue (mod (+ ehue 1.0) 360.0)
:esat (mod (+ esat 2.0) 100.0)}))
(defn draw-state [state]
(q/background 0 0 0 100)
(q/stroke (:ehue state) 60 60 100)
(q/fill (:ehue state) (:esat state) 60 100)
(q/ellipse 250 250 200 200))
(q/defsketch my
:host "host"
:size [500 500]
:setup setup
:update update-state
:draw draw-state
:middleware [m/fun-mode])
@ehue で済んでいたのが (:ehue state) になったりでコード量は多少増えるけど、 state の変更部分が update-state 関数にまとまって読みやすくなったかも。
draw-state 関数の中は描画だけになってスッキリしてるし。
特に便利というよりも、コードがわかりやすくなるという感じかな?
『関数型のスタイルを Quil にもたらすもの』って説明だったけど、それはどういうことなんだろう?
副作用が起きる部分を update-state に隔離して、他の部分を純粋に保てるということなのかな?
まだよくわかんないけど、Examples 見てもほとんどが fun-mode を使ってるようだし、この流儀に則って書いていってみよう。
書いてるうちにきっとわかってくるさ!まだほとんどなんも書いてないんだし…
よし!次からは本気出してコード書く!
この記事が面白かったらサポートしていただけませんか? ぜんざい好きな私に、ぜんざいをお腹いっぱい食べさせてほしい。あなたのことを想いながら食べるから、ぜんざいサポートお願いね 💕