EmacsでClojureの開発環境を整えるためにCIDERを入れる

なんだか最近急に寒くなってきた。

先週までジーパンにTシャツ一枚みたいな軽装だったのに今日は普通にジャケットを来て外に出てた。

多分このまま気温が急降下するんだろうなと思って勢いでダウンジャケットなどをポチり、明日か明後日には届くらしいのでちょっと楽しみにしている。

夏服よりも冬服の方が好きなので冬のコーディネートは全力で楽しんでいく予定。

そんな最近なんだけれども、この頃Clojureに興味を持ち始めてちょっと勉強してみたところ割と手に馴染んだので、本格的にClojure²手を出してみようかなと思い始めた。

で、そのためにClojureの開発環境の構築を行っていたわけだけれど、案の定やはり手間取ってしまった。

一応なんとか解決したので今回はそれの作業メモとしてのエントリー。

最初に

とりあえずGoogleの検索ボックスにClojure 開発環境 macと入れて検索してみたらそれっぽい記事があった。

qiita.com

一応すでにLeiningenはHomebrew経由で入れてあってREPL上ではClojureは触っていたのだけれど、Emacsでの開発環境を整えていなかったのでそこらへんを確認すると

の3つがあるらしい。

ここらへんがそれぞれ何なのかは先ほどの記事を参照してもらうとして、とりあえず一つずつ入れていく。

まずはじめに、テキトーにhoge.cljみたいなファイルを作って、中にテキトーなコードを書いておく。

(let [long-long-var 10
        v 99])

(def m {:some-key 1
              :key2 10})

(defn hello []
  (println "Hello World !"))

これをEmacsで開いてみるとシンタックスハイライトは効いておらず、全部白黒。

さて、M-x package-install RET clojure-mode RETとしてClojure-modeを入れてみる。

再起動してもう一度コードを開いてみるとちゃんとシンタックスハイライトもついている。

先ほどのコードの中で2行目のv 99])のところにカーソルを合わせ、C-c SPCとするとインデントを整えてくれる。

うん、すごい。Clojure-modeすごい。すごすぎてちょっとキモい。

とりあえずClojure-modeの確認はできたので今度は2つ目のCIDERを入れる。

先ほどと同様にM-x package-install RET cider RETと打ち込む。

ここで事件は起こった....

Emacs何もわからんになった

CIDERをインストールしようとすると

Package 'queue-0.2' is unavailable

というメッセージ。

よくわからんけど、多分CIDERが依存している別のパッケージが死んでるらしい。

とりあえずGNU ELPAのパッケージをブラウザ上で確認してみる。

ちなみにELPAとはEmacs Lisp Package Archiveのこと。

見てみたらちゃんとqueueは存在する。

elpa.gnu.org

あれ?ちゃんと存在してるのに何でインストールできないの?

一度Emacsの設定ファイルの.emacs.d/init.elを開いて、パッケージの参照先を確認。

自分のパッケージ参照についてはこんな感じになっている。

;; パッケージ設定
(require 'package)
(add-to-list 'package-archives '("gnu" . "https://elpa.gnu.org/packages/") t)
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("marmalade" . "http://marmalade-repo.org/packages/"))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
(package-initialize)
(unless package-archive-contents (package-refresh-contents))

多分melpamelpa-stableが衝突したんかなーとか思ってテキトーにコメントアウトしてCIDERをインストールし直すも動かず。

そもそもでgnuの参照がちゃんと見れてるのか不安になり、一度パッケージの参照先をgnuだけにしてみてM-x list-packagesでパッケージのリストを見てみる。

GNU ELPAのパッケージリストと全く一致していないことを発見。

色々調べてみたけど謎だった。

とりあえずこの謎は放置するとして、パッケージの参照先にmelpaなどを追加してみてもう一度M-x list-packagesを見てみると、そこにはqueueがある。

うーん、どういうことなんだろう。

Issueにて同様の問題を発見

困ってどうしようもなかったので、なんとなく最初のエラーメッセージのPackage 'queue-0.2' is unavailableGoogleの検索ボックスに打ち込む。

そうするとCIDERのインストールで詰まっているような内容がちょこちょこ見つかる。

どうやら今回の問題は割と他の人たちもハマっていたらしい。

そこでIssueでのdiscussionを眺めていると、以下のコメントを発見。

github.com

debbugs.gnu.org

どうやらOpenSSLあたりでTLSが衝突していることが原因でつまづいているらしく、TLSの優先順位をつけることによって解決できるらしい。

ということでEmacsの設定ファイルに

(setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")

と書いておけば解決するらしい。

なるほどなるほど。とりあえず試してみる。

設定ファイルのパッケージ周りのところに

;; パッケージ設定
(require 'package)
(add-to-list 'package-archives '("gnu" . "https://elpa.gnu.org/packages/") t)
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("marmalade" . "http://marmalade-repo.org/packages/"))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
(package-initialize)
(unless package-archive-contents (package-refresh-contents))
(setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")

; brah brah brah

と追記して、M-x package-install RET cider RETとしてみる。

ちゃんと入った。めでたい。

早速試してみる。

先ほどのゴミコードを開いてCIDERを立ち上げ、関数定義の部分にカーソルを合わせてC-c C-cとしてみる。

f:id:komi1230:20191016233754p:plain
CIDERが動いてる!

うん、ちゃんと動いてる。しっかりSLIMEみたいに開発ができる。

これで欲しがってた環境が完成した。

まとめ

かなり紆余曲折あって何とかClojureの開発環境を手に入れた。

まさかパッケージが入らない問題がTLSの衝突とは予想外だった(てかなぜ今までその問題にぶつかることなくパッケージが入れられていたのか...)

実を言うと今まで何回かノリでClojureの環境構築に挑んだことがあるのだが、毎回このCIDERが入らない問題で挫折していたので、今回ようやくそれを乗り越えられたのでとても幸せな気分。

よし、明日から開発がんばっていこう。

Emacsで開発するための最小構成

どうやら最近またツイッター上でEmacsがいじめられている。

Emacsユーザーとしては悲しい。

てことでプログラミングで特にエディタのこだわりはないんだけどな〜って人をEmacsサイドに引き込むためにEmacsで開発するための最小構成を公開しようと思う。

欲しい機能

開発に使えるエディタとしての要求を考えたとき、最小構成として求めるのは

あたり。

まあそこまで大した機能ではないのでぶっちゃけVimだろうがVSCodeだろうがなんでも実現できると思う。

今回はこれらを満たしたEmacsの設定ファイルについて考える。

ちょっと余談

先日に友人(@5ebec)と飲みながらちょっとエディタの話になったんだけれども、デスクトップ型コンピュータならキーボード上で全てが完結することになった方がいいのは容易にわかる。

なぜならデスクトップ型だとキーボードの近くにトラックパッドがあるわけではなくマウスにまで手を伸ばさないといけないから。

だけどラップトップ型の場合、キーボードのすぐ下にトラックパッドがあってちょっと親指を伸ばせば届くので、キーボード上だけで全て解決させずともなんとかなる。

これに加えて自分はMacbookが普段使いで、Macbookについてはトラックパッドの感度がかなり良い(エロい意味ではない)ので、トラックパッドはむしろ活用した方がいいんじゃないかと思っている。

Macbookトラックパッドが優秀だしキーボードの打鍵感も好きで、iPhoneなどの他の端末との相性がかなり良いので、自分がなかなかLinux端末に移れない理由にここらへんがあったりする。

Emacsの設定をする

余談はこの程度にしておいて、設定の方を早速やっていこうと思う。

ちなみに今回色々書いていく設定についてはこの記事の最後の方にgistとしてまとめておいたのでコピペする場合はそれを丸ごとパクってくれたら大丈夫だと思う。

ちなみに設定ファイルはホームディレクトリに.emacs.dというディレクトリを作り、その中にinit.elを作って設定を書き込んでいくのがオススメ。

パッケージ周りの設定

まずはパッケージのインストーラなどを設定する。

;; パッケージ設定
(require 'package)
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)
(add-to-list 'package-archives '("marmalade" . "http://marmalade-repo.org/packages/"))
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
(package-initialize)
(unless package-archive-contents (package-refresh-contents))

このmelpaなどはEmacsのパッケージのリポジトリのことで、何かパッケージをインストールする際はここから取ってくる。

日本語オッケーに

次はUTF-8の設定。

;; 環境を日本語、UTF-8にする
(set-locale-environment nil)
(set-language-environment "Japanese")
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-buffer-file-coding-system 'utf-8)
(setq default-buffer-file-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(prefer-coding-system 'utf-8)

Emacsのバージョンが上がってから文字崩れなどは無いようだけど、一応UTF-8の設定をしておく。

バックアップファイルを作らない

Emacsは優しい設計なのでバックアップファイルを作ってくれる。

ただ、あんまりそれをやるとディレクトリが汚れるので今回はその設定を外しておく。

;; *.~ とかのバックアップファイルを作らない
(setq make-backup-files nil)

;; .#* とかのバックアップファイルを作らない
(setq auto-save-default nil)

括弧の自動補完とかソフトタブとか

自動補完とか色々。

;; 括弧を自動で補完する
(electric-pair-mode 1)

;; tabにスペース4つを利用
(setq-default tab-width 4 indent-tabs-mode nil)

;; デフォルトの起動時のメッセージを表示しない
(setq inhibit-startup-message t)

;; 列の番号
(column-number-mode t)

;; 行番号の表示
(global-linum-mode t)

;; 1行ごとの改ページ
(setq scroll-conservatively 1)

;; 対応する括弧を光らせる
(show-paren-mode 1)

;; メニューバーの非表示
(menu-bar-mode -1)

;; ツールバーの非表示
(tool-bar-mode -1)

;; リージョンのハイライト
(transient-mark-mode 1)

;; タイトルにフルパス表示
(setq frame-title-format "%f")

;;current directory 表示
(let ((ls (member 'mode-line-buffer-identification
                  mode-line-format)))
  (setcdr ls
    (cons '(:eval (concat " ("
            (abbreviate-file-name default-directory)
            ")"))
            (cdr ls))))

;; 時刻をモードラインに表示
(display-time-mode t)

;; 別のウィンドウ
(global-set-key "\C-t" 'other-window)

今回別のウィンドウへの移動はCtrl - tで設定。

自動補完

変数名などを自動補完してくれるパッケージを利用する。

;; auto-complete(自動補完)
(require 'auto-complete-config)
(global-auto-complete-mode 0.5)

サイドバー

画面の左部分に自分の今いるディレクトリのファイルが見えるようにする。

;; neotree(サイドバー)
(require 'neotree)
(global-set-key "\C-o" 'neotree-toggle)

ここでキーバインドCtrl - oと設定しておいたので、コントロールキーを押しながらoを押せばサイドバーを出したり閉じたりできる。

ちなみにこれはneotreeというパッケージを使うので、あとでEmacs起動後にパッケージインストールしておく。

コードをエディタ内で実行

VImにはquickrunという内部実行のパッケージがあるのだけれど、これをEmacsに移植したのがquickrun.el。

;; quickrun.elのキーバインド
(require 'quickrun)
(global-set-key (kbd "C-i") 'quickrun)

;; quickrun.elでPythonをPython3に
(quickrun-add-command "python"
  '((:command . "python3")
    (:exec . "%c %s")
    (:compile-only . "pyflakes %s"))
  :mode 'python-mode)

自分は昔にAtomを触っていたときに内部実行のキーバインドcommand + iだったので、未だに内部実行のキーバインドはiじゃないと気持ち悪く感じてしまう。

ということでキーバインドはそのように設定した。

quickrun.elのconfigについて、ターミナル上での実行コマンドを変えることができ、今回は.pyのファイルについてはpython hoge.pyではなくpython3 hoge.pyとするように設定した。

見た目を色々

最後に見た目の問題を。

ここまではCUIでも有効だけれど、自分はEmacsGUI版のやつを使っているのでウィンドウのサイズやカラーテーマを以下のように設定している。

;; 起動時のフレーム設定
(if (boundp 'window-system)
  (setq default-frame-alist
    (append (list
      '(top . 0) ;ウィンドウの表示位置(Y座標)
      '(left . 0) ;ウィンドウの表示位置(X座標)
      '(width . 140) ;ウィンドウ幅
      '(height . 50) ;ウィンドウ高
    )
    default-frame-alist)
  )
)
(setq initial-frame-alist default-frame-alist)

;; color theme
(load-theme 'monokai t)

;; alpha
(if window-system 
    (progn
      (set-frame-parameter nil 'alpha 80)))

;; フォント
(set-fontset-font t 'japanese-jisx0208 (font-spec :family "Source Han Code JP"))

ウィンドウサイズは横140文字×縦50文字のサイズにし、カラーテーマをmonokai、背景の透過度を80%、フォントをSource Han Code JPとしている。

一般に、ターミナルの画面もエディタも透過させるのがオシャレ(と言われている)

パッケージ等をインストール

いくつかのパッケージを使ったのでインストールをする。

Emacsを立ち上げたら、M-x package-installでインストールするパッケージを書く。

M-xはメタキー+xで、EscキーだったりoptionキーだったりAltキーだったり。

今回入れるパッケージはauto-complete-configneotreequickrunmonokai-theme

各種コマンド

Emacsを触るときはCtrlキーが基本的に使われていて、なのでずっと小指を伸ばしている感じになる。

以下に自分がよく使っているコマンドを並べておく。

ファイル操作系

  • ファイル保存 : C-x C-s
  • 他のファイルを開く : C-x C-f {ファイルのパス}

ウィンドウ操作系

  • 今いるウィンドウを閉じる : C-x 0
  • 今いるウィンドウを全面に : C-x 1
  • ウィンドウを上下で分割 : C-x 2
  • ウィンドウを左右で分割 : C-x 3
  • 他のウィンドウに移動 : C-t

サイドバー

  • サイドバー : C-o

終了系

  • コマンド中止 : C-g
  • 終了 : C-x C-c

まとめ

今回は自分が普段使っている設定とか諸々を並べておいた。

多分誰かの役に立つと思う。

以下に今回の設定ファイルを置いておいた。

中には自分がLispを使うための設定とかも書き込んであるので、適度に取捨選択してコピペすれば大丈夫だと思う。

gist.github.com

macOS CatalinaでのHomebrewとSBCL

先日macOS Catalinaがリリースされたので早速macOS MojaveからアプデしてみたところSBCLが動作しなくなった問題が起きたのでその解決策を簡単にメモとして残しておく。

問題

macOSをアプデしてからターミナルで sbcl と叩いてみたところ以下のようなデバッグメッセージを吐いてSBCLが立ち上がらない。

f:id:komi1230:20191009210507p:plain
SBCL起動失敗

ちなみにCLISPはちゃんと動いてる模様。

対策1

まずやったことはコマンドラインツールのアプデ。

macOSがアプデされてもコマンドラインツールはまた別途で自分でやらなきゃいけないらしい(OSのアプデと同時に一緒にやってくれよとか思ってしまう)

$ xcode-select --install
$ softwareupdate --install --all

ちなみにxcode-selectsoftwareupdateなどのコマンドは/usr/bin//usr/sbin/にパスが通ってないと動かないので注意。

ちなみにこれらはやってみたけどSBCLは動かなかった。

対策2

Homebrewで配布しているバイナリはmacOS Mojaveでコンパイルされたもので、どうやらそれがCatalinaでは動作しないようなのでリポジトリを丸ごと持ってきて手元でビルドしてみることに。

www.sbcl.org

ここからソースコードをダウンロードしてくるかGitHubから持ってくる。

github.com

あらかじめHomebrewで入ってるSBCLは動かないので抜いておく。

$ brew uninstall sbcl

そうしてこの中のディレクトリに入ってビルドしてみる。

ちなみにビルドの仕方はINSTALLに書いてある。

$ cd [リポジトリへのパス]
$ sh make.sh

さて、これで大丈夫かと思いきやビルドには古いSBCLのバイナリが必要だという...

Homebrewで配布しているバイナリは動かないのでさあ困った。

対策3

詰んだかなぁと萎えてたらRoswellの作者の佐野さんから助け舟がきた。

そういやRoswell上でSBCL動いてたなってことで、とりあえずRoswellでの動作を確認。

$ brew install roswell
$ ros run

ここでSBCLがちゃんと動くことが確認できた。佐野さんマジ神。感謝。

じゃあRoswellのSBCLのバイナリをローカル環境にインストールすればいいや、と。

~/.roswellに入って色々探してるとビルドのためのスクリプトを発見。

$ cd ~/.roswell/src/sbcl-1.5.7-x86-64-darwin/
$ sh install.sh

これで/usr/local/bin/SBCLが入った。

動作を確認してみる。

f:id:komi1230:20191009212641p:plain
SBCLが動いてる様子

ちゃんと動いてるのでどうやらオッケーらしい。

まとめ

一応これでSBCLが今後とも使えるようになったので大丈夫なんだけれども、未だに残る問題というか謎としてMojaveからCatalinaにOSが変わったおかげでなんかしらの変更があり一部のバイナリについては後方互換性がなくなっているということ。

ちなみにPythonやnode、Julia、Rust(rustc, cargo)、ClojureなどはCatalinaになっても問題はなさそう。

もしかして今回はSBCLだけの問題なのかもしれないけど、macOSのアプデでなんかしらが変わっているらしい。

うーん、謎。

とりあえず動いてるので当面の間は問題なさそうだけど、問題が起きたらそのときまた別途でトラブルシューティングすればいいかな。

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

大学卒業まで残り半年

時間の流れは速いもので、気がつけば学部4回生となって残りの学生生活も半年となっていた。

だから別になんだというわけではないんだけども、今日は内定先のソフトバンクの内定式があり、節目として色々振り返るにはちょうどいい機会だということ簡単に文に起こしておこうかなというモチベで書き始めたのが今回のエントリー。

自分にとっての学業

少々昔の話なんだけれども、自分は大学生以前まではそこまで勉強熱心ではなかったと思う。

別に大した進学校ではなかった(ちょうど自称進学校という表現が適切なレベル)中高では、ひたすら部活(アメフト)に明け暮れていた感じだった。

志望する大学についてもとりあえず慶應あたりのいい感じの大学に行ければいいやくらいのつもりだったので、うまいこと高校からの推薦が取れればいいやという感じ。

そんな中でなんで今京大にいるのかというと、一度京大のアメフト部と合同練習する機会があり、そのときに話した京大生が面白くて、別に京大のアメフト部に入ろうと思ったわけではないけどなんとなく京大に対して憧れを持ち始めたのが一番最初のきっかけだったと思う。

ここらへんについてグダグダ書いていると文庫本ができるんじゃないかってくらい長くなるので割愛するけど、高3の頃は京大に受かるための受験勉強に励んでいた。

結局現役では京大に落ちて浪人することになったわけなんだけども。

さて、自分が勉強を楽しいと思えるようになったきっかけは実はこの浪人中にあって、浪人のときに物理の講師をしていた山本義隆先生。

この先生はホントにすごい人で(リンクを辿ればいかにすごい人かわかると思う、恐らく物理学徒で知らない人はいないはず)、いつも授業中に勉強は楽しいんだぞという旨を伝えつつ楽しそうに授業を進めてくれていて、そのときから自分も大学に入ってもっといろんなことを勉強したいなと思い始めた。

その後無事に京大の理学部に受かって大学生活が始まった。

待ちに待っていた大学生活で、しかも憧れの京大で、どんな感じなんだろうかとワクワクしていたのだけれど、最初の1週間を過ごしての感想が授業が全く面白くないだった。

受験後から入学前にかけて趣味で線形代数を勉強していたりしたけど授業がものすごく退屈で、そんな感じで気がつけば授業に全く出席しないようになっていた。

で、そんな中で自分がのめりこんだのがプログラミングで、大学合格祝いで買ってもらったMacbook Airで色々やり始めていたのでそれに本格着手することにした。

合格後にMacbookを買ってもらったわけなんだけれども、そのときはそういえばプログラミングなるものが存在するらしいけど全く知らないしちょっと興味あるからやってみようかなというモチベで、テキトーなプログラミング解説サイトを見ながらC言語でしょぼいコードを書き始めていた。

ちなみにそのとき参照していたサイトがこれ。

webkaru.net

環境構築の仕方から簡単なUnixコマンドの解説も載っていて、初学者が学ぶにしてはちょうどよかったのかなと今は思う。

ちなみにC言語をやっていたと言ってもポインタとかよくわかっていなかったのでアドレス演算子とか全く知らなかったし、

#include <stdio.h>

int main(void){
    printf("Hello World!");

    return 0;
}

の中身でprintf()以外の部分に関してはナンカスゴイ呪文ナンダナーくらいの感覚だった。

int main(){ ... }でなぜreturn 0;をするのかもよくわかっていなかった。

ただまあとりあえずコードは書けるわけで、なんとなくやりたい計算とかはできるのでそれで素数を羅列するようなコードを書いたりとかして遊んでいた。

そうこうしているうちに他の言語にも手を出してみようと思い、そこで始めたのがPythonだった。

多分この言語選択が自分の学生生活を大きく変えたものだったと思う。

ちょうど当時は『ゼロから作るディープラーニング』が出版されたばかりで、AIとか人工知能という単語がバズり始めていた時期だったので、そのディープ本を買ってひたすら独学で機械学習を勉強していた。

そんな中で、産学連携の関係で日立製作所が京大のキャンパス内に基礎研究所の一部を置いていて、そこで学生研究員を募集していたので応募してみたところ合格をもらい、学生研究員として日立の基礎研で研究をし始めた。

ちょうどこの日立で働き始めたのが学部2回生の6月頃だったと思う。2017年の6月。

日立では主に強化学習の研究をしていて、学部3回生の7月ごろまで色々研究をさせてもらった。

周りの学生研究員は修士の人ばかりだったので、そんな中で学部生として同じく研究に携われたのは良い経験だったと思うし、当時の上司には非常に感謝している。

...とまあ昔話はここら辺にしておいて、本題に話を戻すと、自分は大学という学術機関にどっぷり浸かっていたというわけではない。

日立や他のAIベンチャーなどの企業サイドに身を置いている時間が長かったので、あまり学部生としてあるべき姿(ちゃんと授業を聞いて予習復習をしっかりやって...的な)は全くもって体現できていない。

ただ、授業はサボっていたわけだけど学んでいないということではなくて、むしろかなり積極的に勉強していたと思う。

日立での強化学習の研究に必要なことは機械学習系の概念は全部勉強したし、上司にコードレビューをしてもらっていたので良いコードの書き方やメンテナンス、リファクタリング諸々も学んだ(ちなみにこのときEmacsを布教された)(Emacsはいいぞ)

かなり特殊な状況下だったけど、大学生という時間を使って好きなことをちゃんと勉強できたのかなと思う。

こういう背景のもと、自分が考えるに、大学は自分の勉強したいことを勉強する環境という認識が一番いいのかなと思う。

大学から提供されるものを適宜取捨選択し、その中で自分が必要としてることだけを掴み取っていけばいいのかな、と。

大学生活は基本的に自由で縛りがないもので、最低限のことをやっておけば誰も怒らないし、なんなら法律さえ守ればなんでもオッケーみたいな空気があるわけで、全て自主性に任されるからこそ、いかに主体的になって学びたいことを学べるかということが大学における学業の本質なんだと思う。

ちゃんと授業に出席して勉強するのもいいし、自分のように企業などの学外の組織に身を置いて付随する必要なことを勉強するのもいいし、そこらへんは各自の選択のもとになんでもいいと思う。

学びたいことを好きに学ぶ、それが学業との向き合い方で、それが勉強の楽しさなのかなと今は考えている。

研究とエンジニアリング

今日の内定式では社長や副社長からソフトバンクがどこに向かっていってどんなことを実現したいかというアツい話をしていただいたのだけれども、やっぱりエンジニアリングは面白いなぁという感想になった。

自分の中では

  • エンジニアリング = 既存の技術でどう社会の問題を解決していくか
  • 研究 = 未知の可能性をどう開拓していくか

というような区分けがあると定義していて、ここらへんの似たような二項対立としては理学と工学企業とアカデミアあたりもあるのかもしれない。

もしかしたらこれらは二項対立ではなく直交関係にあると言った方が正確なのかもだけど、少なくともこれらは互いに明確に違う部分があると思う。

もちろん、あまり厳密かつ明確に区別するのは難しいしエラーケースもたくさん出てくると思うので一般論なんかないのだろうけど、あくまで漠然とした傾向として。

自分のポジションとしては技術を人の役に立てて価値を創出して世の中を良くしていきたいという考えのもと、企業サイドであることは一応ここで表明しておく(あまり伺うような見方は好きではないので積極的にポジションを取りに行くスタイル)

さて、これらの二項対立について考えていると実はその人の本質に関する根源的な問いにぶつかると思う。

つまりどういうことかというと、利己的な行動に喜びを見出すか、利他的な行動に喜びを見出すか。

自分はどちらかというと自分から積極的に面白いことをやって自分から笑いに行くというより他人を面白がらせてその人たちが笑っている様子を見て自分も笑うというような感じ(?)で、こうした観点だと利他的な人間なんだと思う。

そう考えたとき、自分は技術を社会に直接活かすことによって価値を生みたいと考えている。

そのもとで研究とエンジニアリングで考えると自分はつくづくエンジニアリング寄りの人間なのかな、と。

もしかしたら研究とエンジニアリングは二項対立でも直交関係でもなく段階の違いでしかないのかもしれない。

ここらへんについては色々考え方があるので難しいのだけど、ただ一つ、社会の既存の問題をどう面白く解いていくか、これがエンジニアとしての自分が突き詰めるべき問いなんだと最近は考えている。

この問いに答えるためにフィールドとして自分はソフトバンクは最高のフィールドだと思っていて、来年からは全力でフルコミットしていこうと思う。

終わりに

最近は色々答えが出にくい難しいことを考えすぎているような気がしている。

ただ、大学生活の中での一つの成長として、自分は何者で何がしたいのかという問いにしっかりと答えられるようになった。

こんなことを書くのは恥ずかしい感じもするけど、あえてここで大学生活の中で自分が得たものを言葉にしておきたくて、この問いには「ソフトウェアエンジニアとして技術で誰かを喜ばせたい」と答えておこうと思う。

社会人となって、自分の人生をどうデザインしていくかについては永遠の課題になるだろうけど、色々今後もがんばって行こうと思う。

うーん、またわけのわからないポエムを書いてしまった...

まあ数年後の自分がどうなってるかを楽しみにしておこうと思う。

人生がんばるぞい!

Lispインタプリタを作った

とうとうApple MusicにPerfumeが追加されたので、ここ数日はワンルーム・ディスコを聞きながらコーディングをしている。

Perfume、良い。良すぎる。幸せになれる。

そう、幸せといえばLisp

1週間労働を耐え抜いてまた週末にのんびりLispを書ける。

安定感がすごい。

そんな感じで週末Lisperとして今日もLispに向き合っていたのだけれど、そういえば前にLispでCのコンパイラを書く個人プロジェクトを立ち上げたものの完全に放置していたのを思い出した。

で、久しぶりに取り組もうと書きかけのコードを見ていたのだが、なんかグッとこない。

どうも自分のコードにときめかない...

ということで中途半端な自分のクソコードをガチャガチャ触って気分の悪い土曜日にしてしまうくらいならまた別のプロジェクトをやった方がいいだろうと判断。

ただ、全く別の何かをやるのも少し気が引けたので、コンパイラに近しいものとしてインタプリタを作ることにした。

そんなこんなで今日はLispインタプリタを作ったので、その簡単な概要をつらつらと書いていこうと思う。

ちなみに今回のLispインタプリタは1時間とかそんなもんで作ったので超絶しょぼい。

ただ、その程度の作業量でLispは実装できるので今回のエントリーでLispにときめいてくれる人がいると嬉しいかも?

目標

今回は最終的にREPLを作り上げるのをゴールにする。

REPLとはRead Eval Print Loopの頭文字をとったもので、対話的環境とか言われるもの。

ターミナルでPythonと打つとその場でコードを書けて逐次的に結果を得られる機能があると思うのだけれど、まさにそれである。

最終的に今回はこれを作る。

使用言語

今回のLispインタプリタは実用性などを考えたら速度の出るものを採用するべきなのだけれど、別に今回は雑に作るもので、あとは1時間くらいでサクッと仕上げたいのでPythonで実装しようと思う。

別にPythonが好きとかそういうわけじゃないんだけれど。

ただ、言語実装という観点では関数を引数にとったり返り値にできたりする方が嬉しい(カリー化すごい)のでこうしたメタプログラミングにはRubyPythonHaskellが嬉しいのは一つある。

実装

今回のコードはあらかじめ以下のリポジトリにあるので、実行や参照は適宜よろしくお願いしますという感じで。

github.com

準備

まずは変数などを格納する環境を定義する。

ここにdefineなどで定義された関数の名前が入ったりする。

class Env(dict):
    def __init__(self, parms=(), args=(), outer=dict):
        self.update(zip(parms, args))
        self.outer = outer

    def find(self, var):
        if var in self:
            return self

        else:
            return self.outer

dictのサブクラスとして定義することでシンボルから実際の値を取り出すのが容易になる、というお気持ち。

一つ重要なこととして、この環境クラスにouterとして外部環境を用意することであり、この外部環境はラムダ式を使うときの仮変数を格納するためにある。

さて、ここに+carなどの実際に使うような関数をあらかじめ突っ込んでおく。

from __future__ import division
import math
import operator as op

    
def add_globals(env):
    env.update(vars(math))
    env.update({
        '+':op.add,
        '-':op.sub,
        '*':op.mul,
        '/':op.truediv,
        'not':op.not_,
        '>':op.gt,
        '<':op.lt,
        '>=':op.ge,
        '<=':op.le,
        '=':op.eq,
        'equal?':op.eq,
        'eq?':op.is_,
        'length':len,
        'cons':lambda x,y: [x]+y,
        'car':lambda x: x[0],
        'cdr':lambda x: x[1:],
        'append':op.add,
        'list':lambda *x: list(x),
        'list?': lambda x: isinstance(x,list),
        'null?':lambda x: x == [],
        'symbol?':lambda x: isinstance(x, str)
    })
    
    return env

Parser

Lispの構文はシンボルと数値、カッコからなるので構文解析がめちゃくちゃ簡単なのである。

def tokenize(s):

    return s.replace('(', ' ( ').replace(')', ' ) ').split()


def read_from(tokens):

    if len(tokens) == 0:
        raise SyntaxError('unexpected EOF while reading')

    token = tokens.pop(0)

    if '(' == token:
        L = []
        while tokens[0] != ')':
            L.append(read_from(tokens))
        tokens.pop(0) # pop off ')'

        return L

    elif ')' == token:
        raise SyntaxError('unexpected )')

    else:
        return atom(token)
    

def parse(s):

    return read_from(tokenize(s))

うーん、なんと構文解析が簡単なんだろう。

カッコと空白でsplitして中のシンボルを抽出するだけ。

優しい世界。

Evaluator

さあ、いよいよEvaluator。

ここで各構文を定義していく。

import sys
from environment import Env
from environment import add_globals


def check_string(x):
    if isinstance(x, str):
        return x[0] == x[-1] == '"' or not '"' in x[1:-2]
    else:
        return False

def my_eval(x, env):
    print("Here x is : ", x)

    # Check symbol ?
    if isinstance(x, str):

        return env.find(x)[x]

    # Check atom ?
    elif not isinstance(x, list):

        return x                

    # (quote exp)
    elif x[0] == 'quote':
        (_, exp) = x

        return exp

    # (if test conseq alt)
    elif x[0] == 'if': 
        (_, test, conseq, alt) = x

        if my_eval(test, env):
            return my_eval(conseq, env)

        else:
            return my_eval(alt, env)


    # (set! var exp)
    elif x[0] == 'set!': 
        (_, var, exp) = x
        env.find(var)[var] = my_eval(exp, env)

    # (define var exp)
    elif x[0] == 'define':
        (_, var, exp) = x
        if check_string(exp):
            env[var] = exp
        else:
            env[var] = my_eval(exp, env)

    # (lambda (var*) exp)
    elif x[0] == 'lambda': 
        (_, vars, exp) = x

        return lambda *args: my_eval(exp, Env(vars, args, env))

    # (exit)
    elif x[0] == 'exit':
        sys.exit("bye")

    # (begin exp*)
    elif x[0] == 'begin': 
        for exp in x[1:]:
            val = my_eval(exp, env)

        return val

    # (proc exp*)
    else:
        exps = [my_eval(exp, env) for exp in x]
        proc = exps.pop(0)

        return proc(*exps)

もう基本的にゴリゴリ再帰で処理していく感じ。

簡単な解説として、最初のcheck_string"hoge"のようにダブルクオーテーションで囲われたシンボルを文字列として認識するために、それが文字列かどうか確認するものである。

そして、my_evalの中では各構文を用意しているが、まあだいたい眺めれば理解できると思われる。

ifなどはほぼ見た通りのような感じだし、defineは後の構文を値として環境にセットしている。

lambdaの実装について、一度プログラムの実行立場を外部環境に移して一時変数をその外部環境にセットしてからその関数の中身を実行するという形式をとっており、そうしてラムダ式を実現している。

動かす

ちゃんと動いてる模様。

実際に動いててウキウキして騒いでいた。

まとめ

今回はPythonを使ってLispインタプリタを作った。

だいたいここらへんの基礎部分を作り上げるとあとは楽しいことが待っていて、構文糖衣を導入したり最適化したりとなんでもできる状態。

これからが一番楽しい感じなので、引き続きLispインタプリタを作っていこうと思う。

参照

(How to Write a (Lisp) Interpreter (in Python)) norvig.com

ASDFをちゃんと理解したい

最近はインターンで日中はずっと仕事していて特にツイッターにネタを投下することがない(余裕がない)ので、ブログを更新するのもだいぶ久しぶりな気がする。

というのも、勤務時間自体はいたって普通なんだけれども通勤時間が割と長くて(片道 : 電車1時間+徒歩30分)、朝7時半とかに家を出て帰宅するのは夜8時とかだったりするので趣味で進めている個人プロジェクトの進捗がゼロなのである。

インターン自体は楽しいのだけれども、やっぱり通勤時間が長い(おまけに満員電車...)と人は精神的に死ぬので、来年から都内に勤務なので全力で職場から近くに住もうと思う。

理想としては職場(汐留)から30分以内に到着できること。そして家賃がある程度抑えられていること。

...となると御徒町とか浅草橋あたり?

今は大学が京都なので半年後には京都から東京に引っ越さなければいけないので考えなければ....

さて、前置きはこんな感じでそろそろASDFの話を始めようと思う。

Common Lispのパッケージについて

プログラミングをやっていて初心者がよくつまづくポイントとしてライブラリモジュールパッケージフレームワークあたりの概念があると思う。

つまづきポイントとして、これらをimportとしたときのパス関係とか名前空間あたりがちょっと大変らしい。

このライブラリとかモジュールとは要するにコードの塊のことなんだけれども、言語とか用途によってその呼び方は結構変わってきたりする。

前にQiitaで何か記事を書いたときにそれはライブラリじゃなくてモジュールですよとコメントをもらったことがあるのだけれども、正直そこらへんの感覚は自分はよくわからない。

というよりも言語によって呼び方が変わる、というのが率直なところ。

PythonだとNumpyとかMatplotlibのことをモジュールと読んでいてDjangoとかTensorFlowとかをフレームワークと呼んでいるらしい(?)

まあそこらへんの呼び方の名前はあんまり議論したところで何か前進があるわけでもないので議論好きな人に任せておくとして、Common Lispではいわゆるモジュールとかライブラリと呼ばれるコードの塊のことをパッケージと呼ぶ。

なのでCommon Lispでパッケージを作るときは defpackage としたりする。

具体的にどういう風にパッケージを定義するかについては後述。

ASDFとは

ASDFとはCommon Lispの依存関係を管理してくれるもののことで、Another System Definition Facilityのこと。

先ほど述べたパッケージについて、どういう順番にロードしたりコンパイルするべきかをまとめておくを制御するもので、これがちゃんと使えなければCommon Lispではプロジェクトが作れないので悲しいことになる。

今回のエントリーのモチベとしてはこのASDFを自力で記述できるように要点をまとめてサクッと簡単なものを作っていこうという感じ。

とりあえず見てみる

語学でもプログラミングでもなんでも、最初に机の上で勉強してばっかで手を動かさないと全く頭に入ってこないので、とりあえず実際のASDFのファイルを見てみようと思う。

わからんことがあったら後から調べていこうと思う。

ということでまずサンプルとしてCL-OpenGLリポジトリの.asdファイルを見てみる。

ちなみに、CL-OpenGLOpenGLCommon Lispで触れるパッケージのこと。

github.com

中身としてはこうなっている。

(defsystem cl-glut    ;; システム定義
  :description "Common Lisp bindings to Freeglut."    ;; 簡単な説明
  :author "Luis Oliveira  <loliveira@common-lisp.net>"    ;; 作者
  :version "0.1.0"    ;; バージョン
  :licence "BSD"    ;; ライセンス
  :depends-on (alexandria cffi cl-opengl)    ;; 使っているパッケージ
  :components    ;; 以下、システムを構成するソースコード
  ((:module "glut"    ;; glutというディレクトリ
    :components    ;; glut内でのシステムを構成するソースコード
    ((:file "package")    ;; package.lispというファイル
     (:file "library"   :depends-on ("package"))    ;; library.lispがpackage.lispに依存
     (:file "state"     :depends-on ("library"))
     (:file "init"      :depends-on ("library" "state"))
     (:file "main"      :depends-on ("library" "init"))
     (:file "window"    :depends-on ("library"))
     (:file "overlay"   :depends-on ("library"))
     (:file "menu"      :depends-on ("library"))
     (:file "callbacks" :depends-on ("library"))
     (:file "misc"      :depends-on ("library"))
     (:file "fonts"     :depends-on ("library"))
     (:file "geometry"  :depends-on ("library"))
     (:file "interface"
            :depends-on ("init" "main" "window" "library" "callbacks"))))))

ざっと簡単にコメントをつけてみた。

とりあえず、ASDFを使うために.asdにはライセンス情報とか使っているファイルを書き込めばいいらしいことがわかる。

で、ここから見た感じ package.lisp がどうやら重要そうなのがわかるので、 package.lisp を見てみる。

(in-package #:cl-user)    ;; 最初にCommon Lispのもともとのパッケージに入る

(defpackage #:cl-glut    ;; cl-glutというパッケージを定義
  (:nicknames #:glut)    ;; 略称
  (:use #:cl #:cffi)    ;; これから定義するパッケージで使う他のパッケージ
  (:import-from #:alexandria #:deletef #:emptyp)    ;; alexandriaというパッケージからdeletefとemptypをimportする
  ;; interface.lisp stuff
  (:shadow #:special #:close)    ;; specialとcloseはCL-USERのものを使う
  (:export     ;; これから外に出すもの
   ;; events / GFs
   #:idle
   #:keyboard
   #:special
   ;; (中略)
   #:set-key-repeat
   #:force-joystick-func
   #:report-errors
   ))

ポイントとして、(in-package cl-user) で最初にCommon Lispのもともとの関数が用意されているパッケージに入らなければいけないこと。

定義の中でどのような設定を行うかについてはコメントを見ればなんとかなる(はず)

で、package.lispはこんな感じで、他のファイルでは最初に

(in-package cl-glut)

;; brah brah brah

として、以降は定義したパッケージの中に入って関数を作っている。

自作する

中身を見てみると意外と簡単なことがわかったので早速自作してみる。

構成として

.
├── src
│   └── main.lisp
└── test-cl.asd

という感じにしてみる。

そして、main.lispでは

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

(defun hoge ()
  (format t "This is ASDF test project !"))


(defpackage foo-package
  (:use :cl)
  (:export :foo))
(in-package foo-package)

(defun foo ()
  (format t "Hi! This is foo package !"))

として、先ほど見た通り、パッケージを定義してそのパッケージに入り、中でテキトーな関数を用意する。

さて、今度はtest-cl.asdファイルの編集。

(defsystem test-cl
    :components ((:module "src"
                          :components ((:file "main")))))

本来ならライセンス情報とかいろいろ書くのだけれども今回は省略。

とりあえず重要な:componentsを書いておけばASDFはロードするべき情報がわかっているので大丈夫。

さて、準備ができたのでtest-cl.asdがあるディレクトリでREPLを開いてロードしてみようと思う。

(asdf:load-system "test-cl")

そうするとロードできたのが確認できたと思う。

で、定義したパッケージ名はhoge-packageだったので

(hoge-package:hoge)

として関数を実行する。

f:id:komi1230:20190916170251p:plain

これで良さげ。

foo-packageについても同様。

いじってみる

.asdのファイルをロードするときtest-clとしたけど、この名前はtest-cl.asdというファイルのことなのか中の定義したシステム名のことなのかを調べてみる。

test-cl.asdというファイルをtest-hoge.asdというファイル名に変更。

そうしてREPLで同様にロードしてみると...

f:id:komi1230:20190916171327p:plain

最初にtest-hogeでロードするとダメだけどそのあとtest-clでロードしてみると怒られながらもなんとかいける。

ちなみに最初にtest-clをやるとダメで、そのあとtest-hogeをロードをしてみてもダメ。

順番を変えればいけるらしい?

まあとにかく結論として.asdのファイル名と中の定義するシステム名は一致させるべきという感じ。

まとめ

ASDFの中身を簡単に見ていって、そのあと具体的に.asdファイルを書いて実行してみた。

多分これでASDFは最低限は使えるようになっていると思う。

ASDFの仕様書はかなり膨大で、ライセンスとか作者情報以外にも他にもいろいろオプションがあったりするわけだけど、そこらへんはまた各自でがんばってもらう感じで....

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

参考

ASDFの仕様書

common-lisp.net

LINE KYOTOのインターンに参加してきた

少し久しぶりのエントリーです。

さて、8月26日〜30日の5日間、LINE KYOTOのインターンに参加してきました。

今回はその感想とかをまとめるためのエントリーです。

f:id:komi1230:20190831002804j:plain
LINE KYOTOのオフィス

前にインターン参加について書いたエントリーの通り、この夏はLINE KYOTOのインターンに参加が決まっており、それでちょうど今日終わりました。

komi.hatenadiary.com

参加動機

インターン参加の動機としては、武者修行社会勉強の2つという感じです。

前のエントリーに書いた通りぼくは来年の4月からソフトバンクに就職するのですが、普段ツイッターででかい口叩いてるくせにエンジニアリング力が低すぎるので来年から働き始めるまでにエンジニアリング力を高めておきたい気持ちがあって、それでインターンを通してガンガンレベルアップしていこうぜということでインターンに参加したわけです。

この夏でやっておきたいこととしては色々あって、例えばVue.jsやReactなどのフロント系の技術などがあるわけですが、今回のインターンではDockerやKubernetesなどのコンテナ技術系とかインフラ周りをがっつり触ってきました。

LINE KYOTOのインターンについて

LINE KYOTOのインターンの内容としては、3人チームを組んで5日間でプロダクトを開発しよう!という感じでした。

まあ実際のところ初日はガイダンス的なやつだったり事務手続きとかで何もしてなくて、最終日も成果発表会とかがあったりして実際に開発をしていたのは3.5日間だったのですが....

これらに加えて、1日の勤務時間が10時から18時半までと決まっていて、18時半には強制終了という...(ホワイト?)

てことでかなり時間が限られた状況下でスピード感を持って開発にあたることになります。

で、その開発をLINEの社員がメンターとしてサポートしてくれるという感じです。

このメンターがめちゃくちゃ最強で、わからないことが発生したら超絶スピードでトラブルシューティングにあたってくれるので今回のインターンは学ぶことだらけでした。

やったこと

うちのチームはToDOアプリを作りました。

ToDOアプリの基本的な実装要件として、

  • ToDOを追加できる
  • 削除・編集・追加ができる

という感じで、これらに加えてoptionalな要素として

  • タグをつけてグループ化できる
  • リマインド機能

などを実装しました。

チームは3人で、ぼく以外のメンバーはそれぞれフロントが得意な人とバックエンドが得意な人だったので、自分の担当としては

  • フロントとサーバーをサポート、それらの結合
  • デプロイ周りのインフラ関係
  • リマインド通知のためのbot作成
  • (マイルストーンの設定などのPM的な何か)

を行いました。

自分以外のメンバーが超絶優秀だったので、最初に簡単なミーティングでDB設計やアーキテクチャマイルストーンの策定などを行ってからは基本機能の実装などは2人がほとんどやってくれました(おんぶにだっこで申し訳なかった)

自分にとって今回挑戦的なこととして、今までDockerやKubernetesを触った経験があまりなかったのとbotを作るのが今回初めてだったということで、かなり手探りの中でなんとかプロジェクト完了まで持っていったという感じでした。

今回のプロジェクトの中で学ぶことがたくさんあって、例えばDockerなどの技術的な知識がついたのももちろんなのですが、プロジェクトの進行方向などについてかなり考えることが多かったです。

例えば、プロジェクトの最初でDB設計を決めたあと、サーバーサイド側でダミーAPIを用意してフロントの人を待たせず各自がゴリゴリ実装を進められるようにするというのは自分には無い発想だったので、かなり学びになったと思いました。

おかげさまで開発を始めて2日目には基本機能の実装を終えてデプロイまで持っていくことができ、かなりのスピード感を実現できたと思います。

(チームメンバーがマジで優秀だったなぁとしみじみ)

フロントとサーバーの結合が地味に時間がかかったり、インフラ周りでゴチャゴチャしてて、そんな感じでバタバタした中でちゃんとデーモンを起動させて通知botが機能するようにしたのには結構満足しています。

反省点として、CSS力をもう少しつけておけばUIがもっといい感じになったのかなぁ...と思ったり。

もうちょっと勉強します...

感想

感想として、かなり充実感のある内容だったなぁ、と。

たくさん勉強になりましたね。

あと、LINEのエンジニアの働き方を感じられたのはかなり良かったです。

まあここらへんの月並みな感想はこの程度にしておいて、今回LINE KYOTOのインターンで個人的にものすごい良かったのが積極的に失敗を許容して、最後の発表会でも順位づけをしないという点です。

これの何がいいかというと、無駄な焦りなどが無くなって精神衛生が非常に良いんですよね。

インターン初日の挨拶でエンジニアリングマネージャーの方がこういう機会でチャレンジすることって無いから思い切って失敗するのもいいと思いますと言っていて、おかげさまで今回のインターン全体としてプレッシャーなどを感じることもなく伸び伸びと開発することができました。

また、プロジェクトの進行方法としても各自が独立して開発して担当箇所がオーバーラップすることがあまり無いようにしたのもあって、特に喧嘩が起こることもなくストレスゼロで開発できたのはかなり良かったです。

あと、期間が1週間という短さもあって集中して取り組むことができたのも良かったです。

今回はLINEのSDKを使っての開発だったということもあって、そこらへんの設計思想の把握までに少し時間がかかって認証とか権限周りでかなりコケまくって苦労したのはいい思い出ですw

ちょっと余談

インターン自体についてはこんな感じで楽しかったなぁという感じなのですが、インターンの中でエンジニアリングマネージャーの御代田さん(@josolennoso)がキャリア論とか組織論について熱く語ってくれたのが個人的には良かったなぁって思ってます。

エンジニアリングという組織におけるマネージャーの役割やキャリア選択における好きなことと得意なことの活かし方、いい仕事をするとはどういうことなのかなどなど、めちゃくちゃいい話をしていただきました(だいたいお酒を飲みながらだけど)

エンジニアとは接点を持ててもマネージャーというポジションの人と話すことはあまりないので、色々新鮮な感じがして良かったです。

マジでいいことをたくさん言ってたので、ここらへんまとめてなんかビジネス書っぽくしたら絶対売れると思います(?)

まとめ

上記のような感じでインターンに参加してきました。

ここで学んだことを今後に活かしていきたいと思います。

ぼくは来年からはソフトバンクでエンジニアなのでLINEのインターンに参加できるのは今年がラストチャンスだったわけですが、最高の1週間だったと思ってます。

LINEはいいぞ。