Open51

Clojureキャッチアップの旅

ピン留めされたアイテム
kip2kip2

目的

Clojure使いこなしたいのでキャッチアップをする。

気ままに行う予定なので、雑多な内容になると思われます。

ピン留めされたアイテム
kip2kip2

書籍

キャッチアップするにあたって買った本を虫干し。

関数型デザイン

偉大なるアンクル・ボブの書いた本。
Clojureを使って、OOPと関数型の対比を行うことによって関数型を学ぶ本。
だと思う。まだ読み始めたばかり。
https://www.kadokawa.co.jp/product/302404003071/

プログラミングClojure 第2版

Clojureの解説本。
おそらく日本語で詳しいのはこの本だけ?
翻訳は、Gauche Schemeの作者であり、Lisp界隈に名のしれた川合四郎さん。
聖典、Land of Lispの翻訳者でもある。
つまり、この本も実質聖典。
まだ読み始めたばかり。
https://www.ohmsha.co.jp/book/9784274069130/
追記:第2版はversion1.3の解説らしい。
第3版は1.9に対応しているらしいが、翻訳は出てない。
https://pragprog.com/titles/shcloj3/programming-clojure-third-edition/
1.3から1.9への変更点を書いた記事があったのでこれでお茶を濁そうかな。
https://scrapbox.io/ayato-p/孔雀本で情報が止まっている人のためのバージョンアップ情報まとめ

おいしいClojure入門

Cojureでいろいろなことができることを示したある種のレシピ本。
いろいろなライブラリを使用して、いろいろなことを実現しているから眺めているだけでも楽しい。
まだ読み始めたばかり。
https://gihyo.jp/book/2013/978-4-7741-5991-1

7つの言語7つの世界

パラダイムの違う7つの言語を見ることで、プログラミングの奥深い世界を知る本。
「Clojureの解説もあるし、他の言語との対比も書かれてるかなー?」と思って購入。
まだ読み始めたばかり。
https://amzn.asia/d/4dITn0L

データ指向プログラミング

データ指向プログラミングの解説本。
Clojureが話に出てきた覚えがあるので、ここに挙げた。
読みさして止まっているので、この気に読み込んでいきたい。
https://www.shoeisha.co.jp/book/detail/9784798179797


未読

The Joy of Clojure

https://amzn.asia/d/a4ajx47

ピン留めされたアイテム
kip2kip2

記事で気になっているもの

https://qiita.com/nori-ut3g/items/280812e99978b58838cd

https://qiita.com/na4da/items/aefa1fdc6ec731385649

https://zenn.dev/clazz/books/92f00040722df7

https://qiita.com/223kazuki/items/23e480880698becdbeff

https://zenn.dev/uochan/articles/2020-12-09-clj-kondo-hooks

https://zenn.dev/uochan/articles/2022-05-26-build-edn

https://zenn.dev/kj455/articles/dfa23c8357b274

https://qiita.com/dexia/items/d3e8a7593f8c105f5124

https://howitworks.hatenablog.jp/entry/2018/04/21/111137

ゼロからClojure入門した振り返り
https://qiita.com/Saibaba81/items/16ef2486d2345f7f0e57

関連リンク集
https://qiita.com/lagenorhynque/items/68c314c288b75a9492ba

ClojureによるWeb開発
https://ayato-p.github.io/clojure-beginner/intro_web_development/index.html

https://note.com/aki_takeuchi/n/n8ebd49cb677a

https://www.kbaba1001.com/posts/202408021559_the-clojure-workshop/

https://github.com/athos/japanese-clojure-companies

https://zenn.dev/huuya/articles/601699ee8a5bc7

https://zenn.dev/neco8225/articles/5c233c5b7688ee

https://scrapbox.io/lagenorhynque/Clojureに入門したら知っておきたいN個のこと

https://blog.totakke.net/posts/2016-06-29-lazy-seq-loop/

https://qiita.com/miyabisun/items/5dc3b8a5d42a8eae84d5

https://qiita.com/tetsutakamurata76/items/1da929606f36c6466e11

https://tnoda-clojure.tumblr.com/post/128047865422/java-collection-interop

末尾再帰最適化

https://qiita.com/takl/items/6d3319e835a27fc5e4b9

https://qiita.com/pebblip/items/cf8d3230969b2f6b3132

マクロ

https://qiita.com/BooookStore/items/e7919d7c2504bc7a5f2b

https://qiita.com/lagenorhynque/items/41b8ea18ccaee8a0a5db

オブジェクト指向

https://qiita.com/yosgspec/items/25d392c47b883480e09d

アルゴリズム

クイックソート
https://eddmann.com/posts/quicksort-in-clojure/

ライブラリなどに関して気になるもの

型validate
https://zenn.dev/uochan/articles/2020-12-25-malli-clj-kondo

文字列操作
https://zenn.dev/hatappo/articles/9007316cee7867

kip2kip2

動画

Clojureに関する動画がまとまっているチャンネル。
まだ見ていないが、Clojure作者のRich Hickeyさんによる講演動画もあるっぽい。
https://www.youtube.com/@ClojureTV

自分をClojure化する方法

Clojureのイントロダクション動画。
Clojureの魅力を楽しく解説している動画。
なお、「タイトルは釣りです」とのこと。
2010年の動画だが、色褪せない魅力があると思う。
https://youtu.be/OqIjENKRydc?si=uVxY7FOqTpeoDV4J

Clojureによるバイトコードプログラミング

発表者自作のインラインアセンブラマクロであるiasmの話。
まだインラインアセンブラマクロがどのようなものか理解していないが、挑戦として面白いと思う。
https://youtu.be/OXBlKUpM_Bs?si=vkmBOxVtwnE1OVOW

気になっているもの

まだ見ていないものをメモとして。
見たら、順次簡単な解説を書こうと思う。

