モバイルファクトリーを退職しました
今年もあと1時間ですね。 退職エントリとかかける機会は人生でそんなにないと思うので3年以上ぶりのエントリです。
表題通り、五反田の株式会社モバイルファクトリーを12月末日付けで退職しました。(最終出社は12月27日でした)
もともとkoropicotのIDと仕事は紐付ける気はなかったので入社エントリすらないので、そのへんから自分の整理も兼ねて書きます。
入社まで
モバイルファクトリーには大学学部卒業後の新卒として2016年4月に入社しました。 入社のきっかけはPaizaというサイトで、一般的な情報系大学生として就活めんどくさいな〜となってる中でなんとなく利用して、新卒採用の募集を出していた会社として、まずはカジュアルにお話からという感じで知りました。
その時や面接を通してお会いしたジンジニアの id:mameco0417 さんやエンジニアの id:karupanerura さんを始めとした社員の方がいい感じ(語彙力)だったのと、当時諸事情で東京に出たかったのでそのまま入社にいたりました。
なにやってたの?
2017年まで
入社後、研修を終えてからはとあるゲームの裏方チームとして、ユーザに直接見える機能ではない部分を支えるいろいろをしていました。 コードのリファクタや、クラウド環境での運用、他の開発メンバーのためのツール作成や、障害対応などなどです。 もともとWebやクラウドやらPerlやらその他やらの使っている技術のバックグラウンドはほぼ皆無でしたが、自由度を持って任せてもらったおかげでかなりいろんな経験ができました。
結構頑張ったこともやってた気がしますが、反面いま思い返すと設計やらその時の振る舞いで後悔している部分もなくはないです。
この期間に、社内的にはエンジニアからリードエンジニアにランクアップしました。
2018年
2018年には前述のチームからなんだかんだあってブロックチェーンチームに移りました。 ここでやってたこと以下をご覧ください。 この辺で書いてない部分でいうと、技術面からいろいろBizの人にアドバイスするみたいなこともしてた気がします。
この期間には、社内的にはリードエンジニアからシニアエンジニアにランクアップして、対外的にはシニアブロックチェーンエンジニアを名乗ってました。
なんでやめるの?
なんか経歴を見るとトントン拍子にランクアップして、いい感じなのになんで急にやめるの? という感じですが、自分もそう思います。 コンテキストを共有できていないのでこのエントリで正しく伝えるのは困難なので、あんまりいろいろは書きません。
退職が決まって最終出社も終えた今になって考えると、入社もなんとなくフィーリングだったのに対して、退職も結局はなんとなくのフィーリングという感じでしょうか。 もう少し具体化すると、ブロックチェーンチームでエンジニアとして中心となってやらせて頂いた中で、自分の考えを上回る議論をできなくてつまらなくなった、という感じです。
逆に、退職の理由ではない方は明確に挙げられて、モバイルファクトリーのいいところは自分の中で以下のようにたくさんあります。
- 働き方はホワイト
- 無駄な残業もほぼないし残業しないことに引け目を感じることもない
- お休みもちゃんと取れる
- バックオフィス含め改善を心がけてる
- エンジニアの勉強会が活発
- 毎日1時間が勉強会に使える時間として確保されてます
- 基本的に悪い人はいない
- 給与も悪くない
- 特に自分は正直結構もらってました
似たようなことは入社のきっかけになったジンジニアの方の退職エントリにも書いてあるので、知ってる人は同意できると思います。
つぎどうするの?
とりあえず、ちょっと疲れたのとなんとなくやりたいことやれる時間がほしいなと思ったので来年1月は無職を満喫します。 モバイルファクトリーはお休みを取りやすい会社過ぎて有給使い果たしていたので、特に有給消化期間はないです。
その後は、2月から渋谷にある会社で働きます。 具体的にどの会社かは、自分が言いたいなと思ったタイミングで言ったり言わなかったりします。 一応言っておくと、直近ではブロックチェーンとは関係ないことをします。
おわりに
新卒からこれまでお世話になったモバイルファクトリーの皆様、本当にありがとうございました。
あれ
ここにほしいものリストを貼ろうと思ったけど、Kindle版の書籍はプレゼントできないことをこないだ知りました。
Twitter で 🍣にでも誘ってください。
2018年もお疲れ様でした。来年もよろしくお願いいたします。
マインスイーパーのソルバーを作った
昨日今日の土日にオープンキャンパスがあり、研究室紹介で利用するためにマインスイーパーのソルバーを作ったのでGitHubにあげました。
C#/WPF(with Livet)で作成しました。 オープンキャンパスという締め切りに間に合わせるためにコードが色々と酷いことになってます。 でも、もうメンテナンスする気ないのでそのまま公開しちゃってます。
解法
基本的には人間が解く時のやり方そのまま。 具体的(?)な方法はポスターに使ったスライドをちょっと手直しした画像を貼っておくのでそちらで。
注意点として、このスライドではしっかり触れずに流しているのですが、8近傍がすべて開いていないマスについては分からないということにして解のパターンから除外し、パターンの数が莫大にならないようにしています。 この省略を行うことにより、もしかしたらもしかして解の完全性が失われている(全通り試したら分かる解を見逃す)かもしれません。
あと当然ですが、どのマスも確実に開けることが出来ず1/2とかの確率で当てるしかない状況になると解は得られなくなります。
Try SLC を公開しました
SLCをWebブラウザ上で実行できるTry SLC(Symmetric Lambda Calculus)を公開しました。
SLCって?
SLCとはSymmetric Lambda Calculusの略で、日本語だと対称ラムダ計算と呼ばれる、ラムダ計算の一種です。
その名の通り対称性が特徴なラムダ計算です。
ここで言う対称性は、値(これまでの計算結果)と継続(残りの計算)におけるもので、関数と組(ペア)において値と継続を対称性を持って同じように扱うことができます。
要は値の否定が継続で、関数には対偶があって、ド・モルガンです!
と言う説明ではわからないので元のSLCの論文 "Declarative Continuations and Categorical Duality" (Filinski 1989) か、
関連した日本語の論文 "型付き対称λ計算の基礎理論" (阪上 2008) を読みましょう。(ぶん投げ)
ちなみに以下で"値"と書いているものはお茶大の論文では"項"にあたるので注意して下さい。(Filinskiの方では"value"なので"値"と書いてます。お茶大の方では"値"は"項"のそれ以上簡約できない形を指すのに使っています。)
Try SLC
Try SLCはSLCをJavaScriptで動くようにして、Webブラウザで動作を試すことができる環境です。
実際にはFilinskiの論文の付録のCamlでの実装をF#で書き直し、さらにFunScriptというF#→JavaScriptコンパイラによってJavaScriptのコードを生成しています。
使い方
ページ下部の入力欄にSLCの式
、または変数名:=SLCの式
と入力し、右の丸いボタンを押すかShift+Enterを押すことで実行します。
SLCの式
を入れた場合にはその式を評価して結果を返し、変数名:=SLCの式
を入れたときは式を評価した結果で変数を定義します。
文法
基本的な文法は先の論文を読んでもらうとして、いくつか論文中の表記と相違点があるので書いておきます。
変数の定義
前述の使い方で書いた通り変数名:=SLCの式
で変数が定義できます。
元の実装ではzd
にあたる動作です。
関数の表記
関数の表記に用いられる⇒
および⇐
は、=>
および<=
になります。
再帰関数はrec 識別子=本体
のままです。
関数適用の表記
関数適用に用いられる↑
および↓
は、^
および?
になります。
関数の値扱い継続扱い(及びその逆)
関数の値扱い及びその逆⌈⋅⌉
, ⋅
と、関数の継続扱い及びその逆⌊⋅⌋
, ⋅
は、論文中でさらっと
In the SLC, explicit conversions between the three syntactic classes are not necessary in the concrete syntax, but are present as implied operators in the abstract one.
とあるように実際には出てきません。
構文においてある部分で要求されるのが値か継続か関数(か値引数か継続引数か)か分かる(例えば値が要求される部分でf^x
とあったらfは関数でxは値である必要があるというように上から順次決まっていく)ので、それに従いこれらの変換が付けられます。
既知のバグ
6/8:修正しました。
原因は生成されたJavaScriptで関数を呼び出した結果が保存されず、何度も同じ呼び出しをしていた部分があった為でした。
複雑な式を入れると結果が待てど暮らせど返ってこなくなります。
例えばpf := f<=n=>(r<={r?(()=>1), r?(()=>n*f^(n-1))})^(n=0)
とかやると死にます。(この式は元の論文での実行例にでている式)
どうやら型推論のあたりで問題が起きてるっぽいんですが原因まだ特定できてません。
前述した通り、Try SLCはF#で書いたインタプリタからJavaScriptに変換してその上で動いており、元のF#実装では問題なく動いているので色々謎です。
そのうち治します。治せたらいいな。
Land of Lisp 7章メモ
7章メモ
ドットリスト
終端がnil
でないコンスセルの連なり。終端がnil
で終わるのがリストなので真のリストではない。
対
対はコンスセルをそのまま使うことで表現できる。
循環リスト
終端がなく循環した(自己参照をもった)リスト。
REPLが正しく循環リストを表示するために、以下のように設定する必要がある。 このように設定する事で、表示する時に循環をチェックして無限ループを回避してくれる。
(setf *print-circle* t)
連想リスト
5章メモ参照。 遅い(きっとO(n))。
木構造
Lispコードでいい感じに表現できる。
グラフ
グラフをコード(1次元データ)で上手く表現するのは困難。 Graphvizを使って可視化する。
mapc
リストを返さないmapcar
、副作用が主作用()の時に使う。
標準出力を横取りする
*standard-output*
を一時的に別のストリームで上書きすることで、標準出力に出力する関数の出力を得ることが出来る。
本文中では、ファイルへの書き込みストリームで上書きしている。
また、出力という副作用が意味を持つので、dot->png
関数はthunkを引数にとって中で呼んでいる。
この辺の出力とかを使った書き方気持ち悪すぎ
キーワードシンボル
:
で始まるシンボルはそれ自身をさす定数となる。
Source
;ノード(場所) (defparameter *wizard-nodes* `((living-room (you are in the living-room.)) (garden (you are in a beautiful garden.)) (attic (you are in the attic.)))) ;エッジ(移動手段) (defparameter *wizard-edges* `((living-room (garden west door) (attic upstairs ladder)) (garden (living-room east door)) (attic (living-room downstairs ladder)))) ;識別子の変換 (defun dot-name (exp) (substitute-if #\_ (complement #'alphanumericp) (prin1-to-string exp))) (defparameter *max-label-length* 30) (defun dot-label (exp) (if exp (let ((s (write-to-string exp :pretty nil))) (if (> (length s) *max-label-length*) (concatenate 'string (subseq s 0 (- *max-label-length* 3)) "...") s)) "")) ;ノードをDOTフォーマットへ変換 (defun nodes->dot (nodes) (mapc (lambda (node) (fresh-line) (princ (dot-name (car node))) (princ "[label=\"") (princ (dot-label node)) (princ "\"];")) nodes)) ;エッジをDOTフォーマットへ変換 (defun edges->dot (edges) (mapc (lambda (node) (mapc (lambda (edge) (fresh-line) (princ (dot-name (car node))) (princ "->") (princ (dot-name (car edge))) (princ "[label=\"") (princ (dot-label (cdr edge))) (princ "\"];")) (cdr node))) edges)) ;グラフをDOTフォーマットへ変換 (defun graph->dot (nodes edges) (princ "digraph{") (nodes->dot nodes) (edges->dot edges) (princ "}")) ;標準出力に出力される内容をDOTファイルとして書き込みグラフ画像を生成する (defun dot->png (fname thunk) (with-open-file (*standard-output* fname :direction :output :if-exists :supersede) (funcall thunk)) (ext:shell (concatenate 'string "dot -Tpng -O "fname))) ;グラフを画像にする (defun graph->png (fname nodes edges) (dot->png fname (lambda () (graph->dot nodes edges)))) ;無向グラフを追加 (defun uedges->dot (edges) (maplist (lambda (lst) (mapc (lambda (edge) (unless (assoc (car edge) (cdr lst)) ;残りのリストに行き先ノードが無いとき (fresh-line) (princ (dot-name (caar lst))) (princ "--") (princ (dot-name (car edge))) (princ "[label=\"") (princ (dot-label (cdr edge))) (princ "\"];"))) (cdar lst))) edges)) (defun ugraph->dot (nodes edges) (princ "graph{") (nodes->dot nodes) (uedges->dot edges) (princ "}")) (defun ugraph->png (fname nodes edges) (dot->png fname (lambda () (ugraph->dot nodes edges))))
出力画像
有向グラフ
無向グラフ
Land of Lisp 6.5章メモ
6.5章メモ
lambda
らむだ!らむだ!
Land of Lisp 6章メモ
6章メモ
5章のゲームにコマンドラインインターフェースを追加する。
コンソールへの表示
print
: 表示位置が行頭でなければ改行して値を出力して空白を出力prin1
: 単に表示
入出力いろいろ
print
とread
はLispの値をそのまま入出力できる。
すなわち文字列なら"
で囲われて、文字なら#\
がついて出力され、入力するときにはこれらをつける。
読みやすい形で出力するにはprinc
を使う。
また、文字列として入力するにはread-line
を使う。
読みやすい形への整形は単射ではない
> (princ "10") 10 > (princ 10) 10
ので、読みやすい形で入力された値をいつも適切な型で受け取るprinc
の逆関数は原理的に無理。
eval
データとしてのLispコードを実行する関数。
REPL
Read Eval Print Loop
(defun repl () (loop (print (eval (read)))))
なんというそのまま。素敵。
Read,Eval,PrintをカスタマイズすることでオリジナルのREPLを作れる。
ゲーム用REPL
- Read : 括弧と引数のクオートをつけることで入力を楽に
- Eval : ゲームのコマンド以外を弾く事で安全に
- Print : 内部データを整形して表示する
ここでのPrintは基本的な変換しかしていないが、内部データを扱いやすい形で持ち、
適切に変換して表示する構造は多くのプログラムで使える。
要はView。
作成したREPLの安全性
Readで引数をクオートして、Evalでゲームのコマンド以外の関数を弾いてるので基本的には任意のコードは実行できないが、
Readで組み込みのread
を使っているのでリーダマクロを使われると安全ではないらしい。
Source
5章で出力文を1文ごとのリストのリストとしているのでそれに合わせてgame-print
も変えている。
;ゲーム用のREPLを作成する (defun game-repl () (let ((cmd (game-read))) (unless (eq (car cmd) 'quit) (game-print (game-eval cmd)) (game-repl)))) ;括弧とクオートを付けなくてもコマンドを呼べるようにする (defun game-read () (let ((cmd (read-from-string (concatenate 'string "(" (read-line) ")")))) (flet ((quote-it (x) (list 'quote x))) (cons (car cmd) (mapcar #'quote-it (cdr cmd)))))) ;ゲームのコマンド以外を受け付けない (defparameter *allowed-commands* '(look walk pickup inventory)) (defun game-eval (sexp) (if (member (car sexp) *allowed-commands*) (eval sexp) '((i do not know that command.)))) ;1文毎のリストに構造を改変してるのでgame-printも改変 (defun game-statement-print (statement) (labels ((proper (fn elem) (if (stringp elem) elem (funcall fn (prin1-to-string elem)))) (capitalize (str) (coerce (let ((lst (coerce str 'list))) (cons (char-upcase (car lst)) (mapcar #'char-downcase (cdr lst)))) 'string)) (proper-capitalize (elem) (proper #'capitalize elem)) (proper-downcase (elem) (proper #'string-downcase elem))) (string-trim "()" (princ-to-string (cons (proper-capitalize (car statement)) (mapcar #'proper-downcase (cdr statement))))))) (defun game-print (statements) (princ (string-trim "()" (princ-to-string (mapcar #'game-statement-print statements)))) (fresh-line))
Land of Lisp 5章メモ
5章メモ
テキストゲームを作る。 世界は場所をノード、その間の移動手段をエッジとした有向グラフとして捉えられる。 ゲームでは以下の操作を行える。
- 周囲を見る
- 移動する
- オブジェクトを拾う
- オブジェクトを使う
文字列とシンボルのリスト
ここでは文字列でなくシンボルのリストをつかってdescriptionを書いている。 構造を持ったデータを扱う練習みたいなもんだろうか。
連想リスト
carにキー、cdrに値をもったKey-Valueペアのリストで連想リストを表現する。 assoc関数で取り出す(KVPが返る)。
有向グラフの表現
ゲーム世界の有向グラフをノードのリストと、エッジのリストで表す。ノードのリストはノード名をキーとし、descriptionを値とした連想リスト。エッジのリストはノード名をキーとし、そのノードから出るエッジの行き先ノード、方向、エッジの実体のリストを値とした連想リスト。
準クオート
クオートされたリスト中に評価したい式を埋め込む構文。'
のかわりに`
でクオートし、,
でアンクオートする。
`(quoted ,unquoted quoted)
みたいな感じ。
高階関数
Common Lispでは関数と変数の名前空間が分かれているので、関数を引数などに渡すときには#'
を付けて関数であることを指定する必要がある。
ここで出てきた高階関数
mapcar
: いわゆるmapapply
: リストの要素をそれぞれの引数として関数を呼ぶ
述語
述語(A->bool)は末尾にpをつける習慣。
関数型プログラミングスタイル
ここでは副作用を伴わない関数を関数型スタイルとしてる。
push/assoc
push
はリストの先頭に要素を加える。
assoc
はリストの先頭から要素を探して最初の値を返すのでこれで更新したように扱える。
Source
本のコードでは出力文を1つのリストにしてるが、 後にいい感じのprintをする上でも1文ごとのリストのリストの方が良さそうなのでそうしている。
;ノード(場所) (defparameter *nodes* `((living-room (you are in the living-room.)) (garden (you are in a beautiful garden.)) (attic (you are in the attic.)))) (defun describe-location (location nodes) (cadr (assoc location nodes))) ;エッジ(移動手段) (defparameter *edges* `((living-room (garden west door) (attic upstairs ladder)) (garden (living-room east door)) (attic (living-room downstairs ladder)))) (defun describe-path (edge) `(there is a ,(caddr edge) going ,(cadr edge) from here.)) ;元のコードだと全部1つのリストにしてるけど、1文ごとに分けたほうが構造保てて良さそうなので (defun describe-paths (location edges) (mapcar #'describe-path (cdr (assoc location edges)))) ;オブジェクト (defparameter *objects* '(whiskey bucket frog chain)) (defparameter *object-locations* '((whiskey living-room) (bucket living-room) (chain garden) (frog garden))) (defun objects-at (loc objs obj-locs) (flet ((at-loc-p (obj) (eq (cadr (assoc obj obj-locs)) loc))) (remove-if-not #'at-loc-p objs))) (defun describe-objects (loc objs obj-locs) (flet ((describe-obj (obj) `(you see a ,obj on the floor.))) (mapcar #'describe-obj (objects-at loc objs obj-locs)))) ;現在地 (defparameter *location* 'living-room) (defun look () (concatenate 'list (list (describe-location *location* *nodes*)) (describe-paths *location* *edges*) (describe-objects *location* *objects* *object-locations*))) ;歩く (defun walk (direction) (let ((next (find direction (cdr (assoc *location* *edges*)) :key #'cadr))) (if next (progn (setf *location* (car next)) (look)) '((you cannot go that way.))))) ;オブジェクトを手に取る (defun pickup (object) (list (cond ((member object (objects-at *location* *objects* *object-locations*)) (push (list object 'body) *object-locations*) `(you are now carrying the ,object)) (t '(you cannot got that.))))) (defun inventory () (list (cons 'items- (objects-at 'body *objects* *object-locations*))))