自作OSSとしてCommon Lisp用の描画ツールKaiを作っています

f:id:komi1230:20191206222039j:plain

Softbank AI部 Advent Calendar 2019の7日目。

まず始めに、今回一緒にアドベントカレンダーを回していってくれている社員の皆さんと内定者に感謝。

というのも、このSB AI部のアドベントカレンダーの企画を立ち上げたのは自分だからである。

先日より内定してちょこちょこ社員や内定者と交流するイベントに出たりしているのだけれど、縁あってSB AI部(社員の方で構成されている部活)のSlackに招待してもらった。

SB AI部については@kmotohasが書かれた2日目の記事に詳細があるので適宜参照。

ちょうど11月の上旬くらい、ツイッターを見てると色々な会社が社内アドベントカレンダーをやっていて(しかも結構楽しそう)、もしかしてソフトバンクもやっているのかな?と思ってSlackをバーっと確認。

どうも見た感じ特にその手のやりとりは無かったので、randomのチャンネルで質問してみる。

f:id:komi1230:20191206221846p:plain
実は自己紹介以外でSlackで発言するのが初めてだった。

そうすると、社員の方の何人かが面白そう!とかやってみよ!というような反応をしてくださった。

これはイケるやつだ!ということで、勢いでアドベントカレンダーを作成。

最初は2, 3枠しか埋まらず、入社前から早速やらかしたやつかぁ...と心配していたのだけれど、だんだんと勢いが出てきて、最終的にはビッシリ埋まった。

adventar.org

本当に社員の皆さんと内定者には感謝である。

Lisp人工知能

さてさて、前置きはこのくらいにして7日目の記事の本題へ。

AIもとい人工知能は少し前から話題になっていて、ソフトバンクの注力事業であるわけだけど、この人工知能は最近になってポッと出てきたものではなく昔から長いこと研究されている領域だったりする。

では人工知能はいつから研究されているかというと、1950年台に遡り、1956年にAIに関する世界初の国際会議(ダートマス会議)が主催された。

世界で初めて実用デジタルコンピューターが作られたのが1940年代で、世界で初めての高水準プログラミング言語であるFortranができたのが1954年、これらのことを考えてみればコンピューターの黎明期に人工知能という概念が誕生したと言える。

ちなみに余談として、FortranはFormula Translation(式変形)の略で、昔はマシン言語を直接書いてコンピューターに計算をさせていたのだけど、Fortranはより人間に近い言語をマシン言語に変換する役割(いわゆるコンパイラ)として登場し、「これからの時代はプログラミングをやる必要はない!全てコンピューターが自動でやってくれる!」という強烈な宣伝文句とともに一世風靡し、当時では画期的なものであった。 (出典 : 『プログラマーのジレンマ 夢と現実の狭間』スコット・ローゼンバーグ著)

さて、そんな時代の中で開催された国際会議で、ジョン・マッカーシーという数学者が世界で初めて「人工知能」という言葉を定義した。

このジョン・マッカーシー人工知能やコンピューターサイエンスにおいて偉大な貢献をした人であり、実は「人工知能の父」と称されるあのマービン・ミンスキーもこの会議を機にAI研究者を志し、その3年後にはMITでマッカーシーを師事することになった。

さて、この人工知能という言葉を生み出したマッカーシーこそ同時にLispを生んだ人物なのである。(Lisperの間ではマッカーシーLispを"発見した"と言う)

この初めてLispを提案したのがこの論文

マッカーシーの数多くの貢献の中でも特に有名なものとして、現在ほとんどのプログラミング言語で実装されているif文やガベージコレクションなどがある。(実はかつてのFortranにはif文がなくgoto文しかなかった)

if文もガベージコレクションも、高水準プログラミング言語として初めて実装されたのはLispなのである。

これ以外に、マッカーシーはタイムシェアリングシステムというものも提案しており、これは何かというと現在でのアプリケーションサービスプロバイダ、つまりSaaSで、現在流行っているビジネスモデルを半世紀以上前にマッカーシーは実現可能だと提案していた。

...とまあマッカーシーがすごいという話はある程度にしておいて、人工知能の歴史やLispがどのような興隆を経てきたかという話については以下の記事が非常によくまとまっているのでぜひ読んでもらいたい。

tech.dely.jp

人工知能は2回の冬の時代を経て現代でまた興隆を極めているのだけれど、現代の人工知能技術の中核をなす技術であるディープラーニングは膨大な数値計算によって成立している。

かつての人工知能は記号推論によって解決しようとしていたのだが、現実世界でも適用可能な妥当なルールベースを作りこむことが難しいことが分かってきたので、その後は大量のデータからボトムアップ的に学習して自動的に知識表現を獲得してやろうという流れになり、ニューラルネットなどの機械学習手法が発達した。

なので大きな枠組みで見れば現在のディープラーニングも知識をどのように計算機で実現するかという話であり、それを抽象的なレイヤで行うかボトムアップ的に行うかでアプローチが異なるだけだったりして、本質的には変わらなかったりする。

もうPython卒業しようよ

コンピュータの性能向上に伴い、プログラミング言語にはアルゴリズムの表現力と開発効率が要求されてくるようになった。

それにより、今はPythonが最も注目される言語となった。

ただPython単体だとスクリプト型言語のために実行速度が非常に遅く、ディーブラーニングのような膨大な量のパラメータを逐次更新していくようなアルゴリズムに対しては実行が遅すぎるので、ライブラリとして一部の処理をC言語のような高速な言語に実行させることによってアルゴリズムの表現力を保持しつつ実行速度の担保をするような手法が取られるようになってきているのが昨今の流れとなっている。

ここでのライブラリというのがTensorFlowだったりPytorch、Keras、今は亡きTheanoやCaffeなどである。

パッと見だとこれらのアプローチは良さげだと思われるが、例えばエッジコンピューティングのような組み込みなどのものだとPythonを主軸にしたやり方ではメモリを圧迫しすぎてしまうのでプログラムを乗せることができず、結局アルゴリズムの実装しやすさと開発効率を重視したのに最終的にC言語で実装し直すという本末転倒的なことになる。

一応PythonをネイティブコードへコンパイルするCythonというものもあったりするが、結局Python本来が持つアルゴリズムの実装しやすさに制限をかけることになり、結局何がしたかったんだろうという感じになってしまう。

このような考えのもと、機械学習アルゴリズムの社会実装への需要が高まっている昨今でいつまでもディープラーニングアルゴリズムの実装はPythonで実装するしかないというのは少々前時代的という考え方にすら至りうる。