https://www.youtube.com/watch?v=PueDZWlwo3U

https://www.youtube.com/watch?v=eveoq3nSOeg

kip2kip2

Leiningenを使ってHello world

Leiningenを使ってHello worldを行う。
Leiningenはデファクトスタンダードのプロジェクト管理ツールらしい。

プロジェクト作成したら基本的なものを作成してくれる。

lein new app hello-clojure

ディレクトリを移動して実行してみよう。

cd hello-clojure
lein run
# Hello, World!と表示される。

プロジェクトのディレクトリ構成

Leiningenを使って、デフォルトで作成されるのディレクトリ構成。

.
├── CHANGELOG.md
├── LICENSE
├── README.md
├── doc
│   └── intro.md
├── project.clj
├── resources
├── src
│   └── hello_clojure
│       └── core.clj
├── target
│   ├── classes
│   │   └── META-INF
│   │       └── maven
│   │           └── hello-clojure
│   │               └── hello-clojure
│   │                   └── pom.properties
│   └── stale
│       └── leiningen.core.classpath.extract-native-dependencies
└── test
    └── hello_clojure
        └── core_test.clj

14 directories, 9 files

本体のコード

Hello worldを出力してくれるが、どのような関数となっているのか。
hello-clojure/src/hello_clojure/core.cljの中身を見てみる。

(ns hello-clojure.core
  (:gen-class))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (println "Hello, World!"))

よくわからないので、ChatGPTに聞いてみる。

kip2kip2

Calvaの使い方についての調査

Calvaの使い方のイメージが沸かなかったので、以下の動画を視聴。

https://youtu.be/6uUynWkMDGM?si=KL6NGp6m65ATeY1y

Calvaを使うとREPLサーバーが立ち上がり、ファイルに書き込んだ内容を実行することが可能らしい。
つまり、

  • ファイルへのコードの書き込み
  • 書いたコードをREPLみたいに、その場で実行して確かめることができる

という、REPLとファイルにコードを書くことのいいとこどりのような形式で使えるらしい。
なにそれ最強じゃんね。

kip2kip2

Calvaを使ってみる

Calvaを使って簡単なコードを書いてみるテスト。

起動の仕方

まずはREPLサーバーの起動の仕方。

コマンドパレットを開き、Calva: Start a Project REPL and Connectを選択。

REPLを実行したいディレクトリを選択する。
今回はPractice-Clojure/hello-clojureが該当。

ProjectのTypeを選択する。
よくわかっていないが、Leiningenを導入しているのでそれを選択。

すると、REPLが起動する。

起動したREPLのファイルは特にいじらないでOK。

あとは、コードを書く方のcljファイルにコードを書いていけば良い。

実行の仕方

コードを書いた行の末尾で、Cmd + Enter(Mac)あるいはCtrl + Enter(Windows)を入力すれば実行できる。

試しに実行している様子。

なお、行の中間などで実行した場合は、シンタックスエラーが出るようだ。
カーソルの位置が重要みたい。

Hello, のスペースのあたりで実行した場合こうなった。

終了の仕方

REPLサーバーを終了させる方法。

コマンドパレットを開き、Calva: Disconnect from the REPL Serverを選択する。

kip2kip2

fizzbuzzを書いてみる

とりあえずAIに聞きながらfizzbuzzを書いてみる。


(defn fizzbuzz [n]
  (cond
    (and (zero? (mod n 3)) (zero? (mod n 5))) "FizzBuzz"
    (zero? (mod n 3)) "Fizz"
    (zero? (mod n 5)) "Buzz"
    :else (str n)))

(doseq [i (range 20)]
  (println (fizzbuzz i)))

Clojureでは副作用が無いコードが推奨されるらしいので、その形で書いた。

副作用のない形で関数を書くのは馴染がないため、今度も注意して実装していきたい。

kip2kip2

Calvaの開発体験

開発体験かなりいい。
何がいいかというと、「本番用のファイルに実験コードを書いて実行できる」ということ。

Calvaでは、コードを書いて、その場で実行ができる。
つまり、REPLでコードを実行するのと体験が変わらない。

しかも、その書いたコードはファイルに残る。
つまり、REPLで実行することとファイルにコードを書くことを、両立して行える。

具体的に何が嬉しいかというのを一つのケースで示す。

  1. 開発中に、いままで使ったことの無い関数を使う必要が出てきた。
  2. 関数の動きを確かめるコードを書いて、関数の使い方を学ぶ。
  3. 使い方がわかった関数で、必要なコードを書く。

という流れになると思うが、従来、自分は以下のように行っていた。

  • 別ファイルや実験用のプロジェクトを作成し、動きを確かめるコードをそこに書いて確かめる。
  • 本番用のファイルに、必要なコードを書く。

という感じで、ファイルを行きつ戻りつしており、集中が途切れること甚だしかった(ワーキングメモリーが小さいのでわりと困っていた)。

Calvaを使うと、同じファイル内で完結できる。

  • 同じファイルに実験用のコードを書いて動きを確かめる。
  • 本番用のコードを、そのまま同じファイルに書く。
  • 不必要になった実験用のコードを削除する。

これはかなりいい。
自分が探してきた理想の開発方法かもしれない。

追記

こんな記事を見つけた。
https://zenn.dev/nfunato/articles/jp-on-repl-programming

Clojureでの開発方法はREPL駆動開発というらしい。
Calvaでやってることが、その考え方の一つの実装と言えるのかも。

kip2kip2

テストの方法

テストのやり方を書く。

テストコードを書く

テストコードを書く。
testディレクトリの中にあるファイルに記載する。
今回で言えば、以下のディレクトリにあるcore_test.cljに記載する。

# ディレクトリは一部のみ抜粋しているので、実際のディレクトリ構成とは異なることに注意
├── src
│   └── hello_clojure
│       └── core.clj
└── test
    └── hello_clojure
        └── core_test.clj

