ギークもどきの日記帳

雑多な知識が垂れ流される場所。ほとんど無害。

高校で得られた知見とか感想とか

最近流行りの卒業エントリってやつです。 退職エントリの仲間で、長らく仮説上の存在でしたが、近年その実在が証明され、これからは爆発的に普及すると予測されています。 大学を卒業してから書こうと思ってましたが、なんか書き上がってしまったので高校卒業エントリです。

ようは卒業文集に書いたりするアレです。

けっこうこっ恥ずかしいことも書いたので、リアルの僕を知っている方は「読んでないし存在も知らない」という体でお願いします。 思いやりと信頼ですよ?

授業とかテストとかの感想

知らないことをたくさん学べるし、テストがあることで理解度を大雑把に把握できます。 しかし、ペースが遅い。内容もやや単調。授業の魅力は教師ガチャの結果次第。 やることはモバマスや神撃のバハムートとあまり変わらず、得られる報酬の魅力も薄い。 無事にクリアした君にはオプーナを買う権利をやろう。

でも、面白いことには面白い。鯖落ちやメンテナンスもないし、興味のない分野の知識もある程度得られるので、まぁ一回はプレイしてもいいかなと言う感じです。

公立の普通科高校で一からプログラミングをやっていった話

自分の高校生活をまとめると、部活に打ち込んだ3年間、という感じで表現されるのではと考えています。 体験入部もせずにコンピュータ部に入り、そこから3年間、卒業の1週間ぐらい前まで色々やってました。

主にプログラミングについて色々やってたので、ここに問題点と解決策を残しときます。

まず問題になるのが、強すぎるフィルタリングサービスです。これはぶっ壊すわけにもいかないので、顧問と話して、必要なときはスマートフォンなどを使えるようにしました。不便なことには不便ですが、気合で乗り切ります。 単語を見るタイプのフィルターは、言語設定をポルトガル語とかにするとスルーできたりするので、やってみる価値はあるかも。

次に問題なのがPCです。予算もあまりなく、学校のパソコンは好き勝手にはいじれない。大問題です。 解決策として、僕はRaspberry Piを使いました。割りとなんとかなります。Emacs動きます。aptで入らなければ野良ビルドLinuxの勉強にもなるし一石二鳥。 自前のノートPCを利用するのももちろんありです。

もっとも大きな問題は、学習方法でした。教えてくれる先輩も先生もいないので、人類の叡智に頼るほかありません。 How To Become A Hacker: Japaneseはかなり参考になると思います。 プログラミング言語の入門書としては、主に「すごいHaskellたのしく学ぼう!」や「Land of Lisp」を読んでました。 他にも「たのしいRuby」や「すごいErlangゆかいに学ぼう!」、「リーダブルコード」など、とにかく読みまくりました。 プログラミングは独学でなんとかなるので、自分で情報を集めて学ぶ姿勢を身につければあとは楽勝です。(個人の感想です。効能には個人差があります。)

コンピュータ部の感想

ここからは完全に思い出話になります。

僕が入った当時は、幽霊部員の3年生が3人いるだけの、廃部待ったなしな部でした。 多分再来年ぐらいに京アニでアニメ化します。12割ぐらい脚色つけて。

1年生は僕含めて3人で、僕を除いた二人はたまたま小学校の同級生だったりして、世の中不思議な縁もあったものだなぁと感じたのを覚えています。 部のロッカーからなのはのステッカーが出てきたり、部のパソコンのパスワードのヒントが「らき☆すた」で、wikipedia見ながらパスワードを当てたり、なかなか面白いスタートでした。

帰ってきたヒトラー」がどうだとか人肉の味がどうだとかパンツレスリングがどうだとか、極めて雑な話をしながら各々が自分のしたい事をする、コワーキングスペースみたいな場所でした。コワーキングスペース知らないけど。