現在ではプログラミング言語も様々な開発が進んでおり、個人的にはJuliaという言語に注目していて、Python以上に柔軟な文法を持ちながら高速である(少なくともPythonの10倍くらい)

このような背景も考えるとPythonに頼り続けるのは限界があると思うし、次の時代を見据えるならPython以外の言語に視野を広げる必要があると思う。

Lispという一つのアプローチ

さて、ここで一つの解決法として自分はLispはアリなのではないかと考えている。

Lispはもともと超絶柔軟な文法を持っており(もはや文法が存在しないと言っていい)、強力な抽象化機構を持っているのでアルゴリズムの表現性という点ではあらゆる言語の中で強力なものである。

柔軟な文法とはどういうことかというと、Lisp自分で構文を定義することができるのである。

例えば、Pythonでは関数を定義する際に

def hoge(x, y):
    return x + y

のように関数を定義することはできるが、このPython本体がもともと持っている関数定義defを自分で定義することはできず、my-defmy-ifのようなオリジナルの構文を作ることはできない。

Lispはマクロという機能を使うことによって自身の構文を定義することができ、言ってしまえばその場で自分オリジナルのプログラミング言語を作ることができる。

具体的に、Lispのマクロを使えばSQLやHTMLなどもLispのコードとして実装することができる。

どの言語を使うかで何を言えるかが変わってくる。

Lispは主に関数型プログラミングパラダイムを採用していて、高階関数ラムダ式をサポートしているため、Lisp本来が持つ柔軟さもあって一つにアルゴリズムを実装するにしてもC言語Lispではコードの量が10倍以上違ってくる。

このようなLispではあるが、速度はどうかというと、C言語並みか場合によってはCより速くなる。

今はLispはあまり有名な言語ではないが、かつてはコンパイラの研究でLispが先頭を走っており、Lispマシンというものも存在したほどであり、昔から高速なプログラミング言語の部類にある。

中には強烈な名言もあり

Cで書くコードの方がCommon Lispで書くより速いって人がいたら、それは彼のCの技量が高すぎるってことだね。

“If you can't outperform C in CL, you're too good at C.” — Eric Naggum

Lispが高速な言語であることを物語っている。

このCより高速なLispのコードを書くことについては以下の深町さんの記事が非常に面白いので参照。

blog.8arrow.org

Lispの高速さを物語るプロダクトとして、この深町さんが実装したWooというHTTPサーバーはあらゆるHTTPサーバーの中でも最高クラスの速度を誇っている。

f:id:komi1230:20191207001656p:plain

Lispがこのような高速さを担保している理由として、OSSSBCLというネイティブコードまでコンパイルする超優秀なコンパイラがあり、これに最適化オプションをつけることによって超高速なコードを書くことができる。(自分はSBCLのコミッター)

最適化に際して、Lispの処理系にはプロファイラという機能がついており、プログラムのどの部分が時間がかかっているかを調べることができるのでボトルネックの発見に役立つ。

そうしてこの時間がかかっているブロックに対して細かく最適化オプションをつけることができ、他のプログラミング言語に比しても最適化を軽快に行うことができる。

この最適化については前に記事を書いた。

komi.hatenadiary.com

このようにLispは速度も高速で柔軟性を持っている言語だが、もう一つ絶対に語っておかなければいけないのがSLIMEという開発支援ツールである。

Lispの特徴として括弧がたくさん現れるが、Lispでは括弧の塊それぞれが一つのソースコードとなっている。

これの何がすごいかというと、ソースコードの一部だけをコンパイルして実行することができる。

f:id:komi1230:20191207003351p:plain

この機能があると、手元で書いたソースコードを即時コンパイルしてその場でテストすることができ、実装からテストまでのサイクルを高速で回せるようになるのである。

以上、Lispは高速で柔軟な文法を持ち強力な開発支援ツールを持ったすごい言語だと言える。

なぜLispは人気がないのか

このようなプログラミング言語として強力過ぎる魅力を持った言語であるのに、なぜLispはユーザーが少なく人気がないのか。

これに関してはさっぱりわからない。

自分はLispを書き始めて数年経つが、特に文句はないし、むしろC言語PythonJavaScriptなどの他の言語を触るたびに「なぜLispのような機能がないのか」「なぜLispより圧倒的に遅いのか」とイライラさえする。

もっとLispは評価されるべきだしLisp界隈が盛り上がっていくべきだと思っている。

ただ一点、指摘することがあるとすれば、ライブラリの開発が全体として勢いがないというのはある。

これはLispユーザーが少ないことによる副次的な影響なのだが、ライブラリの開発があまり活発に行われていないために死んでしまっているようなライブラリも数少なく、ソースコード自体はバリバリ現役でもドキュメントが古すぎて存在しない(もしくは埋もれてしまっている)ということがある。

Lispのこれからのために

そのようなLispであるが、個人的には最近どんどん勢いが上がっている気がする。

先述の深町さんがCommon LispでWeb系ライブラリを作りまくってLisp × Webの雪原を踏み荒らしまくった結果、Lispユーザーがグッと増えたような気がする。

それを見て、自分は自分も得意分野を盛り上げて行こうと思うようになった。

先日もIBMでAIリサーチャーをやっているMasataro AraiさんからNumclというライブラリを一緒に開発していかないかとお誘いいただいた。

これはPythonのNumpyにある機能をCommon Lispライクに実装しようというプロジェクトで、自分は機械学習系・数値計算系の人間なので、このようなプロジェクトは非常にワクワクしている。

さて、数値計算機械学習系のプロダクトを開発するにあたって、何かとデータを可視化させるツールというのは必要になる。

一応Common LispにはCL-Plotのような可視化ツールは存在するが、だいたいが2次元プロットで、3次元プロットや散布図、ヒストグラム、ヒートマップのような機能はない。

ということでMatplotlibのようなプロッターをOSSとして自分で作ることにした。

github.com

名前はKaiとし、ハワイ語で海という意味を持つ。

現在まだ開発途中であるが、すでに2次元プロット程度ならできるようになっている。

f:id:komi1230:20191207005216p:plain
Kaiでプロットしてみた図

まだ依存関係を記述するファイルなどを整えていないのでプロダクトとしてしっかりリリースはできていないが、ライブラリとしての体裁が整ったらまた追って正式にリリースの報告を出そうと思っている。

自分はLispを愛しているが、これからどんどん盛り上がっていけるように自分にできるコミットをどんどんやっていこうと思う。

これからLispの時代が来るように、一つ一つできることを積み上げていこうと思う。