今回はfizzbuzz関数のテストコードを書いている。

(ns hello-clojure.core-test
  (:require [clojure.test :refer :all]
            [hello-clojure.core :refer :all]))

(deftest fizzbuzz-test
  (testing "FizzBuzzの動作")
  (is (= "Fizz" (fizzbuzz 3)))
  (is (= "Buzz" (fizzbuzz 5)))
  (is (= "FizzBuzz" (fizzbuzz 15)))
  (is (= "2" (fizzbuzz 2))))

CalvaのREPLにロードする

書いたテストファイルをREPL上にロードする必要がある。

ショートカットキーでも可能らしいが、vimキーバインドと競合していたため、コマンドパレットを用いた方法で実行してみる。

コマンドパレットを開き、Calva: Load/Evaluage Current File and its Requires/Dependenciesを選択する(画像は表示の関係上見切れている)。

REPL側で読み込まれていることが確認できれば成功。

テストの実行

コマンドパレットを開き、Calva: Run Current Testを選択。

REPLでテストが実行されていることを確認する。

追記補足

設定をいじったら、クリックだけで実行できるようになったので、メモ。

VSCodeの以下の項目にチェックを入れる。

すると、他の言語ではおなじみの、テスト実行ボタンが出現する。

注意点として、core.cljの実行しか成功しなかったこと(同じネームスペース内にテスト対象の関数とテストコードが併存している必要がある?)。

core_test.cljでの実行が失敗しているので、これについては今後の課題。

kip2kip2

テストの方法2

ファイルを分けるのではなく、同一ファイル内に一旦書くことはできないだろうか?
Rustだと同一ファイル内にテストコードを書けるので、書けないかなと。

CalvaのRefresh

先ほど読み込んだテストコードをアンロードする必要がある。
しかし、個別のアンロード機能は無いらしい(AI情報。裏取りはまだしていない)ので、全体読み込み直す必要がある。

コマンドパレットから、Calva: Refresh All Namespacesを選択する。

すると、Calvaが読み込み直しされた結果でStopするようなので、再度Calvaを立ち上げ直す。
やり方は以前に書いているので省略。
REPLサーバーを立ち上げるときと全く一緒。

同一ファイルにテストコードを書く

関数コードと同じファイルにテストコードを記載する。
ポイントとしては、インポートにテストライブラリを追加するくらい。

(ns hello-clojure.core
  (:gen-class)
  (:require [clojure.test :refer :all]))


(defn -main
  [& args]
  (println "Hello, Clojure!"))


(defn fizzbuzz [n]
  (cond
    (and (zero? (mod n 3)) (zero? (mod n 5))) "FizzBuzz"
    (zero? (mod n 3)) "Fizz"
    (zero? (mod n 5)) "Buzz"
    :else (str n)))

(doseq [i (range 20)]
  (println (fizzbuzz i)))

(deftest fizzbuzz-test
  (testing "FizzBuzzの動作")
  (is (= "Fizz" (fizzbuzz 3)))
  (is (= "Buzz" (fizzbuzz 5)))
  (is (= "FizzBuzz" (fizzbuzz 15)))
  (is (= "2" (fizzbuzz 2))))

テストの実行

テストの実行は、ファイルを分けたときと同じくCalva: Run Current TestでOK。
REPLへのロードは特に必要なかったので、おそらくRefresh時に読み込まれているのではないかと思う。

テストコードの更新

テストコードを更新したら、REPLに読み込ませる必要がある。

これは通常のREPL実行のショートカットキーと同様に、Control + Enterでできるようだ。
なお、これは別ファイルに分けた場合もできることが確認できた。

更新後は、通常のテスト実行と同様に行えば良い。

別ファイルに移動

満足するまでテストできたら、別ファイルに移動したらいいのかなと思う。
本来、そこに書かれるのが正しいだろうし。

課題

後々のTodo。

  • アンロードと再起動がめんどくさい。一つのコマンドで実行できないか。
  • できればコマンドパレットではなく、ショートカットキーなどで手軽に実行したい。
kip2kip2

TDD

TDDでなにか実装してみる。
簡単な四則演算アプリを実装してみる。

これに関しては結果だけ貼っておく。

(ns hello-clojure.core
  (:gen-class)
  (:require [clojure.test :refer :all]))

(defn calculation [op a b]
  (cond
    (= op "+") (+ a b)
    (= op "-") (- a b)
    (= op "*") (* a b)
    (= op "/") (cond
                  (= b 0) (str "Division by 0 is not allowed.")
                  :else (/ a b))
    :else "The operator is incorrect."))

(deftest calculation-test
  (testing "四則演算関数のテスト"
    (is (=(calculation "+" 1 2) 3))
    (is (=(calculation "-" 2 1) 1))
    (is (=(calculation "*" 2 3) 6))
    (is (=(calculation "/" 10 2) 5))
    (is (=(calculation "/" 2 10) 2/10))
    (is (=(calculation "/" 10 0) "Division by 0 is not allowed."))
    (is (=(calculation "n" 1 2) "The operator is incorrect."))
    ))

リファクタリング

Clojure流に書けているかが気になったので、AIに聞いてみて改良する試み。

AI回答のポイントとしては、

  • 演算子をHashMapにしてはどうか
  • ハッシュマップにない場合のエラーを表示する形にしてはどうか

といった形であった。

AIに言われたそのままの形であるが、以下のように変更。

(defn calculation 
  [op a b]
  (let [operations {"+" + 
                    "-" - 
                    "*" * 
                    "/" (fn [a b] (if (= b 0)
                                    "Division by 0 is not allowed."
                                    (/ a b)))}]
    (if-let [operation (operations op)]
      (operation a b)
      "Invalid operator.")))