1年の文化祭ではRAPIROを動かして、大体あそこで学校でのキャラが確定したんじゃないかなと。 じわじわ部員も増えて、アニメ見たりアナログゲームやったりだべったり、あんまりコンピュータ関係ないことをしながら、僕はオセロのAI実装したりRAPIROいじったりしてました。

コンピュータ部は、作業場であり、制作物の発表の場であり、友達と遊ぶ遊び場であり、学校に行く大きな動機だったように思います。

伝えるタイミングを微妙に逃した気がしてるので、ここで感謝の意を公開しておきます。恥ずかしいので部員の皆様はこの記事を見なかったことにしてください。 変なテンションで鈍器を読み漁って黒い画面を見つめる僕と一緒に毎日数時間を共有してくれた部員の皆にはとても感謝しています。 わざわざ家まで来て、スプラトゥーンのウデマエをSまで上げておいてくれてありがとう。おかげですさまじい連敗を味わいました。 結局のところ、2015年4月某日にあの部屋に3人が集まったことで、僕は死にそうなほどブルーな日も高校に行く羽目になり、おかげで無事に卒業でき、ついでに18歳になることもできました。該当の2名には特別の感謝をしたいと思います。出会えた幸運に祝福を。あと顧問の先生二人にも。お世話になりました。

後輩も10人ちょっと入り、コンピュータ部は相変わらずの調子で続いていくようです。良かった良かった。

大学に進学します

おしかったりおいしかったりする県の、3つぐらい似た名前の大学があってややこしい大学の情報科学部に進学します。 特になにかしでかそうとは考えてませんが、とりあえずはAR面白そうだなぁとか考えてます。あとCをちゃんと書けるようになりたい。 関係者となる皆様方はよろしくお願いします。

3年間は早かった。でも疲れたのでしばらくのんびりします。

Common Lispのライブラリ事情

ANSI Common Lispでは、ライブラリのフォーマットについてあまりちゃんとした仕様が存在しません。

当然、ライブラリを扱えないのは不便極まりないことですから、その点をカバーするためのシステムが存在します。

  • REQUIRE, PROVIDE 現在非推奨の、ANSI Common Lispに存在する唯一のライブラリ管理システムです。

  • ASDF デファクトスタンダードのライブラリ管理システムです。

  • QuickLisp ASDFを使用したライブラリのコレクションです。 Rubyにおけるgem、HaskellにおけるHackageのような立ち位置です。

  • Roswell Common Lispの開発環境を管理するためのツールです。 ASDFの機能を拡張し、GithubからのライブラリDLなど、モダンな機能が追加されています。

REQUIRE, PROVIDE

ANSI Common Lispに存在するライブラリ管理システムです。 ‘(require :hoge)'でファイルを読み込み、そのファイルで’(provide :hoge)‘されていれば、2回目以降の’(require :hoge)‘では何も行われません。

これは無駄なロードを防ぐための仕組みです。

しかし、REQUIREがどこのファイルを読みにいくかは処理系の実装に依存しています。 処理系ポータブルなコードを書くには非常に不便です。

そこで、Common Lispでは主に"ASDF"というシステムをつかってライブラリを管理します。

ASDF

ASDFは、Common Lispソースコードをsystemとしてまとめ、ビルドし、ロードするためのツールです。

例えば、Makefile的なものを、(systemname).asdとして書いてASDFのロードパス下においておくと、

(in-package :cl-user)
(asdf:defsystem :foobar ;; 定義するsystem名
    :description "A sample Lisp system."
    :version "0.0.1"
    :author "Joe <joe@example.com>"
    :licence "Public Domain"
    :depends-on (:alexandria :serapeum) ;; 依存するパッケージ
    :components ((:file "foobar" :depends-on ("utils")) ;; systemに含まれる.lispファイルを、(systemname).asdからみた相対パスで
                 (:file "utils")))

次のようにロードすることができます。