終わりに

Softbank AI部のアドベントカレンダーの記事だったが、結果的にただのLispの宣伝になってしまった感がある。

ただ、本当に自分はこれからのAI系プロダクトではLispは一つのアプローチになると信じているので、これからもコミットしていこうと思う。

それではお疲れ様でした。

興味を持った人に読んでほしいもの

Lispをちょっと始めてみようかなという人向けの記事。

qiita.com

Common Lispの勉強で、入門として非常に良い本。

www.oreilly.co.jp

Y Combinatorの設立者でありLisp界のゴッドファーザーポール・グレアムのエッセイ。

Amazonのリンク

gist.github.com

SLIMEからOpenGLを触れるようにする

Lispの開発においてSLIMEは必須のものであり生命線。

これがあるとないとでは開発効率は10倍以上は変わるだろうし、LispLispたらしめている要因の一つであるのがSLIMEなんだと思う。

そんなSLIMEを使って現在は開発を進めているのだけれど、実は一つ問題が発生していた。

現在趣味でCommon Lispでグラフィック系のものを作っているのだけれど、SLIMEからだとウィンドウが立ち上がらないのである。

これがSLIMEで長いこと目の上のタンコブであったわけだけど、今回なんとか解決したのでメモ書き。

概要

環境はmacOS CatalinaでSBCLのバージョンは1.5.7.55、Emacsは26.3。

そもそもSLIMEとはEmacs起動中に裏側でLispのREPLを起動したサーバーを立てておいて、それをエディタからそのREPLに通信するという方式をとっている。

今回問題が起きていたのは、SLIMEのREPLサーバーに対しての通信方式で、デフォルトだと通信に個別のスレッドを使用する方法を採用している。

これだとREPL上の白黒のデータを動かすようなスレッドはちゃんと動いてもグラフィック系のような別ウィンドウを立ち上げるものはうまくいかない(個別のスレッドなのでデータのやり取りが途切れてしまう)。

common-lisp.net

なので、SLIMEの通信方式をデフォルトの個別のスレッドを使用する方式からループアプローチに変更する必要がある。

コード

自分の環境では、Emacsの設定ファイルはホームディレクトリに設定ファイルを格納したファイルを入れており、SLIMEはそのディレクトリ内に格納している。

実際、

~/.emacs.d
├── init.el
├── slime/
│   ├── swank.lisp
│   ├── README.md
...

というような構造をとっている。