(deftest calculation-test
  (testing "四則演算関数のテスト"
    (is (=(calculation "+" 1 2) 3))
    (is (=(calculation "-" 2 1) 1))
    (is (=(calculation "*" 2 3) 6))
    (is (=(calculation "/" 10 2) 5))
    (is (=(calculation "/" 2 10) 2/10))
    (is (=(calculation "/" 10 0) "Division by 0 is not allowed."))
    (is (=(calculation "n" 1 2) "Invalid operator."))
    ))

さらに改良をお願いしたら、

  • デフォルトの場合をバインドする
  • テストに名前をつける

という話だったので、コードを変更。

(defn calculation 
  [op a b]
  (let [operations {"+" + 
                    "-" - 
                    "*" * 
                    "/" (fn [a b] 
                          (if-not (= b 0)
                            (/ a b)
                            "Division by 0 is not allowed."))}
        default-fn (fn [_ _] "Invalid operator.")] 
    ((or (operations op) default-fn) a b)))

(deftest calculation-test
  (testing "四則演算関数のテスト"
    (is (=(calculation "+" 1 2) 3) "Addition test failed")
    (is (=(calculation "-" 2 1) 1) "Subtraction test failed")
    (is (=(calculation "*" 2 3) 6) "Multiplication test failed")
    (is (=(calculation "/" 10 2) 5) "Division test failed")
    (is (=(calculation "/" 2 10) 2/10) "Fraction division test failed")
    (is (=(calculation "/" 10 0) "Division by 0 is not allowed.") "Zero division test failed")
    (is (=(calculation "n" 1 2) "Invalid operator.") "Invalid operator test failed")
    ))

いままでのパラダイムと違うので、AIにバンバン質問しながらClojureのスタイルに慣れていきたい。

kip2kip2

バブルソート実装

入門したてで右も左もわからないので、AIに聞きながら実装してみる。
TDDで実装。

空のリストを返すテストを書く

まず、空のリストをもらったときにそのまま返すテストを書く。

(deftest bubble-sort-test
  (testing "バブルソート関数のテスト"
    (is (= (bubble-sort []) []))))

空のリストを返す関数を書く

次に、空のリストを返す関数を書く。

(defn bubble-sort 
  [coll] 
  coll)

テストが通るのを確認してフィニッシュ。

要素一つの場合

要素を一つだけの場合を書いていく。

(deftest bubble-sort-test
  (testing "バブルソート関数のテスト"
    (is (= (bubble-sort []) []))
    (is (= (bubble-sort [1]) [1]))))

実装の方は変える必要がないので、テストが通ることを確認したらフィニッシュ。

要素2つの場合

要素2つの場合。
まずテストコードを追加。

(deftest bubble-sort-test
  (testing "バブルソート関数のテスト"
    (is (= (bubble-sort []) []))
    (is (= (bubble-sort [1]) [1]))
    (is (= (bubble-sort [2 1]) [1 2]))
    (is (= (bubble-sort [1 2]) [1 2]))))

ソートされてないものと、ソートしているものの両方のケースを追加。

さて、最小限度のコードで実装する。

(defn bubble-sort 
  [coll] 
  (if (= (count coll) 2)
    (if (> (first coll) (second coll))
      [(second coll) (first coll)]
      coll)
    coll))

テストが通ったことを確認してフィニッシュ。

要素が3つ以上の場合

要素が3つ以上の場合はちょっと飛躍がいる。
Clojureにふさわしい書き方で実装したいので、AIに聞きながら実装したものがこちら。

テスト含めて、以下のコードになった。


(defn bubble-sort-pass 
  [coll] 
  (loop [unsorted coll
          result []]
        (cond
          (empty? unsorted) result
          (empty? (rest unsorted)) (conj result (first unsorted))
          :else
            (let [[a b & rest] unsorted]
              (if (> a b)
                (recur (cons a rest) (conj result b))
                (recur (cons b rest) (conj result a)))))))

(defn bubble-sort [coll]
  (loop [coll coll]
    (let [sorted (bubble-sort-pass coll)]
      (if (= coll sorted)
        sorted
        (recur sorted)))))

(deftest bubble-sort-test
  (testing "バブルソート関数のテスト"
    (is (= (bubble-sort []) []))
    (is (= (bubble-sort [1]) [1]))
    (is (= (bubble-sort [2 1]) [1 2]))
    (is (= (bubble-sort [1 2]) [1 2]))
    (is (= (bubble-sort [2 1 3]) [1 2 3]))
    ))
kip2kip2

選択ソート

選択ソートを実装してみる。

(defn selection-sort [coll]
  (loop [unsorted coll
        sorted []]
        (if (empty? unsorted)
        sorted
        (let [smallest (apply min unsorted)
              remainder (remove (fn [x] (= x smallest)) unsorted)]
          (recur remainder  (conj sorted smallest))))))

(deftest selection-sort-test
  (testing "選択ソート関数のテスト"
    (is (= (selection-sort []) []))
    (is (= (selection-sort [1]) [1]))
    (is (= (selection-sort [2 1]) [1 2]))
    (is (= (selection-sort [1 2]) [1 2]))
    (is (= (selection-sort [2 1 3]) [1 2 3]))
    (is (= (selection-sort [3 2 1]) [1 2 3]))
    (is (= (selection-sort [5 4 3 2 1]) [1 2 3 4 5]))))
kip2kip2

modとremの違い

割り算用の関数にmodとremの2つが存在しており、違いわからなかったため色々調べてみた。

以下の記事に詳しかった。
https://qiita.com/dys7/items/3b05ff52381579075e42

しかし

remはわかる。
あまりが1で被除数の符号に揃えるということで理解ができた。

しかし、なぜmodだと2という数字が出てくるのだろうか。
不思議でならない。