* (require 'asdf)
* (require 'foobar)

require, ASDF, quicklispを正しく使う | κeenのHappy Hacκing Blog独学Common Lisp に詳しい内容があるので、そちらも参照することをおすすめします。

packageとsystemの違い

Packages, systems, modules, libraries - WTF?

TODO: ちゃんと書く

Quicklisp

Quicklispは、Rubyにおけるgem、HaskellにおけるHackageのようなシステムです。 次のようにsystemをロードできます。

* (ql:quickload :alexandria)
* (ql:quickload '(cl-annot trivia))

また、(ql:system-apropos substring)で検索、(ql:update-all-dists)でアップデート、(ql:update-client)でQuicklisp本体のアップデートができます。

QuickdocsにはQuicklispでロードできるすべてのsystemのドキュメント、プロジェクトページへのリンクなどが掲載されています。

TODO: Quicklispへ自分のsystemを追加する方法

Roswell

Roswellには、コマンドラインでQuicklispからライブラリやソフトウェアをダウンロードする機能があります。

$ ros install qlot        # Quicklispからダウンロードします。
$ ros install fukamachi/qlot # Githubからダウンロードします。

TODO: ASDFのdepends-onとかにもgithubリポジトリを指定できる様になったはずだけど試してないので試して書く

参考文献

Clozure CLをちょっと早くする

Clozure CLのLispの部分をコンパイルしてうんたらかんたらして起動とかを早くする。

? (ccl:compile-ccl)

フィボナッチ数の20番目を計算してテスト

#!/bin/sh
#|-*- mode:lisp -*-|#
#| <Put a one-line description here>
exec ros -Q -- $0 "$@"
|#
(progn ;;init forms
  (ros:ensure-asdf)
  ;;#+quicklisp (ql:quickload '() :silent t)
  )

(defpackage :ros.script.fib.3691033179
  (:use :cl))
(in-package :ros.script.fib.3691033179)

(defun fib (n)
  (case n
    ((0 1) 1)
    (t (+ (fib (- n 1)) (fib (- n 2))))))

(defun main (&rest argv)
  (declare (ignorable argv))
  (princ (fib 20))
  (terpri))
;;; vim: set ft=lisp lisp:

起動時間も見たいのでRoswell scriptで

$ ros build fib.ros
$ #ビルド前
$ time ./fib.ros
10946
./fib.ros  1.51s user 0.14s system 98% cpu 1.671 total
$ #ビルド後
$ time./fib.ros
10946
./fib.ros  0.37s user 0.09s system 96% cpu 0.482 total

Roswell scriptでshebangツライときの覚え書き

ANSI Common Lispではshebangの存在は考慮されない。 しかし、Roswell scriptのデバッグをSlimeでしていると、shebangでリードエラーが起きてツラくなる。 その回避方法。

Roswellは起動時に、$HOME/.roswell/init.lispを実行する。 また、ros:ignore-shebang関数で、リードマクロにshebang行の読み飛ばしが追加される。

$HOME/.roswell/init.lisp

(ros:ignore-shebang)

このように設定しておけば、shebangの行を読み飛ばすので幸せになれる。

C++14でstd::vectorのmap関数書いた(白目)

大体一年ぐらい前に

C言語でmapとreduce - ギークもどきの日記帳

という記事を書きました。

これをC++14で、パフォーマンスを気にしないブルジョワ仕様で作ってみてmapで力尽きたという報告です。

map関数本体

複数コンテナへの対応は難しいので、今回はstd::vectorのみを考えた。

#include <algorithm>
#include <vector>
#include <iterator>

template <class T, class R>
auto map(const std::vector<T> a, std::function<R(T)> fn) {
  std::vector<R> result = {};
  std::transform(a.begin(), a.end(), std::back_inserter(result), fn);
  return result;
}

メモリがどうとかはさっぱり考えてないので、resultあたりがおかしくなる気がしないでもないですが、とりあえず動く。

まあ、ここまで書くともうstd::transformをそのまま使えばいいじゃんという話ですが、忌むべき副作用はできるだけ減らしたい。モチベーションはただそれだけです。

使い方

int main() {
  std::function<std::string(int)> twice_show = [](int x) { return std::to_string(x * 2); }; // 絶望ポイント
  
  std::vector<int> v = { 3, 5, 6};
  auto result = map(v, twice_show);
  std::for_each(result.begin(), result.end(),
                [](const std::string& s) { std::cout << s << std::endl; });
}

残念ながらこの実装ではテンプレートにラムダ式がマッチできないらしく、ラムダ式をそのままmap関数へ渡すことができない。
そのため、一度ラムダ式をstd::function型の変数に代入する処理が必要になる。 これではあまり意味がない…

解決策 その1

関数のオーバーロードを悪用してみた。

次のコードをmap関数の下に追加し、ディスパッチみたいな処理をさせる。

template <class T, class F>
auto map(const std::vector<T> a, F fn) {
  std::function<decltype(fn(a[0]))(T)> g = fn;
  return map(a, g);
}

decltypeサイコー!!! 要するに型推論を行うオーバーロード関数を自前で用意する、そういうことです。 しかし、map関数本体がこのディスパッチ関数の前に宣言されている必要があるので、あとから違うコンテナでmap関数を定義するとコンパイルできない。

解決策 その2

返り値の型をdecltypeで求めても良いはず。

map関数を次のように書き換えます。

template <class T, class F>
auto map(const std::vector<T> a, const F fn)
  -> decltype(std::vector<decltype(fn(a[0]))>()) // なんだこれ
{
  std::vector<decltype( fn(a[0]) )> result = {};
  std::transform(a.begin(), a.end(), std::back_inserter(result), fn);
  return result;
}

これでよし。

まったく良くないのでdecltype(auto)

どう見てもdecltype(std::vector<decltype(fn(a[0])>())は読みにくい。 そこで、decltype(auto)を使う。

template <class T, class F>
decltype(auto) map(const std::vector<T> a, const F fn)
{
  std::vector<decltype( fn(a[0]) )> result = {};
  std::transform(a.cbegin(), a.cend(), std::back_inserter(result), fn);
  return result;
}

あらスッキリ!

できたコード

Map function on C++

参考サイト

cpprefjp.github.io

追記

テンプレートテンプレート使えばfmap作れそう。ということで実験中。

Emacsでアンチエイリアスがかかりすぎる on macOS

defaults write -app Emacs AppleFontSmoothing -int 0を実行して、アンチエイリアスのかけ具合を最低にするとうまいこといった。

RoswellでCommon Lisp環境をセットアップする 2016秋

こちらの記事で随時更新予定です RoswellでCommon Lisp環境をセットアップする - ギークもどきの日記帳

Roswellとは (ざっくりと)

Common Lispの処理系やQuicklispのインストール、処理系ごとのオプションの違いの吸収などを行うすごい便利なツール。 Common Lisperなら使って損はない。

インストール

参照Wiki

1. Installation · roswell/roswell Wiki · GitHub

Arch Linux

AURに登録されているので、yaourtでインストールできる。

$ yaourt -S roswell

Homebrew

$ brew install roswell

Windows

1. Installation · roswell/roswell Wiki · GitHub

使っているOSのbitに合わせて、32bitならRoswell-i686.zip、64bitならRoswell-x86_64.zipをダウンロードし解凍、PATHを通す。

ソースからビルド

INSTALL.md参照。

Replの起動

ros runコマンドでREPLが起動する。

$ ros run
* (+ 1 2)

3
* (quit)
$ 

また、roswellはデフォルトでsbcl-1.2.11のバイナリ版を使う。 他の処理系や最新版のsbclを使いたいときは、以下の手順でインストールする。

処理系のインストール (例: Clozure CL)

$ ros install ccl-bin

処理系の切り替え (例: Clozure CL)

$ ros use ccl-bin
$ ros run
Welcome to Clozure Common Lisp Version 1.11-r16635  (DarwinX8664)!

CCL is developed and maintained by Clozure Associates. For more information
about CCL visit http://ccl.clozure.com.  To enquire about Clozure's Common Lisp
consulting services e-mail info@clozure.com or visit http://www.clozure.com.

? 

インストールできる処理系のリストは、ros list versionsで確認できる。

$ ros list version
candidates for ros list versions [impl] are:

abcl-bin
allegro
ccl-bin
clasp
clisp
cmu-bin
ecl
quicklisp
sbcl-bin
sbcl

インストールできるバージョンは、ros list versions [処理系の名前]で確認できる。

$ ros list versions sbcl
Installable versions for sbcl:
Checking version to install....
1.3.15
1.3.14
1.3.13
1.3.12
1.3.11
1.3.10
1.3.9
1.3.8
1.3.7
1.3.6

SLIME

みんなだいすきSLIMEも簡単に使える。 ros emacsで、.emacs.dの設定なしにSLIMEを使えるemacsが起動する。すごい便利。

普通のemacsで使うには以下の手順を踏む。

$ ros install slime

続いて~/.emacs.d/init.elに次の行を追記する。

(load (expand-file-name "~/.roswell/helper.el"))

より詳しい設定方法は、

1.1 Initial Recommended Setup · roswell/roswell Wiki · GitHub

を参照のこと。

Roswell Script

Common LispではRubyPythonと異なり、プログラムをロードしたREPLを用いることが多い。 スクリプト言語のようにバッチ処理を実行するには、処理系ごとに異なった方法を取らねばならず少し不便を感じる。

その点を解決するため、RoswellにはRoswell Scriptという仕組みがある。 ros init <ファイル名>で、Roswellがインストールされている環境上で、単体で実行できるCommon Lispが生成される。

$ ros init fact
Successfully generated: fact.ros

$ cat fact.ros
#!/bin/sh
#|-*- mode:lisp -*-|#
#| <Put a one-line description here>
exec ros -Q -- $0 "$@"
|#
(progn ;;init forms
  #+quicklisp (ql:quickload '() :silent t))

(defpackage :ros.script.fact.ros.3685511769
  (:use :cl))
(in-package :ros.script.fact.ros.3685511769)

(defun main (&rest argv)
  (declare (ignorable argv)))
;;; vim: set ft=lisp lisp:

このファイルはEmacsVimの両方でCommon Lispソースコードとして認識される。

main関数がエントリーポイントとなる。main関数の引数には、適切にパースされたコマンドライン引数が渡される。 例えば、引数の階乗を返すコマンドは下のようになる。

#!/bin/sh
#|-*- mode:lisp -*-|#
#| Calculating the factorial of given number
exec ros -Q -- $0 "$@"
|#

;;; 今回は使用しないのでコメントアウト
;; (progn ;;init forms
;;   #+quicklisp (ql:quickload '() :silent t))

(defpackage :ros.script.fact.ros.3685511769
  (:use :cl))
(in-package :ros.script.fact.ros.3685511769)

(defun fact (n)
  (if (zerop n)
        1
              (* n (fact (1- n)))))

(defun main (n &rest argv)
  (declare (ignorable argv))
  (format t "~&Factorial ~D = ~D~%" n (fact (parse-integer n)))
  0 ; 実行成功
)

;;; vim: set ft=lisp lisp:

実行例

$ ./fact.ros 3
Factorial 3 = 6

$ ./fact.ros 10
Factorial 10 = 3628800

さらに、Roswellがインストールされていない環境でも(おそらく)動作するexecutableも作れる。 ただし、処理系やライブラリのイメージを含むため、ファイルサイズが50MiBぐらい大きくなる。レッツブルジョア!!

$ time ./fact.ros 10
Factorial 10 = 3628800
./fact.ros 10  0.43s user 0.09s system 98% cpu 0.530 total

$ ros build fact.ros

$ time ./fact 10
Factorial 10 = 3628800
./fact 10  0.00s user 0.01s system 89% cpu 0.018 total

$ du -ah
 54M     ./fact
4.0K     ./fact.ros

割りと速くなった。気がする。もっと計算量の多い処理だと大きく変わるかも。