SLIMEの利用に関してはinit.elの中にて(add-to-list 'load-path (expand-file-name "~/.emacs.d/slime"))としてSLIMEのファイルをロードしている。

さて、今回変更を書き加えるのはこの~/.emacs.d/slime/内のswank.lisp

まず、先ほど述べた通り、SLIMEの通信方式を変える。

このswank.lispの130行目あたりから始まるConnectionのところにて、*communication-style*nilから:fd-handlerにする。

(defstruct (connection
             (:constructor %make-connection)
             (:conc-name connection.)
             (:print-function print-connection))
  ;; The listening socket. (usually closed)
  (socket           (missing-arg) :type t :read-only t)
  ;; Character I/O stream of socket connection.  Read-only to avoid
  ;; race conditions during initialization.
  (socket-io        (missing-arg) :type stream :read-only t)
  ;; Optional dedicated output socket (backending `user-output' slot).
  ;; Has a slot so that it can be closed with the connection.
  (dedicated-output nil :type (or stream null))
  ;; Streams that can be used for user interaction, with requests
  ;; redirected to Emacs.
  (user-input       nil :type (or stream null))
  (user-output      nil :type (or stream null))
  (user-io          nil :type (or stream null))
  ;; Bindings used for this connection (usually streams)
  (env '() :type list)
  ;; A stream that we use for *trace-output*; if nil, we user user-output.
  (trace-output     nil :type (or stream null))
  ;; A stream where we send REPL results.
  (repl-results     nil :type (or stream null))
  ;; Cache of macro-indentation information that has been sent to Emacs.
  ;; This is used for preparing deltas to update Emacs's knowledge.
  ;; Maps: symbol -> indentation-specification
  (indentation-cache (make-hash-table :test 'eq) :type hash-table)
  ;; The list of packages represented in the cache:
  (indentation-cache-packages '())
  ;; The communication style used.
  (communication-style :fd-handler :type (member nil :spawn :sigio :fd-handler))  ;; この部分!
  )

次に、swank.lispの最後の方にて、(defun before-init ...(setq *communication-style* nil)と書き加える。

(defun before-init (version load-path)
  (pushnew :swank *features*)
  (setq *swank-wire-protocol-version* version)
  (setq *load-path* load-path)
  (setq *communication-style* nil))  ;; この部分!

この2つをやれば動くようになった。

f:id:komi1230:20191201184202p:plain

果たしてこれが正しいのかはわからないけど、とにかく動くようになって作業が捗るようになったので良いと思う。

まとめ

今回はSLIMEの設定ファイルを直接いじることによってSLIMEからCL-OpenGLを叩けるようにした。

なんとか解決したので、ようやく開発にスピード感が出てくると思われる。

がんばっていきたい。

自分がAI系のことを勉強し始めてから今まで

この記事は Softbank AI部 Advent Calendar 2019 その2 の1日目の記事。

とうとう来てしまった12月。

もう2019年も終わりに近づいており、今年もこうしてアドベントカレンダーの時期が来たわけである。

Softbank AI部のアドベントカレンダーは2つあり、一つ目が

adventar.org

で、その2が

adventar.org

という具合で、この記事はアドベントカレンダーその2の第一日目となる。

実は、このソフトバンクAI部のアドベントカレンダーの企画を提案したのは自分だったりする。

ツイッターを見てると色々な企業がアドベントカレンダーをやっていて、そういうのを見ながら楽しそうだなぁ....と漠然と思っていたのだけれど、縁あって(まだ内定者だけど)Softbank AI部のSlackに入れもらい、そこでちょろっと話を振ってみたら割とみんなやってくれそうな感じがあり、それで話が進んだというのがきっかけ。

なんやかんやで25日分全部埋まっており、本当に感動しております。

本題へ

とりあえず前置きは置いといて本題。

実はこの記事を書き始めるまでどんなことを書こうかずっと悩んでいて、何か開発したものの公開にしたかったのだけど、ここ最近が忙しすぎるというのと使ってるOSSが謎のバグで色々試行錯誤していたら開発が全く捗らず...

ということで開発したものの公開はまだ別の機会に回すとして、このエントリーでは自分がAI系のことを勉強し始めていたら今までの振り返りをしようかなと思う。

AI系の勉強の一番最初

自分がAI系の勉強、特に機械学習を始めたのは学部1回生の春休みとかだったと思う。

大学に入ってからはずっとプログラミングをやっていて、ずっとC言語CUIのゲームを作ったり色々していた。

そんな中、ちょうど2017年の頭くらいにゼロから作るディープラーニングが発売され、当時もメディアがAIガーと騒いでた時期だったので自分もなんとなく勉強しようと思い、その本を買ってAI系の勉強をスタートした感じである。

ゼロから(ryを始めるに際して初めてPythonを使い始めたわけだけれど、ぞれまでずっとC言語を使っていたので、なんと素晴らしい言語なんだろう!!!!と感動した記憶がある。

ゼロ(ryを終えたあとはもっと色々勉強しようと思い、そこでPRMLを読んだり当時松尾研が翻訳していたIan GoodfellowのDeep Learningを読んだりして勉強を進めていた。

これがだいたい学部1回生の春休みから2回生の6月くらいまでの話。

日立での研究

この頃、実家からの仕送りが減り始めたのでバイトをする必要が出てきて、そこでバイト先を探していた。

京大には生協が斡旋するバイト紹介のサイトがあるのだけれど、そこを見てみたら日立製作所 未来課題共同探索部門というのが引っかかっているのを発見。

内容は良くわからなかったけどとりあえず面白そうだったのでなんとなく申し込んでみた。

ものすごく簡単なノリだったが、実はこれが自分の人生を大きく変えた出会いだった。

早速面接をしようということになり、日時を決めて指定されたオフィスに向かう。

そこで面接で出てきたのはスキンヘッドの強面なお兄さんで、自分は正直かなりビビりながらの話していた。

ちなみに余談だが、このスキンヘッドの方はのちの上司となる方で、同時に自分が心の底から尊敬しており、今自分がEmacsを使ったりしているのはその人からの影響がものすごく大きかったりする。

さて、面接の内容としては多岐にわたって、

  • CS : 配列とポインタはメモリでどのような挙動の違いがあるか
  • 機械学習のこと : CNNにおけるパディングはデータ構造に対してどのような効果を与えるか
  • 今までのプログラミング経験 : 言語やGitHubの使用経験など

あたりを聞かれた。

数日後、採用の連絡をもらい、晴れて日立でアルバイトすることになった。

自分はそれまでそのバイトはちょっとした開発をするものだと思っていたのだけれど、実は普通の研究員で、ガッツリ機械学習の研究をするというのが業務内容だった。

で、周りの学生研究員はというと、だいたい修士か博士の人で、なぜかそこから飛んで学部2年の自分がいるという状況で、周りのレベルの高さに震えながらのアルバイト環境だった。

日立でのテーマはいわゆるAIを実現するというやつで、そこから付随するテーマ(転移学習やNLP、計算軽量化とか)を各学生が論文サーベイと実装をして社員向けに発表をするというのが主な業務の進行フローという感じ。

自分に割り当てられたのは強化学習だった。

そこから約一年間、強化学習関係の論文を無限にサーベイしては面白そうなものを追試実装、数値比較をしてネットワークの工夫やデータの入れ方を工夫するなど、本当にちゃんと研究していた。

研究環境は非常に良く、当時は高価だったGeForce 1080Tiを3つ並列で繋げたり、ラズパイ100個並列コンピューティングもできたし、ソファと冷蔵庫は完備、コーヒー飲める、めちゃくちゃオフィスきれい、という感じで最高だった。

そこでぼーっと論文を読んで好きなものを実装しているだけでお金がもらえるというので、ホントに素晴らしい環境だったと思う。

留学

せっかくの大学生活ということで、留学したい気持ちが出てきたので、留学することに。

その際、留学資金の確保のために強化学習の本を書いてクラウドファンディングで売ったりした。

1週間くらいで30万くらい集まり、なんとか留学(スイス)に行けるだけの資金が用意できた。

ちなみに当時の本がこちらなので、良ければご参照くださいませ。

github.com

留学先では主に機械学習系の授業やCSの授業をとったりした。

現地ではハッカソンに出たりして、色々楽しく勉強していたと思う。

帰国、就活

その後、帰国してから今までの云々については就活エントリーとかを書いたのでそちらを参照。

komi.hatenadiary.com

自分がソフトバンクを選んだ理由として、事業的にめちゃくちゃ面白そうだったので、そこにグッときたというのが率直なところ。

エンジニアリングの方ではエンジニア一般のイメージとしてはパッとしないかもだけど、そこらへんに関してはこれから自分が盛り上げていくつもりなので、がんばっていこうと思う。

ちなみに現在の取り組みとして、あまりAI系のことはしっかりやっておらず、どちらかというと開発方面のことばかりやっている。

今はアイフルでバイトをしており、画像認識のシステムの開発などもしたりするが、どちらかというとそういう細かい部分のチューニングをしているよりプロダクションのコードを書くことの方が多く、そういう意味でフロントからサーバー、インフラ、機械学習系、PMをこなすフルスタックエンジニアとして奔走しているのが最近の取り組み。

あと、今は卒業研究で画像の超解像化をやっており、GANを使って云々するというようなモデルを実装したりしている。

f:id:komi1230:20191201130042p:plain

f:id:komi1230:20191201130106p:plain

画質の粗い画像を高画質化するというのが今AI系としてやっていること。

基本的にはGoogle Colab上でコードを書いているのだけれど、そのうちGitHub等で公開する予定。

雑感

自分はAI系の人なのかというと若干謎なのだけれど、機械学習アルゴリズムの実装もカバーしているエンジニアという雑な括りだとちょうど自分が当てはまるのではないかなぁと思う。

自分でも色々作っていて、例えば前に作ったGunosyの記事をスクレイピングしてきてそのカテゴリーを判別するモデルを前に作ったり。

github.com

内部ではガウシアンナイーブベイズを採用していて、これについては解説記事も前に書いた。

qiita.com

他にもカルマンフィルターの記事とかも。

qiita.com

正直、あまり自分は何かの分野に固執しているわけではなくて、単純に自分が面白いと思ったことをやり続けているだけで、もしかしたら数年後は全然違うことをやっているかもしれない。

ただ、今は機械学習のシステムをプロダクトとして仕上げるのが面白いので、まだまだAI屋さんとしてがんばっていこうと思う。

来年からはソフトバンクで面白い仕事をどんどんやっていきたい。

Common Lispの外部ライブラリと名前空間

今回はCommon Lispのプロジェクトでの外部ライブラリの利用について、ちょっとしたメモ書き。

実はちょっと前にCommon Lispでの依存関係を処理してくれるASDFについて記事を書いたのだけれど...

komi.hatenadiary.com

この記事ではローカルでライブラリを作成した場合の依存関係を書いただけだったが、外部ライブラリの利用やdefpackageによる名前空間の衝突についてはカバーしていなかったので、今回をそこらへんを。

外部ライブラリ

例えばAlexandriaなどのものは.asdファイルのdepends-onに書けばよい。

(defsystem "hoge-foo"
  :version "0.1.0"
  :author ""
  :license ""
  :serial t
  :depends-on ("alexandria"
               "woo")
  :components ((:module "src"
                :components
                ((:file "main"))))
  :description ""
  :in-order-to ((test-op (test-op "hoge-foo/tests"))))

ここに書いておいて、REPLで

(asdf:load-system :hoge-foo

とやればロードができる。

パッケージ名の工夫

GitHubとかをのぞいているとdefpackageにて

(in-package :cl-user)
(defpackage #:hoge
  (:use :cl))
(in-package #:hoge)

(defun ....

というようにdefpackageのあとにシャープ#をつけているものがある。

これは、Common Lispではパッケージ名を他のシンボルと同様に見てしまうことがあるらしく、それを避けるためにシャープ#をつけることによって他のシンボルとは別物として扱ってもらえるようにするらしい。

プログラム中のライブラリの使用

外部ライブラリの利用について、ASDFの中で依存ライブラリの定義をしてあれば名前空間つきの関数の利用は可能らしい。

例えば.asdの中のdepend-onにalexandriaを書いてあれば

(in-package :cl-user)
(defpackage #:hoge
  (:use :cl))
(in-package #:hoge)


(defun hoge ()
  (alexandria:map-iota #'print 10))

というようにdefpackageで特にimportの指定をせずとも関数を使うことが可能である。

ただ、名前空間が衝突してしまうことを避けるためだったり依存ライブラリの利用している関数を明示的に示すため、defpackage内に:import-fromで使うものだけimportするのが一般常識というやつらしい。

上記の例だと

(in-package :cl-user)
(defpackage #:hoge
  (:use :cl)
  (:import-from :alexandria
                :map-iota)
(in-package #:hoge)


(defun hoge ()
  (map-iota #'print 10))

とする感じ。

このようにdefpackage内で:import-fromで依存パッケージを書いておけば[名前空間]:[関数名]のように名前空間を書かずに良くなってコードも微妙にスッキリする。

ローカル同士の依存関係

外部ライブラリの使い方は分かったし名前空間の処理を上手いことやる方法は分かったので、ではローカル同士の依存関係はASDFでどのように扱うのだろう。

例えばhoge.lispfoo.lispを利用している場合、

hoge.lisp

(in-package :cl-user)
(defpackage #:hoge-package
  (:use :cl)
  (:import-from :foo-package
                :hoge)
(in-package #:hoge-package)


(defun my-hoge ()
  (format t "Hello !")
  (hoge))

となっていて、foo.lisp

(in-package :cl-user)
(defpackage #:foo-package
  (:use :cl)
  (:import-from :alexandria
                :map-iota)
(in-package #:foo-package)


(defun my-foo ()
  (map-iota #'print 10))

となっているような状況を考える。

この場合、ASDFには

(defsystem "hoge-foo"
  :version "0.1.0"
  :author ""
  :license ""
  :serial t
  :depends-on ("alexandria"
               "woo")
  :components ((:module "src"
                :components
                ((:file "hoge" :depends-on ("foo")))
                ((:file "foo"))
  :description ""
  :in-order-to ((test-op (test-op "hoge-foo/tests"))))

というように:componentsのところの内部の各ファイルについて依存関係を書いてやればオッケー。

まとめ

今回はライブラリの利用やローカルファイル同士の依存関係について簡単にまとめた。

多分恐らくこれでASDF経由での依存関係のまとめは網羅したと思われる(多分)

割とASDFの設定は慣れれば意外と簡単かもと感じてしまうけど、昔は色々良く分かってなくて困ってたので、実は学習コスト高いのでは?という顔をしてる最近。

ちなみにCommon LispにはQuicklispなるものが存在するが、これについてはPythonでいうpip的な感じだと思う。

ASDFはどちらかといえばpipenvくらいの感じ。

Quicklispは手元で簡単に遊ぶための手軽なライブラリマネージャ的なやつで、ASDFがプロジェクトのまとめをやってくれるようなイメージ。

まあなんであれ、とりあえず今回はASDFをある程度使いこなせるようになったので、これで晴れてCommon Lispのプロジェクトをサクサク作ることができるようになったと思う。

ちなみに自分はまだRoswellについてはしっかり触ったことがないので、Roswellについても使えるようになりたいなぁなんて思ったり。

とりあえず今日はここまで。

CLOS再訪

全人類wanimaを聴くべき。

wanima聴いてるとよくわからんけどめちゃくちゃ元気が出てくる。

自分は作業するときいつも音楽を聴いているのだけれど、あまり甘ったるいJ-POPなどは聴かずJazzやクラシック、インストあたりを聴いてる。

ただたまにロックなども聴きたくなるわけで、それで先ほど色々Apple Musicを漁ってたらwanimaがグッときた次第なのである。

ということで全人類はwanimaを聴くべき。毎日元気になれる。

本題へ

さて、今日はCLOSについて。

数値計算するとき計算結果のためのplotterが欲しくなるわけだけど、Common Lispではあまりそういうのがないらしい。

一応Gnuplotのラッパーはあるが、PythonでいうMatplotlibのように完全にまとまったものはどうやら存在しないらしい(もしかしたら自分のリサーチ不足かもしれないが)

そんなわけで自分で何か作れるかなと思ってちょっとQiitaの記事とかを色々漁ってたら、どうやらCL-OpenGLとかいうOpenGLのラッパーが存在するので、それを使えばいけそう。

実はCommon Lisp用のPlotterを作る計画は割と結構前からあったのだけれど、なんやかんやで忙しかったりまとまった時間を取れなくて開発が後回しになっていた。

で、最近ちょっと取り組み始めた次第。

基本的にこのページを参考にしている。

qiita.com

この手のグラフィック系のものは基本的にオブジェクト指向型で書いていくのがデフォであるわけだけど、Common LispにはCLOSというCommon Lisp Object Systemというのがあり、今回はそれで書いていく。

そんなわけでCLOSを使うわけだけど、つまづいたポイントをざっとメモしていく。

Common Lispのクラスシステム

SBCLの実装を眺めているとCommon Lispにおけるデータ構造はビルトインクラスとして色々用意されていることがわかる。

github.com

さて、REPLで動作クラスが何かを調べる際は

(class-of 10)
;; => #<BUILT-IN-CLASS SB-KERNEL:FIXNUM>

(class-of "hoge")
;; => #<BUILT-IN-CLASS SB-KERNEL:SIMPLE-CHARACTER-STRING>

(class-of #(1 2 3))
;; => #<BUILT-IN-CLASS COMMON-LISP:SIMPLE-VECTOR>

などすればわかる。

Common Lispでクラスを定義する際は

(defclass クラス名 (スーパークラス)
  (スロット[クラスに所属するもの])
  (オプション))

というような形式となっており、具体的に

(defclass a-class ()
  ((a-slot :initarg :a)))

といった具合。

このクラスのインスタンスを生成して動作クラスを確認すると

(class-of (make-instance 'a-class :a 10))
;; => #<STANDARD-CLASS COMMON-LISP-USER::A-CLASS>

とわかる。

また、クラス内のスロットの取り出しは

(slot-value (make-instance 'a-class :a 10) 'a-slot)
;; => 10

で可能。

メソッド

PythonでクラスというとAttributeとメソッドが一緒にいるイメージで、例えば

class hoge:
    def __init__(self, a):
        self.a = a

    def foo(self):
        return a

というような具合。

一方でCLOSではクラスはあくまでクラスはスロットの塊を定義するだけで、総称関数をまた別で用意する。

(defclass foo () ())                   
(defstruct foo-struct)                  

(defgeneric foo (obj))          

(defmethod foo ((x foo))
  "foo class!")

(defmethod foo ((x foo-struct))
  "foo struct!")

;; built-in-class float
(defmethod foo ((x float))
  "float!")

;; built-in-class function
(defmethod foo ((x function))
  "function!")

;; foo specifier
(defmethod foo ((x (eql 1)))
  "one!")


(let ((a (make-instance 'foo))
      (b (make-foo-struct))
      (c pi)
      (d #'car)
      (e 1))
  (mapcar #'foo (list a b c d e)))
;=> ("foo class!" "foo struct!" "float!" "function!" "one!")

クラスの使いどころ

そもそも関数型プログラミングのお作法が基本となるLispでクラスを使うのはどんなタイミングか。

これはプログラミング一般の話で、プログラム内に現れるデータを一括で管理したいときに有用なものとなる。

で、Common Lispではクラスを作る方法としては主にdefclassとdefstructureの2つの方法がある。

これらの違いとしてはほぼ同じだが、特徴として

  • defclassは多重継承をするとき
  • defstructureは速い(単一継承はできる)

というようなノリで、機能的には多重継承を使うかどうかくらいが違いとなる。

なのでCommon Lispではたくさんオブジェクトを生成しては捨てるというようなプログラムを書く際は構造体を使うのがベストプラクティスとなる。

では、クラスで何ができるだろう。

これは色々できるが、一つにオリジナルの型を定義できることがある。

Common Lispでの型とは動作クラスのことで、実際に総称関数を定義する際に動作クラスに基づいて挙動を変えることができた。

ちなみに型の定義にはdeftypeを使うこともできる。

(deftype uint () '(integer 0))

(typep 23 'uint) ;; => T
(typep -23 'uint) ;; => NIL

また型ごとに挙動を変えるtypecaseも存在する。

このdeftypetypepで扱う型はもともとCommon Lispのビルトインクラスのものでしかないので、自前で用意したクラスのものを扱うにはインスタンスを作成する必要がある。

ぱっと見だとdeftypeでできることはdefmethodなどでもできそうな感じだが、ここらへんはCommon Lispあるあるで、様々なマクロが用意されていて一つのことを複数のやり方で実現できる。

ここらへんは機能的には細かい違いがあったり速度の違いがあったりするのだろうが、若干よくわからないことがあっても割となんとかなったりする。

とにかくCommon Lispではクラスはこのような扱いとなる。

まとめ

Common Lispは割とクラス周りのマクロが充実しているが、その代わりにそれぞれの違いを把握するのが若干難しく、完全理解にはそれなりの時間を要しそうである。

CLOSのシステムは色々だが、マスターすれば確実にプログラムの表現力がだいぶ向上するはずなので友好活用していきたい。

個人的に、CLOSはJavaのようなガチガチさがなく割とゆるっとしている印象で、割と好印象だったりする(そもそもで自分はあまりJavaが好きではないのだけれど)

とりあえずフワッとCLOSを理解したので開発をがんばっていきたい。

Common Lispの処理系SBCLにおける型と最適化

ここ数日はしんどいスケジュールだった。

11/19(火)に8時間ほど働いた後に夜行バスで東京に移動、11/20, 21でLINE DEVELOPER DAYに2日間参加して夜行バスで京都に戻り、その後8時間労働。

少々この1週間はおおよそ人権がないのではという感じだったが、なんとか乗り切ってゆっくりとした休日を迎えている。

そんな休日ということで今日はのんびり散歩したりしていたのだが、ふとLispの型と最適化方針について気になったので今日は少しそこらへんについて調べていた。

今回のエントリーはそこらへんを簡単にまとめたもの。

結論から

ちなみに結論から言うと、Lispはめちゃくちゃ速い言語である。

ちゃんと型を指定した上で最適化してコンパイルすればC言語と遜色ないレベルの速度が出る。

Lispインタプリタコンパイラの両方で実行ができる言語で、インタプリタで実行した際あまり速度は期待できないのは事実ではあるが。

色々な記事でLispはめちゃくちゃ遅いというように断定するようなものがあったりするが、これはLispについて何も知らないド素人が自分はプログラミング言語について詳しいですよという顔をするために書いたクソ記事である可能性が高い。

実際にこんな名言もある。

Cで書くコードの方がCommon Lispで書くより速いって人がいたら、それは彼のCの技量が高すぎるってことだね。

“If you can't outperform C in CL, you're too good at C.” — Eric Naggum

また、このSchemeのサイトにて川合史朗さんも

普通に(教科書的に)書かれたLispコードは、コンパイルしても確かにCより 数倍遅いでしょう (ソースコード量は数分の一でしょうが)。

C並に速いLispコードを書くためには、いくつも気を付けなければならないことがあります。

  • inner loopでメモリをアロケートしない: Cと違ってLispの場合は暗黙のうちに メモリアロケーションが入ることが多いので、どういう操作だとアロケーションが 入らないかを熟知している必要があります。
  • 型宣言をつけまくる: あいにく、商用のLispコンパイラでもあまり賢い 型推論は行ってくれないみたいで、とにかく鬼のように型宣言をつけまくる 必要があります。
  • safetyを0にすると処理系は救ってくれない:宣言された型とは違う型の オブジェクトを渡すとSEGVしてくれます ;-(

なので、C並に速いLispコードは見た目も安全性もC並になる、というのが 私 (Shiro) の経験です。

このように高速化に際して危険性を孕みつつもC並みに高速化することへの可能性について語っている。

本エントリーではどのように型を指定してプログラムを高速化するかということを簡単にまとめる。

Lispというプログラミング言語

プログラミング言語は型についてシステムとして(静的|動的)な(強い|弱い)型付けという説明がなされたりする。

静的型付けは、簡単に説明すると変数や関数に型を予め定義しておき、その型以外のデータを変数では使えないような型システムのこと。

一方で動的型付けとは、動的に型を付けるという読んでそのままの通り、プログラムを書くときに変数や関数に何が入ってくるかというのが特に決まっていない形を指す。

主に静的型付けとして代表されるのがC言語とかで、動的型付けとして代表的な言語がPythonとか。

また、強い型付けと弱い型付けについて、プログラムでは基本的に演算は型の指定をしてくるが、もし指定ではない型を用いてしまった場合にどのような挙動を示すかについての分類を強い型付けとか弱い型付けとか言ったりする。

具体的に、C言語

int a = 10;
char b = "20";

のようにしたときa + bは整数と文字列の和をとろうとしてエラーを吐く。

しかしJavaScriptだと10 + "20"は結果として"1020"という文字列を返してくる。

型の強い弱いに関して、整数型を浮動小数点型に自動的にキャストするような言語は弱い型付けと言っていいと思われる。

個人的にはここらへんの型システムとプログラミングスタイル(オブジェクト指向型と関数型)については色々言いたいことがあるのだけれど、それについては来月のアドベントカレンダーまで温めておこうと思う。

さて、このような型システムにおいて、Lispは動的な強い型付けの言語であると言うことができる。

動的な型と速度の問題

動的な型を用いた言語は、プログラムの実行に際してその都度に変数や関数の型をチェックしたり推論したりするので、静的な言語に比べて一般的に実行速度で劣る。

動的な型システムを持つ言語ではプログラムの実行に際してユーザーが厳密に指定する必要はないので高速な開発に向いていると言えるが、その分プログラムの実行速度は遅くなるのでこれらの型システムとユーザーの自由度(開発スピード)はトレードオフの関係にあると言える。

余談だが、自分が一番最初に触ったプログラミング言語C言語で、最初はプログラミングとは変数を型とともに宣言してそれをコンピュータに読ませる必要があるものだと認識していたのだけど、そのあとにPythonを触ってこんなにも使いやすい言語が存在するのかと非常に驚いた記憶がある。

Pythonを触り始めて以降、自分はアルゴリズムの実装において基本的に静的な型システムを持つ言語は使っておらず、だいたいLispPythonばかり使っている。

動的な型システムの言語で型を指定して速くする

さて、先述の通り、動的な型システムを持つ言語でプログラムを普通に記述するとC言語のような速度は出ない。

しかし、型の指定を行えばかなりの高速化が狙える。

例えば具体的に以下の階乗を計算するコードを見てみよう。

(defun factorial (n)
  (if (= n 1)
      1
      (* n
         (factorial (1- n)))))

これで10000の階乗を計算すると、実行時間は以下の通り。

CL-USER> (time (factorial 10000))

Evaluation took:
  0.050 seconds of real time
  0.050077 seconds of total run time (0.044013 user, 0.006064 system)
  [ Run times consist of 0.026 seconds GC time, and 0.025 seconds non-GC time. ]
  100.00% CPU
  110,624,296 processor cycles
  4 page faults
  69,740,608 bytes consed

ちなみにコンパイルすると実行速度は以下の通り。

CL-USER> (time (factorial 10000))
Evaluation took:
  0.038 seconds of real time
  0.037463 seconds of total run time (0.024041 user, 0.013422 system)
  [ Run times consist of 0.005 seconds GC time, and 0.033 seconds non-GC time. ]
  97.37% CPU
  82,108,806 processor cycles
  69,739,648 bytes consed

さて、こいつに型宣言をつけてやる。

まず関数の型について、

(declaim (ftype [typespec] [関数名]))
(defun 関数名 (引数)
  (...))

というような感じで引数と返り値の型を定義する。

今回のケースにおいて10000の階乗を計算するが、今回のSBCLにおいてmost-positive-fixnum4611686018427387903であってそれを超えるとintegerになるので、引数の型をfixnumとして返り値をinteger`とする。

(declaim (ftype (function (fixnum) (values integer &optional)) factorial))
(defun factorial (n)
  (if (= n 1)
      1
      (* n
         (factorial (1- n)))))

これをコンパイルして実行した結果として

CL-USER> (time (factorial 10000))
Evaluation took:
  0.021 seconds of real time
  0.021132 seconds of total run time (0.020203 user, 0.000929 system)
  [ Run times consist of 0.005 seconds GC time, and 0.017 seconds non-GC time. ]
  100.00% CPU
  46,617,370 processor cycles
  69,735,360 bytes consed

うん、スクリプトとして実行した際の実行時間は0.050077コンパイル時の実行時間が0.37463だったので、これらと比較したらだいぶ速くなった。

次に変数の型宣言をしてやる。

これは

(declare (type fixnum 変数名))

としてやれば良い。

また、同時に各出力フォームについても型を宣言してやる。

(declaim (ftype (function (fixnum) (values integer &optional)) factorial))
(defun factorial (n)
  (declare (type fixnum n))
  (the integer
       (if (= n 1)
           1
           (* n
              (factorial (1- n))))))

速度は以下の通り。

CL-USER> (time (factorial 10000))
Evaluation took:
  0.022 seconds of real time
  0.022008 seconds of total run time (0.021223 user, 0.000785 system)
  [ Run times consist of 0.005 seconds GC time, and 0.018 seconds non-GC time. ]
  100.00% CPU
  47,991,386 processor cycles
  69,754,544 bytes consed

パッと見だと前より遅くなっているが、システム側の実行時間はさらに短縮されていることがわかる。

さて、ダメ押しで関数のインライン化をして関数の呼び出しを節約し、呼び出しのオーバヘッドを殺そうと思う。

(declaim (ftype (function (fixnum) (values integer &optional)) factorial)
         (inline factorial))
(defun factorial (n)
  (declare (type fixnum n))
  (the integer
       (if (= n 1)
           1
           (* n
              (factorial (1- n))))))

これはコンパイラ側に関数を埋め込むことになり、インライン展開されたコードが実行されればされるほど効果的となる。

Evaluation took:
  0.026 seconds of real time
  0.026104 seconds of total run time (0.025496 user, 0.000608 system)
  [ Run times consist of 0.009 seconds GC time, and 0.018 seconds non-GC time. ]
  100.00% CPU
  57,044,664 processor cycles
  66,851,024 bytes consed

今回は単純な再帰アルゴリズムなのでインライン化の恩恵を受けてないが(むしろ遅くなってる)、これは大きなプロジェクトなどでじわじわと効いてくる最適化なので重宝したい。

まとめ

今回はSBCLでコードを高速化する手法についてざっとまとめた。

最終的に最初のインタプリタとして実行した際の倍以上の速度を獲得することができた。

Lispはかなり速い部類の言語だと思うし、そもそも言語としてアルゴリズムの表現性が高いのでコードデザインの部分として最適化の余地はいくらでもあるように思える。

自分もまだまだCommon LIspについては未熟であるが、もっと極めていきたい。

ClojureのソースコードをClojarsにpushする

今日もClojureの進捗生産。

普段から

  • コードを書く
  • わからんことが発生する
  • 色々ググって調べる
  • 問題解決
  • 問題の発生から解決に至るまでのプロセスをブログで記録に残す

というサイクルを徹底していて、なのでブログの更新は自分が何かコードを書いていてつまづいた形跡となっている。

で、このサイクルを実行するには多少まとまった時間が必要なわけなのだけれど、なぜか自分でもよくわからないのだが色々書類作業が発生していて、あまりここ最近はコードを書くことができていない。

ここでいう書類作業とは、内定先の入社までにやるべきhogehogeだったり各所へのメール出しだったり色々。

ホントにこの手の書類作業はツラいものなのでできるだけのんびり一人でコードを書いている時間の方がハッピーなのだけれど、まあ世の中そう簡単にはうまくいかないわけで。

...とまああまり無駄な前置きをせずにさっさと今日やったことをまとめておこうと思う。

Clojarsへのpush

前回のエントリーでLeiningenの:dependenciesに記述されるものはローカルのディレクトリではなくClojarsであることを述べた。

komi.hatenadiary.com

今回はその続きとして、作ったリポジトリをClojarsへpushする。

前準備

まず前提として、今回の実行環境はmacOS Catalinaの最新バージョンである。

なぜここにきていきなり環境の話をするかというと、今回のClojarsへのpushに際してgnupgというものを用いて暗号化する必要があり、これが環境ごとによって微妙に挙動が異なるため。

さて、まず最初に行うこととして、Clojarsのアカウントを作成する。

clojars.org

これを行ったあと、~/.lein/credentials.cljに以下を記述する。

{#"https://clojars.org/repo"
  {:username  "ユーザー名" :password  "パスワード"}}

これを書いて保存すればオッケー。

GPGを準備

次に、先述のgnupgをインストールする。

今回はmacOSなのでHomebrewで行う。

$ brew install gnupg

インストールできたら、

$ gpg --version

などで確認。

このgnupgはいわゆる公開鍵と秘密鍵でhogehogeするものなので、まず鍵を生成しなければいけない。

なので以下のコマンドで鍵を生成。

$ gpg --gen-key

名前とメールアドレスを要求されるのでテキトーに入力しておく。

ここでメールアドレスは別に入れなくてもなんとかなるらしい(多分)(自分は問題なかった)(ホントにこれでいいかはわからない)

それらの入力が終わったらoの大文字のOで終了。

さて、そうしたらターミナルの画面がパッと切り替わって、なんだか文字化けしながら何か選択する感じの画面が出てくる。

f:id:komi1230:20191117003722p:plain
こちらのQiitaより拝借。GPGの画面。

文字化けしていてパッと見だと何を言っているのか理解不能だが、実はここはパスワードを入力させて最後にOKかキャンセルするかを選択する画面らしい。

GUIならまだしもCUIで無理をした結果、環境の違いでこうなってしまったらしい。悲しい。

一応なんかしらの言語設定を施すことでこの文字化けを回避することができるらしいが、わざわざそれをやるのはめんどくさいのでこのまま入力する。

この画面のままパスワードを入力していくと、だんだん * * * *と溜まっていくのがわかると思う。

入力が終わったら矢印キーで下のOKを選択し、Enterで抜ける。

そうすると鍵の生成が終わり。

Clojarsにpushするぞ

鍵の準備が終わったので、早速ClojarsへのCredentialsを暗号化する。

$ gpg --default-recipient-self -e ~/.lein/credentials.clj > ~/.lein/credentials.clj.gpg

これで暗号化できるはず。

さて、ようやく待ちに待ったClojarsへのpushを行う。

自分のClojureのプロジェクトへのリポジトリに移動し、以下のコマンド

$ lein deploy clojars

これでできる。

バージョン情報やプロジェクト名などはproject.cljから勝手に読み取ってくれる。便利。

pushする際に名前とパスワードが求められるので、入力。

これにて終了。

雑記

このデプロイコマンドを打つと勝手にtarget/が生成されるが、これが自分のGitHubリポジトリへのpushに邪魔だという人は.gitignoreにて

**/target/

と書いておけば全階層のtarget/ディレクトリが無視される。

まとめ

今回はgpgを用意してClojarsへのpushまでを行った。

これにて自分のプロジェクトがClojarsに乗っかった状況になるので、Leiningenで:dependenciesに書けば読んでくれるということになる。

実は今回のエントリーのモチベは、前のエントリーにて発表したClojure機械学習ライブラリのMoeにある。

Moeの進捗について、まだプロジェクト開始から数日しか経っておらず、これに加えてなかなか開発する時間が取れていないのでまだ全然なのだが、今日先ほど入力データのバリデーションを行ってくれるような簡単なユーティリティ関数を用意したので、それをまずClojarsにあげてみて確認したというのが今回の背景にある。

進捗はダメダメだが、その過程でClojureのお作法について色々学んでいっているので数ヶ月後にはかなり成長しているだろうことを期待。

ではでは、本日もお疲れ様でした。