;; modの場合
(mod 10 3)
;; 1
(mod -10 3)
;; 2
(mod 10 -3)
;; -2

;; remの場合
(rem 10 3)
;; 1
(rem -10 3)
;; -1
(rem 10 -3)
;; 1

AIにきいてみた

ググってもよくわからないので、AIに聞いてみた。

切り捨てしてるんだってさ(まだよくわかってない)。

modが正しい?

この記事を読む限り、modの方が正しそうに見える。

https://naop.jp/2021/05/29/hunoamari/

modのソースコード

modのソースコード中にremが使用されてると聞いたので、調査。
source関数を使用してmodのソースコードを表示する。

(source mod)
; (defn mod
;   "Modulus of num and div. Truncates toward negative infinity."
;   {:added "1.0"
;    :static true}
;   [num div] 
;   (let [m (rem num div)] 
;     (if (or (zero? m) (= (pos? num) (pos? div)))
;       m 
;       (+ m div))))

肝は以下の部分だと思う。

;     (if (or (zero? m) (= (pos? num) (pos? div)))
;       m 
;       (+ m div))))

mが0、あるいはnumとdivの符号が一致する場合は、mをそのまま返している。
つまり、

  • numが+、divが+
  • numが-、divが-
  • mが0

の場合はそのまま返す(ちなみに短絡評価である)。

+の場合はいいが、-同士の場合はどうだろうか。

(rem -10 -3)
;; -1

そして、それ以外のばあいは以下のコードが該当する。

;       (+ m div))))

mにdivを足している。
つまり、考えられるケースは、

  • numが-で、divが+
  • numが+で、divが-

の2通り。

具体例で書くと

;; numが-で、divが+
(rem -10 3)
;; -1

;; numが+で、divが-
(rem 10 -3)
;; 1

これらの計算結果にdivを足すので、それぞれ以下のようになる。

(+ -1 3)
;; 2

(+ 1 -3)
;; -2

ソースコードは読み解けたが、なぜこのようにするかは謎のまま。

modの挙動について

数学のモジュロ演算に基づいているらしい。
https://stackoverflow.com/questions/37210994/difference-between-mod-and-rem-in-clojure

剰余演算

wikiに表あり。
https://ja.wikipedia.org/wiki/剰余演算

remのソースコード

参考だが、remのソースコードはこちら。

