見出し画像

Quil で関数型プログラミング3:ループ無しでサインカーブ?

Clojure でのクリエイティブ・コーディング環境 Quil を使って関数型プログラミングを学んでみようというシリーズの 3回目です。

前回はサインカーブを書こうとしたものの、関数型プログラミングの「副作用」という考えに嵌って一行もコーディングできませんでした。

前回の記事はこちら。

今回こそサインカーブ書きたい!

つまり 3回目にしてまだサインカーブも書けないという Clojure にも Quil にもド素人な著者が書く記事です。
勘違いや誤りが無いはずがありません。
肯定文で書くと「勘違いや誤りがあることは間違いありません」
あれ?これも否定文だな?

for ループが無い?

副作用を無くす → 変数への再代入はしない → ++i とかできない

つまり、

 for (int i = 0; i < 10; ++i) {

なんてことは出来ないということですよね。

今までサインカーブを描くときは必ずと言っていいほど for ループを使ってきました。
こんな感じで。

 // Processing での例
 for (float rad = 0.0; rad < TWO_PI; rad += TWO_PI / 100.0) {
   ellipse(rad * 200.0, sin(rad) * 200.0, 5.0, 5.0);
 }

for ループ無しで一体どうやって…?

Clojure でも繰り返しの処理を書くことはあるはず。
こういう繰り返し処理って Clojure ではどう書くんでしょう?

Clojure にも繰り返し処理あるんじゃん♪

調べてみると、こちらのサイトに「for-each のようなことがしたい場合には doseq を使います」との記載がありました!ありがたい!

見よう見まねで書いてみるとこんな感じかな?

(def units [1, 2, 3])
(doseq [s units] (q/ellipse (* s 50), 100, 10, 10))

このコードをブラウザ上で実行できる Quil.info で試してみましょう。

試し方はシリーズ第一回を参照ください。
https://note.mu/deconbatch/n/necaa1ffc5cdc

このコードで y軸上は 100 固定、x軸は
 1 * 50 = 50
 2 * 50 = 100
 3 * 50 = 150
の 3箇所に円が描かれるはず。

よし!思ったとおり!

じゃあ、y軸の計算式をサインカーブにしてみましょう。

(def units [1, 2, 3])
(doseq [s units] (q/ellipse (* s 50), (+ 100 (* (q/sin s) 100)), 10, 10))

う、うん…、多分できてるんだと思う。

円の間隔を狭めて、もっと沢山描けばよさそうですね。

(def units [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
(doseq [s units] (q/ellipse (* s 50), (+ 100 (* (q/sin s) 100)), 10, 10))

よーし!いいぞ!カーブっぽくなってる。

これ多分 def units 無しでもいけるんでは?

(doseq [s [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]] (q/ellipse (* s 50), (+ 100 (* (q/sin s) 100)), 10, 10))

やっぱり! 段々わかってきたぞ。

自分で計算して書く…?わけないよね

しかし、 [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] これ書くの辛いですね。
本当は 0.0 から 2πまで書きたいし。

それに、これって繰り返し処理内で計算する値を自分で計算して手で書いてるってことですよね?
これは変だな…。 そんなわけないもんね。
この解決法も Clojure にはあるんじゃ?

ありました! range!
諸先輩方の記事ありがたい!

(doseq [s (range 1 10)] (q/ellipse (* s 50), (+ 100 (* (q/sin s) 100)), 10, 10))

(doseq [s (range 1 10 0.1)] (q/ellipse (* s 50), (+ 100 (* (q/sin s) 100)), 10, 10))

なるほど! ということは、

(doseq [s (range 0 q/TWO-PI 0.1)] (q/ellipse (* s 50), (+ 100 (* (q/sin s) 100)), 10, 10))

出来た!サインカーブ描けました!

さらにこうすれば、

(doseq [h (range 10 100 10)] (doseq [s (range 0 q/TWO-PI 0.1)] (q/ellipse (* s 50), (+ 100 (* (q/sin s) h)), 10, 10)))

高さの違う複数のサインカーブもこのとおり!
こりゃいいや!

ん!? 考えてみたらサインカーブが一行のコードで書けてますね!?

ループを回す必要ないんだね!

一行で書けちゃったサインカーブ。コードを見ると「ループ」って感じじゃないですね。

(doseq [s (range 0 q/TWO-PI 0.1)] (q/ellipse (* s 50), (+ 100 (* (q/sin s) 100)), 10, 10))

ループを回して処理していくというより、多数の対象に対して処理を一辺に施してるって感じがします。

『Clojureの場合は強力なコレクション関数が使えるため、低レベルのループ(実際には再帰)アルゴリズムを記述するケースは非常にまれになります。』

なるほど~、こういうところの考え方が命令型のプログラミングとは違うところなんですね。

なんか真髄を見た気がする!

よし! これはもう Clojure 完全に理解した!
次は Quil についてもっと知ろう。

最後の図を描いたコードの全体を掲載しておきます。

(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)
  (doseq [h (range 10 100 10)] (doseq [s (range 0 q/TWO-PI 0.1)] (q/ellipse (* s 50), (+ 100 (* (q/sin s) h)), 10, 10))))

(q/defsketch my
  :host "host"
  :size [500 500]
  :setup setup
  :update update-state
  :draw draw-state
  :middleware [m/fun-mode])

次もこの調子で本気出す!


更新履歴
19/04/10
説明中のコード断片のカッコの数が多かったのを修正
最後にコード全体を掲載


この記事が面白かったらサポートしていただけませんか? ぜんざい好きな私に、ぜんざいをお腹いっぱい食べさせてほしい。あなたのことを想いながら食べるから、ぜんざいサポートお願いね 💕