; (defn rem
;   "remainder of dividing numerator by denominator."
;   {:added "1.0"
;    :static true
;    :inline (fn [x y] `(. clojure.lang.Numbers (remainder ~x ~y)))}
;   [num div]
;     (. clojure.lang.Numbers (remainder num div)))

remのドキュメント

https://clojuredocs.org/clojure.core/rem
clojure.coreをみると、次のような記載がある。

;; rem and mod are commonly used to get the remainder.
;; mod means Knuth's mod (truncating towards negativity). 
;; rem implements  ANSI C's % operator
;; Absolute value stays the same, always the distance
;; towards zero.
;; sign depends on dividend.

modはドナルド・クヌース大先生のいうところの切り捨て法[1]らしい。
また、remはANSI Cの%[2]を実装したものらしい。

補足

clojure.coreにも色々書いてあるので、参考。
決定的にどう、というのは書いてなさそう?

https://clojuredocs.org/clojure.core/mod

脚注
  1. wikiによるとこの本に記載があるらしい。https://tatsu-zine.com/books/taocp-vol1 ↩︎

  2. ANSI C (ISO/IEC 9899:1990) に記載があるとのこと。 ↩︎

kip2kip2

関数について調べる方法

以下の4つが使用できる。

  • doc
  • source
  • find-doc
  • apropos

doc

(doc mod)
; -------------------------
; clojure.core/mod
; ([num div])
;   Modulus of num and div. Truncates toward negative infinity.

source

(source mod)
; (defn mod
;   "Modulus of num and div. Truncates toward negative infinity."
;   {:added "1.0"
;    :static true}
;   [num div] 
;   (let [m (rem num div)] 
;     (if (or (zero? m) (= (pos? num) (pos? div)))
;       m 
;       (+ m div))))

find-doc

(find-doc "mod")

;; 表示多いため、一部のみ
; -------------------------
; clojure.test/set-test
; ([name & body])
; Macro
;   Experimental.
;   Sets :test metadata of the named var to a fn with the given body.
;   The var must already exist.  Does not modify the value of the var.
; 
;   When *load-tests* is false, set-test is ignored.
; -------------------------
; cider.nrepl.middleware.debug/skip-breaks!
; ([mode] [mode coor code force?])
;   Set the value of *skip-breaks* for the top-level breakpoint.
;   Additional arguments depend on mode, and should be:
;    - empty for :all or :trace
;    - coordinates, code, and force for :deeper or :before
;   See `skip-breaks?`.
; -------------------------
; cider.nrepl.middleware.debug/skip-breaks?

apropos

(apropos "selection")
(hello-clojure.core/selection-sort hello-clojure.core/selection-sort-test)

参考

https://kazuhira-r.hatenablog.com/entry/20121027/1351329786

kip2kip2

clojure.spec

こちらの本で紹介されていて気になったので、調査。
https://booth.pm/ja/items/1575586

そもそもどういうものか

Clojureは動的言語なので、型の情報なしで開発を進めていくのが基本です。
動的な言語と静的な言語のどちらが優れているか、という議論は
以前からありますが、あなたはどうお考えでしょうか。
この問題にはさまざまな意見があるかと思いますが、Clojureは動的型付け
を選択した言語です。
しかし、specというライブラリーを使うと、Clojureでも型の恩恵を
受けることができます。

https://qiita.com/Haar/items/3ad988cc75a90ae20a2f

動的型付けであるClojureで型が使えるものということだろうか。

使用してみる

;; import
(require '[clojure.spec.alpha :as s])

;; nameが文字列であることを定義
;; 型でいうとstring相当
(s/def ::name string?)

;; 正のintであることを定義
;; 型でいうとuint相当
(s/def ::age pos-int?)

;; データを定義
(def alice {:name "Alice" :age 30}) 
(s/valid? ::name (:name alice))
;; true
(s/valid? ::age (:age alice))
;; true

;; データを定義
(def bob {:name "Bob" :age -5}) 
(s/valid? ::name (:name bob))
;; true
(s/valid? ::age (:age bob))
;; false

参考にした文献

https://booth.pm/ja/items/1575586

kip2kip2

設定変更

Calvaのフォーマット設定が自分と合わなかったので設定変更する。

何をしたいか

Tabキー押したときのフォーマットを無効化したい。
Tabキー押したときにフォーマットされると、自分の想定していないところにカーソルが行くので戸惑っていますので。

公式ドキュメント

フォーマットに関してはこちらの公式ページがあるので、こちらを参照して変えていく。
https://calva.io/formatting/

実施に変えてみる

VSCodeの設定変更

まず、VSCodeのファイル保存時のオートフォーマットを有効化する。
Tabキー押下時のフォーマットは防ぎたいが、フォーマットそのものはほしいので、ファイル保存時のものを有効化する。

Also: If you have Format on Save enabled in VS Code, it will be Calva doing the formatting for Clojure files.

Calvaのキーバインドを無効

キーバインドそのものを無効化する。
特にキーバインドを使用していなかったので、一旦この対応で問題ないと思う。

問題発生

デフォルトキーバインド使ってないと思っていたら、コードの実行で使用していた(Control + Enter)。

なので、なんとかしてデフォルトのTabキーのみを設定変更する必要がある。

キーバインドの変更

まずVSCodeのキーボードショートカットの設定を開く。
Macを使用しているので、Cmd + Sで開く。

検索欄にcalvaと入力し、Tabキーを探す。

あとは適当なキーに変更したらOK。

これでTabキーのフォーマットを無効化し、その他のキーバインドは使える状態になった。

記事化

今後キーマップ変更したいときがあるとおもうので、記事として残すことにした。
https://zenn.dev/kip2/articles/3efd77d21721a8

kip2kip2

スレッディングマクロ

スレッディングマクロと言う記法を使えば、メソッドチェーンのようなことができるらしい。

https://note.com/mi_nil/n/n78f1701bccb0


(def text " hello world ")

;; threading macroを使う場合
(-> text
    clojure.string/trim
    clojure.string/upper-case
    (str "!!!"))

;; threading macroを使わない場合
(str (clojure.string/upper-case (clojure.string/trim text)) "!!!")

別の例

(def person {:name "Alice" :age 28 :city "New York"})

(str "Age in 10 years: " (+ 10 (:age person)))
;; "Age in 10 years: 38" 

(-> person
    (:age)
    (+ 10)
    (str "Age in 10 years: "))
;; "38Age in 10 years: "

;; どうやら各処理の結果は第一引数に渡されるようだ
;; 第二引数に渡すようにするコードは以下になる
(->> person
     (:age)
     (+ 10)
     (str "Age in 10 years: "))

kip2kip2

Todoアプリのバックエンドを置き換えてみる

以前にGolangで作ったTodoアプリのバックエンドを置き換えて、Clojureをキャッチアップする試み。

以前作ったTodoアプリ
https://github.com/kip2/todo-app-go

TodoアプリのAPIドキュメント
https://kip2.github.io/todo-app-go/

まずは準備

まずは必要そうなものを挙げて準備していこう。

参考になると思われる資料

https://qiita.com/lagenorhynque/items/b15689e5432e0170b172
https://qiita.com/lagenorhynque/items/f1e3c75439c1625756f3

https://ayato-p.github.io/clojure-beginner/intro_web_development/index.html

ライブラリやフレームワークの選定

とりあえず候補を挙げてみる。

フレームワーク

Webフレームワーク

Ring
https://github.com/ring-clojure/ring

compojure
https://github.com/weavejester/compojure

reitit
https://github.com/metosin/reitit
https://hirake.link/clojure-reitit-api-01/
https://cljdoc.org/d/metosin/reitit/0.7.2/doc/introduction

Pedestal
https://github.com/pedestal/pedestal
https://qiita.com/lagenorhynque/items/fbd66ebaa0352ec4253d

フルスタックフレームワーク

Luminus
https://luminusweb.com/

ライブラリ

JSONエンコード/デコード

Cheshire
https://github.com/dakrone/cheshire

データバリデーション

https://github.com/metosin/malli
https://zenn.dev/shinseitaro/books/clojure-malli

kip2kip2

コミュニティライブラリのリポジトリ

コミュニティライブラリのリポジトリサイト見つけた。

https://clojars.org/

kip2kip2

cheshrieのparse-stringについて

cheshireのparse-stringでハマったところがあったので、備忘として。

以下のようなテストコードを書いていたが、出力が期待したものとちがっていた。

(:require [cheshire.core :as json])

(deftest test-handler-json
  (testing "GET /json"
    (let [response (app (mock/request :get "/json"))
          body (json/parse-string (bs/to-string (:body response)) true)]
      (is (= 200 (:status response)))
      (is (= "application/json;charset=UTF-8" (get-in response [:headers "Content-Type"])))
      (is (= {:message "これはJSONレスポンスです" :status "success"} body)))))

キーが文字列として扱われている。

; ; expected:
; {:message "これはJSONレスポンスです", :status "success"}
; 
; ; actual:
; {"message" "これはJSONレスポンスです", "status" "success"}

これは、cheshire/core/parse-stringにtrueを指定すると回避できる。

(def json-string "{\"message\": \"これはJSONレスポンスです\", \"status\": \"success\"}")

;; trueを指定せずパースする
(def parsed-without-true (json/parse-string json-string))

;; trueを指定してパースする
(def parsed-true (json/parse-string json-string true))

;; 出力を確認
(prn parsed-without-true)
;; => {"message" "これはJSONレスポンスです", "status" "success"}
(prn parsed-true)
;; => {:message "これはJSONレスポンスです", :status "success"}

trueを指定しないと、キーがJSONで扱っている文字列そのままになるっぽい。
trueを指定すると、Clojureのmap構造に変換してくれる、ということのようだ。

記事化

備忘用に記事化した。

https://zenn.dev/kip2/articles/cheshire-parse-string

kip2kip2

Atcoderについて

簡単にAtcoderについて調べたら、どうやらClojureではメモリをくうからあまりできないという話を見かけた。

しかし、Babashkaを使えば早くできる可能性があるらしい。
https://qiita.com/MeguruMokke/items/a42e794663c0284a6ede

ただ、どうやら公式で対応しているらしいので杞憂かも。(2024年11月現在)
https://www.kbaba1001.com/posts/2024012902_babashka-at-coder/

Babashka

Babashkaそのものも興味深いツールのようなので、いつか触ってみたい。

Babashkaそのものの参考記事。
https://zenn.dev/shinseitaro/books/9270abff642f59
https://qiita.com/minebreaker/items/de78a425db81313eeccc
https://github.com/babashka/babashka#installation

kip2kip2

ソースコードリーディング

なにか一つソースコードをリーディングしてみたい。

ライセンスや著作権の問題があるので、ローカルでやると思うけど。

候補

https://github.com/weavejester/compojure

kip2kip2

フィボナッチ関数を書いてみる

再帰の定番である、フィボナッチ関数を書く。

まず単純にフィボナッチを返す関数から。

(defn fibonacci [n]
  (cond (= n 0) 0
        (= n 1) 1
        :else (+ (fibonacci (- n 2)) (fibonacci (- n 1)))))

(deftest fibonacci-helper-test
  (testing "フィボナッチ関数のテスト"
    (is (= (fibonacci 0) 0))
    (is (= (fibonacci 1) 1))
    (is (= (fibonacci 2) 1))
    (is (= (fibonacci 3) 2))
    (is (= (fibonacci 4) 3))
    (is (= (fibonacci 5) 5))
    (is (= (fibonacci 6) 8))
    (is (= (fibonacci 7) 13))
    (is (= (fibonacci 8) 21))))

次に、フィボナッチのリストを返す関数。

(defn fibonacci-seq [n]
  (letfn [(fib-helper [a b count]
            (if (zero? count)
              []
              (cons a (fib-helper b (+ a b) (dec count)))))]
    (fib-helper 0 1 n)))

(deftest fibonacci-seq-test
  (testing "リストを返すフィボナッチ関数のテスト"
    (is (= (fibonacci-seq 0) []))
    (is (= (fibonacci-seq 1) [0]))
    (is (= (fibonacci-seq 2) [0 1]))
    (is (= (fibonacci-seq 3) [0 1 1]))
    (is (= (fibonacci-seq 4) [0 1 1 2]))
    (is (= (fibonacci-seq 5) [0 1 1 2 3]))
    (is (= (fibonacci-seq 6) [0 1 1 2 3 5]))
    (is (= (fibonacci-seq 7) [0 1 1 2 3 5 8]))
    (is (= (fibonacci-seq 8) [0 1 1 2 3 5 8 13]))
    (is (= (fibonacci-seq 9) [0 1 1 2 3 5 8 13 21]))
    (is (= (fibonacci-seq 10) [0 1 1 2 3 5 8 13 21 34]))))

TOC版。

(defn fibonacci-toc [n]
  (letfn [(fib-helper [a b count]
            (if (zero? count)
              a
              (recur b (+ a b) (dec count))))]
    (fib-helper 0 1 n)))

(deftest fibonacci-toc-test
  (testing "フィボナッチ関数(TOC)のテスト"
    (is (= (fibonacci-toc 0) 0))
    (is (= (fibonacci-toc 1) 1))
    (is (= (fibonacci-toc 2) 1))
    (is (= (fibonacci-toc 3) 2))
    (is (= (fibonacci-toc 4) 3))
    (is (= (fibonacci-toc 5) 5))
    (is (= (fibonacci-toc 6) 8))
    (is (= (fibonacci-toc 7) 13))
    (is (= (fibonacci-toc 8) 21))))

kip2kip2

コールグラフ生成ツール

コールグラフ生成ツールがほしい。

多分、コードリーディングの際にも役立つと思う。

chatGPTにきいてみた

Clojureで使えるコールグラフツールの候補を聞いてみた。

clj-kondoでやる方法

まずCLIツールとしてのclj-kondoをインストールする。
macなのでhomebrewでインストールした。
https://github.com/clj-kondo/clj-kondo/blob/master/doc/install.md

 brew install borkdude/brew/clj-kondo

dot形式にする方法をAIに聞いたもの。

(require '[clojure.edn :as edn]
         '[clojure.java.io :as io])

;; EDNファイルを読み込む
(defn load-edn [file]
  (with-open [r (io/reader file)]
    (edn/read (java.io.PushbackReader. r))))

;; 関数呼び出し関係をDOT形式に変換する
(defn generate-dot [analysis]
  (let [usages (:var-usages analysis)
        edges (map (fn [{:keys [name to]}] [name to]) usages)]
    (str "digraph G {\n"
         (apply str (map (fn [[from to]]
                           (str "  \"" from "\" -> \"" to "\";\n"))
                         edges))
         "}")))

;; EDNファイルからDOTファイルを生成
(defn edn-to-dot [edn-file dot-file]
  (let [analysis (load-edn edn-file)
        dot-data (generate-dot analysis)]
    (spit dot-file dot-data)))

(edn-to-dot "analysis.edn" "graph.dot")
  • 使えそうなツールを調査する
kip2kip2

階乗を計算するプログラム

再帰の練習として、階乗を計算するプログラムを書いてみた。

(defn factorial [n]
  (if (= n 0)
    1
    (* n (factorial (dec n)))))

(factorial 5)
;; 120

末尾呼び出し最適化

末尾呼び出し最適化を行ってみる。

(defn factorial-tco [n]
  (letfn [(fact-helper [n sum]
            (if (zero? n)
              sum
              (recur (dec n) (* sum n))))]
    (fact-helper n 1)))

(factorial-tco 5)
;; 120
kip2kip2

この記事を読んでのメモ

https://boxofpapers.hatenablog.com/entry/2017/04/10/154333

少ない抽象データ構造と、たくさんの関数

Clojureでは、オブジェクト指向言語と異なり、関数とデータはおおよそ完全に分離しています。

一方、データと関数が分離している場合は、関数を特定のクラスやオブジェクトに結びつける意味はないですし、逆に、ある関数のために専用のクラスを作る意味もない。極端に考えれば、すべての関数が共通のデータ構造を処理するようにした方が、多様な関数を柔軟に活用できるようになる。

このような構造はJavaのインタフェースのようなものがあれば、他でも実現できると思うだろうけども(実際、Associativeと Seqは、Javaのインタフェースとして定義されています)、それは「注意深く設計すればそのライブラリ内ではそうできる」という話であって、言語として標準の抽象データを定義して、すべてをそこに集約させよう、という世界では、他の言語機能が、すべて、このような仕組みを支援するように作られています。言語としての前提であるからです。たとえば、JavaですべてのデータをMapで作っても、苦しいだけで利点などないでしょう。そういう前提で言語が作られていないからです。

このような抽象データ構造があるからこそ、関数とデータを分離できるわけです。関数は共通の抽象データ構造しか見ていないからこそ、実際のクラスとは結びつく必要がないのです。

アドホックな多態

一方、Clojureにとって多態とは、関数ディスパッチの話と捉えられています。関数はデータ(オブジェクト)とダイレクトには結びついてないので、多態の説明として、オブジェクトやクラスと結びつけて語ることはできません。ある関数を呼んだ時に、実際に実行される関数実装はどれなのか?というディスパッチの問題に過ぎないのです。

つまり、コードを書いている時に、ある関数を多態にするかどうかというのは後から考えても大丈夫な仕組みなわけです。もちろん、作ってる段階で、設計として「ここの関数は外から拡張できるようにしたいから、マルチメソッドにしておこう」とか「ここはユーザー利便性を考えてプロトコルを定義しておくか」ということはありますが、そうでないところを後からマルチメソッドやプロトコル関数化することも、結構簡単に行えるのです。

だからAd-hoc(場当たり的な)ポリモフィズムと呼ぶわけです。もちろん注意すべきことはあって、対象の処理が関数に分離されてなければ、後で多態関数化することもできませんから、できれば関数は細かく分けておいた方が、後から対処しやすいと思います。そのような注意点さえクリアしておけば、柔軟に後からコード変更可能だ、というのも、Clojureの利点の一つです。

kip2kip2

Calvaでファイル単位に読み込む方法

コマンドパレットからCalva: Load/Evaluate Current File and its Requires/Dependenciesを選択してロードする。

これはショートカットキーでも設定ができるので、ショートカットキー設定を開いて(mac環境ならCmd+Kを押したあとにCmd+S)、自分の使いやすいキーにバインドすれば快適に使える。

とりあえずOpt+Enterにキーを設定した。

kip2kip2

Duct

WebフレームワークであるDcutというのがあるらしい。
https://github.com/duct-framework/duct

Ringの方を手に掛けようとしているから、あとで使うための資料をまとめておく。
あとなんかドキュメントが少ないとかいう恐ろしい話を見かけたんだけど、ホント...?

https://qiita.com/lagenorhynque/items/38537fa91300e0ac0070
https://tech.uzabase.com/entry/2019/09/24/175551
https://qiita.com/lagenorhynque/items/57d5aa086c4a080a1c54
https://qiita.com/lagenorhynque/items/8201c116c87b40eb9c22
https://clojure-camp.com/posts/duct-init-2/

https://www.amazon.co.jp/Clojure製Webフレームワーク-Duct入門-ダックタイピングブックス-馬場-一樹-ebook/dp/B07J3KJZYS

kip2kip2

forとletの関係について

:whenオプションなどを一旦考えなければ、以下のコードのようにletforの動作は再現可能。

(for [x [1 2 3]]
  x)

(let [x (doall (map identity [1 2 3]))]
  x)

つまり、forでは遅延リストではなく、評価したリストを返している模様。

:whenの再現もfilterを使えば可能。

(for [x [1 2 3 4 5 6]
      :when (even? x)]
  x)

(let [x (doall (map identity (filter even? [1 2 3 4 5 6])))]
  x)

:letの再現はどうやら難しいっぽい。

(for [x [1 2 3]
      :let [y (* x x)]]
  (+ x y))
;; (2 6 12)

(let [x (doall (map (fn [x]
                      (let [y (* x x)]
                        (+ x y))) [1 2 3]))]
  x)
;; (2 6 12)
kip2kip2

REPL駆動開発で気になった点

この記事から。
https://qiita.com/223kazuki/items/afb6341cf73a9173fda0

REPL を起動するとプロジェクト中の Clojure のコードは REPL 上に読み込まれ、REPL 上から利用可能となります。

REPL起動で全てが読み込まれるとのことだが、今のところ、自力で読み込まない限り読んでくれない。
なにか起動時のやり方があるのだろうか?
あるいは設定?
Calvaでやってるのが問題?

よくわかっていないが、一旦備忘としてメモしておく。