Memo 2018 Q4 - homma/homma.github.com GitHub Wiki

12.31

microKanren

実装してみました。

動くけど、使い方がよくわからない。。

12.30

Lisp を読む時にしんどいところ

  • データ構造を定義せず、何にでもリスト(連想リスト、ベクター)を使用する
  • アクセッサを定義せず、何にでも car/cdr を使用する
  • 名前(関数名、変数名、引数名)のつけ方が ad-hoc

データ構造からプログラムの意味を読み取れない。

microLogic

12.28

adt-simple

Algebraic data types for JavaScript using Sweet.js macros

どう展開されるのか興味ある。
見やすいコードに展開されることを期待できるかどうかは何とも言えない。

Node: Plan for New Modules Implementation

.mjs はもう決定と考えて良い。

microKanren

μKanren: A Minimal Functional Core for Relational Programming

The implementation of microKanren, a featherweight relational programming language

日本語の情報がほとんどない。

用語

s/cstate で、substitutioncounter のペア。

a state s/c (connoting a substitution and counter pair)

Clojure: core.logic

Type Inference

A type inferencer for the simply typed lambda calculsus

miniKanren

なんで fresh なんだろう。
fresh な束縛を作るからだと思うけど、もっと分かりやすい名前があっただろうに。

Tutorial

論理演算

conjunction: 論理積 (AND)
disjunction: 論理和 (OR)

conAND なのは良いとして、それが なのが分かりづらい。
disOR なのもわかるけど、それが なのが分かりづらい。

一方で、A の領域と B の領域を掛け合わせた部分が なのは分かりやすい。
A の領域と B の領域を足し合わせた部分が なのも分かりやすい。

問題は、英語での概念と日本語での概念の両方を使用しないといけないこと。
どちらか片方だったらすんなり頭に入った。

プログラミングでよく出てくる disjoint union非交和
これは disjointunion なので分かりやすい。

disjoint union: 非交和

Haskell: Fixy Resolution

実装を確認したい。

Haskell: Releases

Haskell: Monadic I/O

Haskell がモナモナし始めたのはバージョン 1.3 から

Haskell: Unicode

Haskell が Unicode に対応したのは 1.4 から

RedixDB

a persistent real-time key-value store, with the same redis protocol with powerful features

Pony Language

🐴 Pony is an open-source, actor-model, capabilities-secure, high performance programming language

12.22

Vim: Disable Command Line Window

  • キー操作を間違えた時に出るお邪魔機能は Command Line Window というらしい
  • 間違えて q: を押した時に表示される
  • ex コマンドや検索履歴を確認できるらしいけど、正直いらない
  • 心を落ち着けて :q で閉じる
  • プロンプトが : になっているので q だけで良いかと勘違いしてしまいますが、:q を入力する必要がある
  • Command Line Window は以下の設定で無効にできる
nnoremap q: <Nop>

12.15

Reduce

Map by Reduce

const rmap = (arr, fun) =>
  arr.reduce((acc, cur) => {
    acc.push(fun(cur));
    return acc;
  }, []);

Rust: mrustc

Alternative rust compiler (re-implementation)

The SubC Compiler

12.13

Moscow ML のドキュメント

再配布不可。
翻訳したかったけど残念。

Electron: Tab Browser

webview タブで作っている。

webview tag

Electron の webview は独自の修正版であり、Chrome のものとは異なっている。
安定性などの面で Electron としては非推奨みたい。

代替の方法として iframeBrowserView が推奨されている。

iframe はセキュリティを高めようとすると、JavaScript の実行を禁止する必要があったり、使いづらいっぽい。 webviewBroserView は基本的には view の側からアプリケーションには干渉することができない。
nodeintegration の設定などで変更できるっぽい)

結局のところ BroserView を使うのが正しい選択みたい。
一つの BrowserWindow で複数の外部コンテンツを取り扱う必要があるケースも多くなさそうだし。

webview で問題がないなら webview を使うのもあり。

Jimp

An image processing library written entirely in JavaScript for Node, with zero external or native dependencies.

12.12

Electron: BrowserView

本日時点でも 1 BrowserView for 1 BrowserWindow
複数の BrowserView を設定しようとすると最後の BrowserView のみが有効になる。

webview タグを使うのが現実的。
ただし webview も色々と問題があるらしい。

複数の BrowserView を使用する

BrowserView を複数作成することは可能。
同時に表示できる BrowserView が一つなだけ。

タブブラウザ的なものは以下のアプローチで実現可能。

  • BrowserView を複数作成しておき、表示したい BrowserViewsetBrowserView で変更する

以下のページのコメント欄参照。

同じ画面に複数の BrowserView を表示することはできない。

12.4

mjs の件

入れてもらっても問題なさそう。

https://nodejs.org/api/esm.html#esm_enabling

The --experimental-modules flag can be used to enable features for loading ESM modules.

Once this has been set, files ending with .mjs will be able to be loaded as ES Modules.

node --experimental-modules my-app.mjs

Node.js requires .mjs suffix for ES modules.
Would you please add .mjs for the plug-in settings?

12.2

演算子の優先順位と結合法則

  • 優先順位 : 一つの式に複数の演算子がある場合に、どの演算子から順に適用するか
  • 結合法則 : 同じ優先順位の演算子がある場合に、どちら側の演算子から順に適用するか
  • 非結合 : 同じ優先順位の演算子がある場合は、エラーとする

11.30

Firestore 試してみた

簡単に使用できました。
データベースにアクセスする際は、ウェブ API キーだけあれば良い。

無料で安心して使えるデータベースサービスは他にないので価値がある。

Google アカウントだけあればプロジェクトを作成できるので、他の人(技術系の人)にも使ってもらいやすい。

11.28

Now

使ってみたい。

Aminal

Aminal is a modern terminal emulator for Mac/Linux implemented in Golang and utilising OpenGL.

グリフの描画は GPU を使用しないので、GPU レンダリングの意味は薄いのではないかという話も。
Unicode サポートも要確認みたい。

Input Method のサポートはなさそう。

Kitty

A cross-platform, fast, feature full, GPU based terminal emulator

Tilix

A tiling terminal emulator for Linux using GTK+ 3

11.27

macOS で絵文字を入力するショートカット

  • Control + Command + Space
  • Input Method を使わない人たちには必須なのかも

Flatpak

Linux application sandboxing and distribution framework

Snaps

Anbox

Anbox is a container-based approach to boot a full Android system on a regular GNU/Linux system

11.26

Linux: 絵文字フォントを有効にできない

半日頑張ってみましたが、無理でした。

Noto Color Emoji はビットマップフォント。
OS の設定でビットマップフォントは無効になっている。
ビットマップフォントを有効にすると、ウィンドウバーの表示がおかしくなり、パネルも異常終了してしまう。

ビットマップフォントを適切に表示させる方法がわからない。
ウェブで検索してヒットした設定を適用してみましたが、解決せず。
Noto Color Emoji 以外の絵文字フォントも見つからず。

こんなことで時間を消費するのは無駄。。
ビットマップフォントの表示にこんなに手間がかかるとは。。

Twitter Color Emoji SVGinOT Font

モノクロ表示ですが、動作させることができました。

Lem: 遅延の件

問題点が伝わりやすいように、日本語の翻訳つけたけど、良かったのかどうか微妙なところ。
これで自分の環境固有の問題だったら悲しい。

11.25

tmux 導入

11.24

Brushtail

Tail call optimisation for JavaScript.

末尾最適化をするためのトランスパイラみたい。

Nodebrew: arm64

Issue サブミットしました。

最初は bitness の判定に int のサイズを使用していました。
調べてみたところ、32bit バイナリでもコンパイル時に 64bit int を使用するように指定できるみたいです。
そのため、代わりに ptrsize を使用するように変更しました。

PR にしなかったのは、use の適切な位置がわからなかったため。

Roy seems Good!

すでにプロジェクトが凍結されているのが残念ですが、Roy は良さそうな感じ。
Elm より良いかも。

Object は OCaml と同じく structural subtyping を使用している。
これを Standard ML にも適用できないかな。

OCaml は良い言語ですが、Unicode に対応していないのと、ダブルセミコロンが残念。

Roy の他の特徴:

  • 2 次元文法(オフサイドルール)
  • Unicode 対応
  • タプルはないっぽい

タプルがないのは辛いですね。
ただ JavaScript もタプルはないですし、配列に複数の型を混ぜるようなことは殆どしないので、良いのかな。

11.23

Roy's JavaScript FFI

Interoperating with JavaScript

Referring to unknown identifier will assume that the identifier refers to a native JavaScript global.

なかなかワイルドな実装。

ghost

ghost さん色んなところで見るなと思ったら、アカウント削除したユーザの代わりに表示される名前でした。。

Xterm.js

Emscripten で変換したのかと思っていましたが、jslinux の vt100 実装をもとにフルスクラッチで実装されたもののようです。

libvterm

Homebrew でインストール可能。

$ brew install libvterm

Terminal で動かしているプログラムで libvterm を呼び出すとどうなるんでしょう?

Vim libvterm

Neovim libvterm

Emacs libvterm intergration

libvte

Nodebrew aarch64

  • can we update the $arch for aarch64 to arm64?

Node has arm64 build since ...

background

Chromebrew

11.22

ToDo

Electron

勉強したい。

tmux

使ってみたい。

  • tmux prefix 考える
    • Alt で良いと思いますが : "tmux prefix alt meta"

テキストエディタのターミナルよりもレイヤーが少ない分、動作が軽そう。

arm64

ML

Lem

  • run process issue report
  • 画面分割のサイズ変更を便利にしたい
  • カラーテーマをもう少し頑張る
  • region を pprint する方法ある?
  • icloud にアクセス可能?
  • terminal emulator を実装可能か
    • libvterm
  • w3m
  • HyperSpec

現状で十分便利なので、時間があったら実施する感じで。
libvterm を統合できたら tmux 使う必要がなくなる。

Lisp

  • "ANSI Common Lisp" 読む
  • "Land of Lisp" 読みたい

Vim

  • カーソル位置を画面の中央にするコマンドは zz   - Emacs Keybind では Ctrl-l

  • 文字の入れ替えは xp   - Emacs Keybind では Ctrl-t

V8 Custom Startup Snapshot

11.21

F11

  • Fn + Shift + F11 でした

VimGolf

11.20

Xi Editor

カラーがないのがカラーみたいな印象。

macOS ショートカット

ウィンドウの切り替え : ⌘ + F1(Fn + F1)
タブの切り替え : Ctrl + Tab
指定した番号のタブに移動する : ⌘ + 数字
クリック可能領域の切り替え : Option + Tab

ウィンドウの切り替えは頻繁に使用するので、他のキーバインドに変更したい。
⌘ + EscTerminal.app で使用できませんでした。。

⌘ + Alt + Tab にしてみました。

設定は『システム環境設定』→『キーボード』→『ショートカット』→『キーボード』→『次のウィンドウを操作対象にする』。

11.18

${HOME}/.local について

ホームディレクトリにソフトウェアをインストールする際は、${HOME}/.local にインストールするのが良さそう。

${HOME}/local だと、目立ち過ぎてしまう。
バイナリ置き場は通常は操作を行わないので、隠しディレクトリになっている方が都合が良い。

規格

XDG Base Directory Specification${HOME}/.local/share の規定がある。

If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.

file-hierarchy${HOME}/.local の規定がある。

~/.local/bin/ Executables that shall appear in the user's $PATH search path.

Micro ML

https://github.com/pascallouisperez/mu-ml

Micro ML — Small, simple compiler in ML for a subset of ML. Meant for teaching.

  • lex を使っている
  • パターンマッチなし

microML

https://github.com/kellino/microML

a simple functional language for learners

Micro ML

https://bitbucket.org/microml/micro-ml/src

a compiled, functional language with similar constructs as Standard ML, which can be run on a Teensy microcontroller.

Micro ML

11.17

"Basic Polymorphic Typechecking"

"Unification"

11.16

"How OCaml type checker works -- or what polymorphism and garbage collection have in common"

MLF

"Type inference"

micro-ML

"Text File formats – ASCII Delimited Text – Not CSV or TAB delimited text"

MapSCII

MapSCII is a Braille & ASCII world map renderer for your console - enter => telnet mapscii.me <= on Mac and Linux, connect with PuTTY on Windows

JS: Static Typing

Linkers and Loaders

11.14

Carlo - headful Node app framework

Carlo provides Node applications with Google Chrome rendering capabilities, communicates with the locally-installed browser instance using the Puppeteer project, and implements a remote call infrastructure for communication between Node and the browser.

Puppeteer

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

11.11

Termux: Web Server

普通に動きました。

ポート 8080 でウェブサーバを起動して、Android のウェブブラウザから localhost:8080 にアクセスできました。

Android

キーボードレイアウトの変更はできた。

あとは日本語変換か。

minml

https://github.com/melsman/sml-llvm/blob/master/test/miniml/miniml.sml

(* A Simple ML-like language ported to Standard ML from OCaml; see
     http://groups.google.com/group/fa.caml/msg/5aee553df34548e2
*)

11.10

"Luca Cardelli and the Early Evolution of ML"

The Gallina specification language

Agda: Language Reference

Typer

Macro は使いにくそう。

Oczor

Oczor is a simple statically typed language that compiles to JavaScript, Lua, Ruby and Emacs Lisp

Camlp5: Scheme and Lisp syntaxes

11.9

P4P: A Syntax Proposal

Racket の別文法の提案。
2010.07 なのでかなり前のもの。

良さそうだけど実装はなさそう。

wisp: whitespace lisp indentation preprocessor

SRFI 110

Macro はどうなるのかな。

今だと Julia を使う方が良さそうな気も。

11.7

Common Lisp: Step 実行

  • M-x start-lisp-repl
(sb-ext:restrict-compiler-policy 'debug 3)
(defun bar (n) (+ n (* n n) n))
(step (bar 0))

11.5

「世界から括弧が消えたなら 」

かっこが消えた Lisp、すごいスッキリしてる。
とても良い。

defunfun になるともっと良い。
lambdafn で。
中置記法も使えるとなお良い。
アサインメントは = で、setf:= になるとすごく良い。

ただ、読みにくい面も多少はある。
エディタの支援も受けにくそう。

SRFI 49 は実際に使ってみるとどうなのかな。
中置記法は SRFI 105

11.2

Lem: ToDo

Lisp のメッセージの表示がおかしい件

これはなんとかしたい。

Issue 登録してみました。

他の環境では発生しない問題だったら、残念。

ただ、trace output はバッファにキープされて欲しい。
warning も残った方が良いと思われる。

*lisp-repl* は気づきにくい感じがするので、最良な方法であるかは微妙。
console.log 的な場所と考えれば違和感はありませんが。

uiop:run-program が遅い件

これもなんとかしたい。

画面の分割を良い感じにしたい

  • 縦に 3 分割した場合に調整が大変
  • 等分するコマンドが欲しい

バッファ編集画面をもっと便利にしたい

  • g でリフレッシュしても開きなおすともとに戻ってしまう
  • ソート順がよくわからない
  • 操作がよくわからない

行番号の出し方確認

オプションかコマンドがありそう。

テーマの作成方法

色が見づらいので変更したい。
かっこは薄い色にしたい。
マゼンタは使わない。

ウィンドウ分割の線を罫線にする方法

  • 要確認
  • 気分転換的な意味で

ここら辺かな。

w3m

  • HyperSpec を参照したい
  • 時間がかかりそうだし、急がない

shell

ステータスラインを消す方法

  • 今となっては特に欲しい機能ではないですが、単なる興味本位で実装してみたい

ファイラー的なもの

  • Atom の代わりに Lem を使いたい

  • Atom のデフォルトの画面と同じ構成を再現したい

  • プロジェクトビュー

    • ファイルやフォルダを登録しておくところ
  • タブ

    • いらないかな
  • Emacs だと NeoTree が相当するみたい

11.1

defun to fun

  • de-fun より fun が好き

7DRL

rexpaint

Lem: Android へのインストール

10.31

PR した後のリポジトリ

消しちゃって良いものなのか。

Land of Lisp 読みたい

  • せっかく Common Lisp の勉強を始めたので
  • 内容が面白そう
  • 時間がない

Wiki の更新が重い

分割してみましたが、やはり少し重い。
サーバ側で時間がかかっているっぽい感じ。

こういうブログサービスが欲しい。

  1. ウェブブラウザで更新できる
  2. Markdown で書ける
  3. エントリーごとに分割されず、一定期間のコンテンツを 1 枚のページで編集できる
  4. 日付ごとに表示することもできる
  5. 自分しか編集できない
  6. 管理不要
  7. 使用できる HTML タグに制限がない
  8. 表示がシンプル

4, 7 以外は Github Wiki で問題なかったのですが、コンテンツが大きすぎると 3 が問題になってしまう。

Lem: Android で Lem を使いたい

UserLAnd は情報が少ないので、まずは termux を試してみたい。

なお、SBCL は termux 直では動かない(ので、proot を試す)

Arch Linux は SD Card 側に入るのか

SD Card には入れられないっぽい。

ファイルの実行パーミッションの問題みたいなので、fasl ファイルなどを置く分には問題ないと思われる。

  • Arch Linux から SD Card の領域に書き込むことはできませんでした(root のみ書き込み可能)

デバイスの内蔵ストレージに入れる形で試してみましょう。
動作するだけでも面白い。
もし容量がきつかったら、その時考える。

Ctrl-[ で生きていく

  • Esc の代わりに Ctrl-[ を使うよう、しばらく修行します

10.30

🌪が半角判定であることについて

1F32ANeutral とされているため。

1F321..1F32C;N # So [12] THERMOMETER..WIND BLOWING FACE

1F321 : 🌡
1F324 : 🌤
1F325 : 🌥
1F326 : 🌦
1F327 : 🌧
1F328 : 🌨
1F329 : 🌩
1F32A : 🌪
1F32B : 🌫
1F32C : 🌬

Ambiguous ですらないとどうしようもないですね。

PR 作ったよ

速攻でマージしてもらえました。
ありがたい。

PR 手順

ToDo

  • PR の手順を調べる
  • run-program が遅い件も fix する

修正して欲しいのは以下。

  • Unicode 11.0 対応
  • run-program が遅い件

.mjs について

  • Node.js は experimental で ES Module に .mjs を使用している
  • まだドラフト段階ですが、JavaScript の拡張子として .mjs が提案される予定みたい
  • エディタのプラグインではサポートしているものもあり、サポートしていないものもある
  • Node.js が正式対応することを様子見しているプロジェクトもある (T なやつとか)

Node.js: .mjs

んー、正式対応になるまで待った方が良いのかも。
それまではローカルでパッチを当てて対応する。

.mjs 対応

vim-javascript: 対応ずみ

tigris.nvim: 対応していない

Lem: アイコン

アイコンはレミング。

Lem: macOS で run-program が遅い件

報告されていました。

PureScript BNF

これは古いみたい。

参考 : https://github.com/purescript/documentation/issues/29

ML 系言語のお約束

  1. ML 系言語は ML 系言語自身で実装する
  2. JavaScript へのコンパイラを実装する

JavaScript へのトランスレータを実装している ML 系言語処理系

  • MLKit
  • HaMLet
  • FStar
    • 古いバージョンのみ
  • ATS
    • C 経由

JavaScript へのトランスレータが存在する ML 系言語

  • F#
    • Fable
  • OCaml
    • Js_of_ocaml
    • BuckleScript

10.29

Programming in Standard ML

hamlet/compile-js

HaMLet には SML を JavaScript にコンパイルする機能がある。
ただし実装は 2013 年のもの。

Added simple JavaScript compiler and runtime as a proof of concept, accessible via the newly added -j mode.

Lem: 調査

  • ros update lem した際に、初回起動時にコンパイルされる仕組み
    • Roswell がやっているっぽい
  • ローカルプロジェクトを明示的にコンパイルする方法
    • コアファイルを削除してからビルドする
  • ローカルプロジェクト以外の場所にソースツリーをコピーした場合の起動方法
    • Quicklisp のパスを変更する
  • ローカルプロジェクト以外の場所にソースツリーをコピーした場合のコンパイル方法

Lem: アップデート

ros update lem すると git pull して最新にしてくれる。

$ ros update lem

起動する際にコンパイルされる。
どういう仕組みでコンパイルされるかは不明。

lem という名前と cxxxr/lem を紐づけている仕組みも不明。

Lem: ソースコードからの起動

ローカルプロジェクト (~/.roswell/local-projects/cxxxr/lem) のソースコードを編集した場合は、ros コマンドで編集版のプログラムを起動できる

$ ros -s lem-ncurses -e '(lem:lem)'

ローカルプロジェクト以外の場所にソースコードを配置しても使用されない。

参考 : http://moremagic.hateblo.jp/entry/2018/09/13/091803

ただし、コンパイルはされない。

ros build lem-ncurses.ros でコンパイルはできるが、ソースコードへの変更が反映されない。
正しいコンパイルはどうやるのだろう?

Lem: ビルドされる仕組み

ros update lem ではビルドされない

% ros update lem
git pull on /Users/me/.roswell/local-projects/cxxxr/lem/
Already up to date.
[1/3] System 'lem' found. Loading the system..
[2/3] Processing build-hook..
[3/3] Attempting to install the scripts in roswell/ subdirectory of the system...
/Users/me/.roswell/bin/lem
/Users/me/.roswell/bin/lem-ncurses-ccl
/Users/me/.roswell/bin/lem-ncurses
/Users/me/.roswell/bin/lem-rpc
/Users/me/.roswell/bin/lem-xcb
up to date. stop

初回起動時にビルドされる。

$ lem
Making core for Roswell...
building dump:/Users/me/.roswell/impls/x86-64/darwin/sbcl-bin/1.4.12/dump/lem-ncurses.core

コンパイルされたイメージはここ。

~/.roswell/impls/x86-64/darwin/sbcl-bin/1.4.12/dump/lem-ncurses.core

これを削除してから Lem を起動するとビルドが実行される。

$ rm ~/.roswell/impls/x86-64/darwin/sbcl-bin/1.4.12/dump/lem-ncurses.core
$ lem
Making core for Roswell...
building dump:/Users/me/.roswell/impls/x86-64/darwin/sbcl-bin/1.4.12/dump/lem-ncurses.core

イメージの削除は ros delete dump コマンドでも実行可能。

$ ros delete dump lem-ncurses

ビルド時に使用されるソースコードはローカルプロジェクトに配置されているもの。

ビルドを行なっているのは Roswell。

イメージの作成は ros dump output
ただし、ros dump コマンドで作成した場合、Lem の起動に時間がかかるようになる。
何らかのオプションが必要なのかもしれない。

$ ros dump output lem-ncurses

-f オプションをつけると、すでにイメージが存在していた場合も強制的に作成できる。

$ ros dump output -f lem-ncurses

イメージの作成場所は -o オプションで指定できる。

$ ros dump output -f -o /tmp/lem.core lem-ncurses

ros build コマンドでビルドすることも可能。
ただし、この場合は .ros ファイルを指定する必要がある。
ros build コマンドでビルドすると、起動速度が速くなる。

$ cd ~/.roswell/local-projects/cxxxr/lem
$ ros build roswell/lem-ncurses.ros
Making core for Roswell...
building dump:/Users/me/.roswell/impls/x86-64/darwin/sbcl-bin/1.4.12/dump/lem-ncurses.core

ros dump executable で実行ファイルを作成することができる。

$ ros dump executable roswell/lem-ncurses.ros -o /tmp/lem.bin
$ /tmp/lem.bin 

Lem: リポジトリのダウンロード

$ git clone https://github.com/cxxxr/lem.git

Lem: 常用版と開発版の並存方法

  • 簡単にはできないっぽい
  • Roswell が ~/.roswell/bin/lem に Lem の起動スクリプトを配置してしまう
    • lem コマンドで起動できる Lem は一つだけ

このコマンドで任意のディレクトリにクローンした Lem のリポジトリから Lem を実行できます。

$ ros run -e '(pushnew (truename ".") ql:*local-project-directories*)(ql:register-local-projects)(ql:quickload :lem-ncurses)(lem:lem)(quit)'

コンパイルされたファイルは以下のディレクトリにキャッシュされるため、次回からは高速に起動します。

~/.cache/common-lisp/sbcl-1.4.12-macosx-x64/<Directory Name>

ソースコードを変更すると自動で再コンパイルしてくれるようです。

これでも OK。

$ ros run -e '(setf ql:*local-project-directories* (list #p"."))(ql:quickload :lem-ncurses)(lem:lem)(quit)'

これも OK。

$ ros run -e '(pushnew #p"." ql:*local-project-directories*)(ql:quickload :lem-ncurses)(lem:lem)(quit)'

ただし、これはダメ。

$ ros run -e '(push (truename ".") asdf:*central-registry*)(ql:quickload :lem-ncurses)(lem:lem)(quit)'

参考

Lem: ビルド時に必ずローカルプロジェクトのコードが使用される理由

  • ros build に渡す .ros ファイルで quickload を使用しているためっぽい

Safari で Ctrl-L を押すと、カーソル位置が画面中央にくるようにページがスクロールされる理由

  • emacs のキーバインド由来っぽい (recenter)
  • Unix ターミナルの Ctrl-L を想定していると違和感がある

10.28

SOSML - Online SML

SOSML is an online interpreter for the functional programming language Standard ML, written in TypeScript.

10.27

ToDo

MLWorks

MLWorks is a Standard ML compiler and development environment.

The open source license for MLWorks is the BSD 2-Clause License_.

Linux で動作するみたい。

Standard ML で実装されているため bootstrap が必要。

やっぱり ML のコンパイラーは ML で書くんですね。

CL-JavaScript

JavaScript compiler for Common Lisp

古いシンタックス (ECMAScript 3) の JavaScript を Common Lisp にトランスレートできる。
CL と JS の相互運用が簡単にできる。

インタープリタではなく、CL にトランスレートされるため、実行速度も速いみたい。

scm2cl

scm2cl is a Common Lisp program that converts Scheme code to Common Lisp.

CL の代わりに Scheme を書きたくなったら。
ただし、funcallfunction の自動挿入が完全ではないため、手動でケアする必要がある。
生成された CL のコードには scm2cl のルーチンが挿入される。

Pseudoscheme

CL で実装された Scheme。
CL と Scheme の相互運用ができる。

Yale Haskell の実装の中の mumble との関連性は不明。
Yale Haskell のものに比べると、ソースコードが増えているように見える。

使い方はよくわからない。
_loader.lisp を実行すれば良いのかな?

SBCL には対応していないっぽいので、事前に修正が必要。

良さそうな感じはする。
ただ、CL も Scheme も詳しくないので、デバッグは大変そう。

詳細

  • PSO は Pseudoscheme で生成された CL コード
    • Pseudo Scheme Output?

Translators to CL

Code related to changing or translating to/from Lisp syntax.

Yale Haskell

Common Lisp で実装された Haskell。
まず CL のマクロなどで scheme-likemumble という言語を実装し、mumble でコードが書かれている。

10.25

Paper: ML under Unix

やはり Standard ML が一番良いなー。

10.22

CL: EastAsianWidth.txt のパース

10.21

Lem: 開発・テスト方法

Lem の実装を変更・テストするにはどのような手順を踏むのが正しいか。

ソースコードは roswell の local-project に入っているので、そこを更新して起動したら良いのかもしれない。

ただ、常用する版と開発版とを分けておきたい。
開発版が起動しなくなったらエディタが起動しないという状況は避けたいので。

Lem: サンプルプログラム

OS コマンドを実行して、実行結果をバッファに出力するサンプルプログラムを実装してみました。

このプログラムから扱えるのは REPL 的な動作をするコマンドだけでした。
そうではないプログラム(例えば w3m)は pread 的な関数が必要でした。

まあ、サンプルプログラムなので汎用性はなくても大丈夫。

10.20

🥦が描画できなかった問題

  • Teraminal.app が対応していないっぽい
  • 🌪も表示がおかしい

一方、古い絵文字(💨など)が描画できないのは半角・全角判定のテーブルが古いため。

サンプルプログラムの作成

小規模

  • OS コマンドの操作
    • bc -l
    • shell
  • キーボードを使ったゲーム
    • rogue-like 的なもの
    • pong の移植

中規模

  • CLHS の検索

shell

これだけ実装すれば良い。

  • lemsh:start
    • サブプロセスとして /bin/sh を起動して、入力と出力を取得する
  • lemsh:write
    • 計算式を文字列で受け取り、/bin/sh プロセスに渡し、結果を受け取り、バッファに出力する
  • lemsh:exit
    • サブプロセスを終了する

/bin/sh に渡した文字列はその子プロセスに渡されるのか、確認が必要。

rogue-like

簡単なものならすぐ実装できそうですが、対応していない絵文字があるので保留中🔥💧🌪💎⚡️✨

Lem: ライブラリの開発

  • ローカルプロジェクトを作成して管理するのは最初は面倒
  • ファイルひとつにプログラムを書いて、load で読み込むのが簡単そう

Lem: 外部ライブラリ

ライブラリの読み込み

load-libraryql:quickload しているだけ。

ql:quickloadql:*local-project-directries* からローカルプロジェクトを読み込む。   ASDF については一旦考えない。

ライブラリのパス

ライブラリのインストール先は ${HOME}/.roswell/local-projects/ で良さそう。

% ros run -e '(write asdf:*central-registry*)(quit)'
(#P"/Users/<UserName>/.roswell/lisp/quicklisp/quicklisp/")%
% ros run -e '(write ql:*local-project-directories*)(quit)'
(#P"/Users/<UserName>/.roswell/local-projects/"
 #P"/Users/<UserName>/.roswell/lisp/quicklisp/local-projects/")%
% ros run -e '(write ql:*quicklisp-home*)(quit)'
#P"/Users/<UserName>/.roswell/lisp/quicklisp/"%

ローカルプロジェクトの作成

quiclprojectcl-project を使うと良いみたい。

(ql:quickload :quiclproject)
(quickproject:make-project ...)

プロジェクトの作成手順

  1. プロジェクトを作成する
  2. コードを書く
  3. テストをする
  4. GitHub にアップロードする

プロジェクトの利用手順

  1. ${HOME}/.roswell/local-projects/ に移動
  2. 同じ名前のローカルプロジェクトがないことを確認
  3. git clone https://github.com/<User>/<Product>.git でプロジェクトをクローン
  4. Lem から load-library で読み込む

Lem: HyperSpec を検索する

  • ファイル名は clhs.lisp
  • load で読み込む
  • 関数名は hyperspec-lookup
  • 適当なキーバインドで起動
  • カーソル付近のシンボルを探す
    • isearch-forward-symbol-at-point
  • clhs の index を開く
    • clhs の index はあらかじめパラメータに格納しておく
    • バッファに読み込んでも良い
  • index からシンボルを探す
    • isearch-forward-regexp でも良い
  • シンボルに対応する URL を確認
    • シンボルの次の行が URL
  • URL を w3m で開く

Lem: プログラムから Lem のバッファを再描画する方法

これを行いたい。

(prog ()
  (do-something)
  (refresh lem window)
  (do-another-thing)

そもそも長く時間がかかる処理をするようなプログラミングスタイルがダメなのかも。
ウェブブラウザ上のプログラムと同じ感覚で。

マルチスレッドを使用すれば解決できそうな気もしますが、そこまでやるのは大変そう。
むしろ JavaScript でコードを書くときと同じ感覚で実装するのが良さそう。

Lem: 関数が終了するまでバッファが更新されない

C-x C-e でプログラムを実行している間はプログラムの側に処理が移っていて、Lem はバッファの更新などができない状態なのだと思われる。

;;;; This works OK on REPL but introduces some delay on Lem.
;;;; That's because the evaluation is being done in the context of Lem.

;; wait three seconds and print "foo"
(prog ()
  (print "foo")
  (finish-output nil)
  (sleep 3))

CL: uiop:launch-program

だいたいこんな感じ。

バッファにデータが残っているかどうかは listenread-char-no-hang で調べられる。
ただし、すぐに listen すると、サブプロセスからの出力が返ってきておらず、nil が返ってしまう。
そのため、遅延を入れる必要がある (listen-with-timeout)。

関数を抜けるまではプログラム側が実行コンテクストを握っており、Lem の側は処理ができない状態。
そのため、バッファは更新されない。
wait してはいけないということなのかも。

(defun listen-with-timeout (timeout interval in)
  (if (< timeout 0)
      nil
      (if (listen in)
          t
          (progn
            (sleep interval)
            (listen-with-timeout (- timeout interval) interval in)))))

(defun read-all (in)
  (labels ((%read-all (acc in)
             (let ((timeout 0.01)
                   (interval 0.01))
               (if (listen-with-timeout timeout interval in)
                   (%read-all (cons (read-line in) acc) in)
                   acc))))
    (%read-all nil in)))

(defun write-and-flush (str out)
  (progn
    (write-line str out)
    (finish-output out)))

(defun write-buffer (str)
  (let ((point lem-base:current-point))
    (lem-base:insert-character point #\newline)
    (lem-base:insert-string point str)))

(let* ((stdout *standard-output*)
       (p (uiop:launch-program "bc -lq"
                               :input :stream
                               :output :stream))
       (p-in (uiop:process-info-input p))
       (p-out (uiop:process-info-output p)))
  (write-and-flush "1 * 2 * 3 * 4" p-in)
  (print (read-all p-out) stdout) ;; not printed until this function has done since we are in the function under evaluation
  (write-and-flush "10+10" p-in)
  (print (read-all p-out) stdout) ;; not printed until this function has done since we are in the function under evaluation
  (write-and-flush "quit" p-in)
  (uiop:close-streams p)
  (uiop:wait-process p)) ;; do not wait!  it takes time and introduces much delay

10.19

CL: OS コマンドとインタラクティブなやり取りをする

(let* ((p (uiop:launch-program "bc -l"
                               :input :stream
                               :output :stream))
       (myin (uiop:process-info-input p))
       (myout (uiop:process-info-output p)))
  (write-line "3 * 3 + 2" myin)
  (finish-output myin) 
  (print (read-line myout))) 

Lem: uiop が遅い問題

原因は謎。
コマンドをリストで括れば解決するので、緊急性は低い。

OS コマンドを文字列で渡した場合に遅い

(uiop:run-program "date" :output :string)

この場合はコマンドの先頭に "exec" が追加されて launch-program される。

       (return-from %system
         (wait-process
          (apply 'launch-program (%normalize-system-command command) keys)))))

launch-program はネイティブの run-program が呼び出している。

           #+sbcl 'sb-ext:run-program

OS コマンドをリストで渡すと速くなる

(uiop:run-program '("date") :output :string)

launch-program を呼び出している。

            (setf process-info
                  (apply 'launch-program command
                         :input reduced-input :output reduced-output
                         :error-output (if (eq error-output :output) :output reduced-error-output)
                         keys))

launch-program はネイティブの run-program が呼び出している。

           #+sbcl 'sb-ext:run-program

Lem の外で実行すると速い

$ ros run
* (uiop:run-program "date" :output :string)

Lem: init.lisp に書くと速くなる問題

単純に load しているだけっぽい。

Lem: 目下の問題

解決可能

  • Unicode 7.0 までしか対応していない
    • 絵文字が全角になっていない(絵文字の全角判定は Unicode 9.0 から)
    • テーブルを更新すれば解決可能
  • Ambiguous に対応していないと思われる
    • テーブルを追加
    • wide-char-p を更新
  • JavaScript モードが ".mjs" に対応していない
    • これは追加してあげれば良い
  • CLHS がひらけない
    • w3m に対応すれば良い

解決方法不明

  • ビルド手順がわからない
    • パッチを送るには必須
    • とりあえず git clone して考えてみる

余裕があったら調査する

  • エディタ上で uiop:run-program を実行すると遅い
    • OS コマンドを文字列で渡すと遅い
    • OS コマンドを文字列のリストで渡すと速い
    • 文字列のリストで渡せば良いので、調査は不要
    • Lem 内で使用されているコードは直した方が良いかもしれない
  • async-process パッケージが含まれていない
    • Scheme Mode が実行できない
    • 別途 ros install する必要がある
    • 実行時に ql:quickload する必要がある
    • uiop:run-programsb-ext:run-program を使えば良いので、調査は不要

調査しない

  • エディタから直接 (lem-vi-mode:vi-mode) すると遅い
    • init.lisp に書くと速い
    • OS の設定でキーリピートを速くすることで解消しているので深くは調査しません
  • 🥦が表示されない
    • 全角判定すれば直るかもしれない
    • Lem を使わず、直接 Terminal.app に入力した時も動作がおかしいので、Lem の問題ではなさそう

SBCL: run-program

(run-program "/bin/ls" nil :output *standard-output*) 

uiop:run-program と違って Lem 上でも実行は一瞬で終わる。

uiop か native か

Clozure CL にも同じ関数があるみたいなので、uiop よりこっちの方が良いかもしれない。

ただし、SBCL と CCL はオプションやプロセスを操作する関数に差異がある。

ポータビリティを考えると uiop が無難。
ラッパーを書くのもあり。

インタラクティブな実行

:wait nil を指定するとプロセスの終了を待たない。

(run-program "/bin/sleep" '("3") :wait nil)

これを使うとインタラクティブなコマンドの実行ができる。

CL: uiop

UIOP, the Utilities for Implementation- and OS- Portability

ASDF は使っている英語が汚いので、あまり触りたくない。。

Lem: サブプロセスの実行方法

一方 Lem は uiop を使っていました。

uiop:run-program で同期的にサブプロセスを実行できるみたい。
uiop:launch-program を実行すると、非同期でサブプロセスを実行できる。

;; ls コマンドを実行し、結果をバッファに書く
(lem:insert-string
 (lem:current-point)
 (uiop:run-program "ls" :output :string)

ただしどうも実行速度が遅い。
ls コマンドの実行に 2 秒弱かかっている。

ros run で実行すると一瞬で終わるので、Lem から実行した場合の問題かも。

$ ros run
* (uiop:run-program "ls" :output :string)

vim-mode がエディタから実行すると遅かったのと同じ問題かもしれない。

コンパイルしても遅い。

(compile 'uiop:run-program) 
(uiop:run-program "ls -l") 

Lem: run-process の実行方法

async-process をインストールする。

$ ros install cxxxr/async-process

quickloadlem-process パッケージをロードしてから run-process を呼び出す。

(ql:quickload 'lem-process)
(lem-process:run-process '("ls"))

これが正解なのかは不明。
そもそも lem-package がロードされていないこと自体を解消した方が良いと思われる。
ただし、正解を見つけるのは大変そう(CL のツール周りの知識が全然ないため)。

とりあえずサブプロセスの実行はできたので、ライブラリのロードはブラックボックス化して先に進んでしまうのが良さそう。

Lem: run-process ができなかった

lem-process パッケージがないと言われてしまう。

(use-package 'lem-process)
;; => The name lem-process does not designate any package.

実際、list-all-packages でも lem-process パッケージはない。

v1.4 のソースコードには存在しているので、原因不明。

Scheme Mode が run-process を使っているみたいなので、試してみると良いかも。

ダメでした。。

(lem-scheme-mode:scheme-eval-last-expression) 
;; => The function lem-scheme-mode:scheme-eval-last-expression is undefined.

Scheme Mode は最新版をビルドしないと使えないかも。

cxxxr/async-process のインストールが必要かも。

(ql:quickload 'lem-process)
;; => System "async-process" not found
$ ros install cxxxr/async-process
$ ls ~/.roswell/local-projects/cxxxr
async-process	lem

できました。

(ql:quickload 'lem-process)
(lem-process:run-process '("ls"))

10.18

CL: ToDo

  • CLHS と w3m をインストールする
  • CL でパーサーコンビネータを書く
  • CL で EastAsianWidth.txt をパースする
  • CL で全角・半角判定プログラムを書く

Lem: ToDo

  • ビルド方法を調べる
  • Lem エディタプログラミングをまとめる
  • w3m をサブプロセスで呼び出せるようにする
    • CLHS を読みたいだけなので、最低限リンクが辿れるだけで良い
  • Lem から pbcopy を呼び出したい
    • バッファの中身を OS コマンドに渡す方法
    • kill バッファの中身を OS コマンドに渡す方法
  • ES Modules 対応
    • .mjs も JavaScript モードで開けるようにしたい
    • その他、対応が必要?

CL: パーサーコンビネータ

funcall が面倒くさいです。。

基本パーサーは、文字列、正規表現、任意の一文字、空文字の 4 種類。
正規表現は cl-ppcre を使用する感じでしょうか。

string-parser

文字列の同値性確認は string=
string-equal だと大文字・小文字を区別しない。

;; string-parser
(defun string-parser (match) #'(lambda (str) (string= match str)))

(funcall (string-parser "hello") "hello")
;; => t

(funcall (string-parser "hello") "bye")
;; => nil 

ちゃんと書くときは以下を追加。

  1. match 文字列の長さを保持
  2. str 文字列から match 文字列の長さ分の部分文字列を取得
  3. match 文字列と部分文字列を比較
  4. マッチした文字数、残りの文字列、を返す
  5. マッチした文字数が 1 以上ならパース成功

Emacs-w3m

外部コマンドとして w3m を実行して結果を取得しているんだと思うけど、ハイパーリンクの扱いはどうしているのでしょう。

単純にキー入力をそのまま渡してレンダリング結果を受け取るのも可能ではある。
そうするとキーバインドをエディタ側に寄せるのか、w3m 側に寄せるのか検討が必要になる。
普通はエディタ側に寄せたいので、w3m 固有のキーバインドとの住み分けを考える感じかな。

cookie, frame, mailto, form などにも対応しているみたい。
historybookmark などの機能もある。

CL: CLHS のページを開く

Symbol Index のページにリンクがまとまっているので、そこから辿れば良い。

こんな感じで閲覧できそう。

  1. CLHS と w3m をローカルにインストールしておく
  2. シンボル名を入力
  3. ページの URL を取得
  4. w3m で HTML をテキストに変換
  5. ウィンドウを開く
  6. ウィンドウにページのデータを出力

UnicodeData.txt

Unicode のデータ。

10.17

Lem: Unicode 対応は 7.0 相当

  • こちらwide.lisp のテーブルが同じなので Unicode 7.0 のデータを使用しているものと思われる
  • Lem は Ambiguous 無し
  • Unicode 8.0 が 2015 年 6 月頃らしいので、Lem 開発初期の最新バージョンの Unicode データを参照したのかも 

Unicode の最新バージョンへの対応はこういうことが必要なんだね。

EastAsianWidth.txt

これをパースすれば、全角・半角判定用のデータができる。

F : Fullwidth
W : Wide
H : Halfwidth
Na : Narrow
A : Ambiguous
N : Neutral

F と W が全角
H, Na, N は半角
A は環境によって異なる(日本語環境では全角)

A については設定で切り替える実装が多いみたい。

合字的な文字についての扱いは別途対応が必要。

判定用データの作成

wide.lisp を参考にする。

  • データは開始位置、終了位置をペアにしたベクター
  • 全角の方が少ないので、全角の範囲をまとめる
  • 全角が連続した複数の領域に別れている場合があるので、まとめる

判定データの検索

バイナリサーチすれば良いみたい。
wide.lisp もバイナリサーチしている。

絵文字は全角か

⌚︎ など、昔からある絵記号の扱いはどうなっているんだろう。

Lem は Unicode 9.0 以前の実装であるため、絵文字が全角であると判断されていないものと思われる。

10.16

Atom: Atom Flight Manual - Hacking Atom

Atom を JavaScript API で操作する方法。

Lem: 絵文字対応

パッチ送りたいけど、それ以前にビルドの方法がわからない。

全角文字として認識されない

  • 絵文字を全角 1 文字として認識できないみたい
  • SBCL での扱いも確認する必要がある
(write-to-string (char-code #\🍣) :base 16)
;; => "1F363"
(write-to-string (char-code #\鮨) :base 16) 
;; => "9BA8"

(lem:wide-char-p #\鮨) 
;; => t
(lem:wide-char-p #\a)
;; => nil
(lem:wide-char-p #\🍣) 
;; => nil

wide.lisp のテーブルの範囲に入っていないものと思われる。

Emoji Charts に絵文字の一覧がある。
他の実装としては、linenoise の utf8.c などが参考になる。

以下は要検討。

  • Emoji は全て Fullwidth と考えて良いか
  • ZWJ の扱い

🥦が表示されない

🍄は OK

(write-to-string (char-code #\🥦) :base 16) 
;; => "1F966" 

(write-to-string (char-code #\森) :base 16)
;; => "68EE" 
  • Terminal.app 側の問題っぽい

10.15

macOS: デフォルトのキーリピート設定

  • キーのリピート : 右から 2 番目(左から 7 番目) [......*.]
  • リピート入力認識までの時間 : 右から 4 番目(左から 3 番目) [..*...]

どちらも最速にすることでターミナルのレスポンスが良くなる。

defaults コマンドを使用するとさらに早くできるみたい。

10.13

plzoo

  • miniml には型チェックが実装されている
  • minihaskell にはパターンマッチが実装されている
  • poly には型推論とパターンマッチが実装されている

ただし、パターンマッチは以下の形式に限る。

match expr with
    val      => e1
  | hd :: tl => e2

CL: パッケージの導入方法

  • 要調査
  • パッケージの検索
  • パッケージのインストール
  • パッケージがインストールされる場所
  • パッケージの削除
  • パッケージのコンパイル
  • パッケージのロード

CL: S 式の自動整形

  • pprint でできました
(let ((*print-case* :downcase)) 
  (pprint '(labels ((f (n acc) (if (= 0 n) acc (f (- n 1) (cons n acc))))) (f 10 nil))))

;; output
(labels ((f (n acc)
           (if (= 0 n)
               acc
               (f (- n 1) (cons n acc)))))
  (f 10 nil))

CL: 再帰関数

  • let* では再帰関数を定義できない
  • labels を使う必要がある
(let* ((f (lambda (n acc)
                  (if (= 0 n)
                      acc
                      (funcall f (- n 1) (cons n acc))))))
      (funcall f 10 nil))
=> The variable F is unbound.

(labels ((f (n acc) 
            (if (= 0 n) 
                acc 
                (f (- n 1) (cons n acc))))) 
  (f 10 nil)) 
=> (1 2 3 4 5 6 7 8 9 10)

Common Lisp

  • funcall がダルい

  • let* で再帰関数がかけない

  • ローカル定義は全て let で済ませてしまいたいのに、それができない

  • 手続き的なコードはなるべく書きたくない

  • setq は使いたくない

  • format が分かりづらい

Lem を使ってみて

  • vi と比較しなければ十分使える
  • 楽しい
  • もっと勉強したい
  • ドキュメントが少ない(ほとんどない)ので実装を確認する必要がある

vi と比較して

  • カーソル移動の自由がない

    • vi は編集したい位置にカーソルを移動させる感じ
    • emacs は編集したい位置が中心になるようにページをスクロールさせる感じ
  • . で繰り返すみたいなショートカットがないのが辛い

  • 基本的なテキスト編集系のコマンドが足りない

    • モードレスなので仕方がないですが
  • 片手で操作できない

    • モードレスなので...
  • vim script は使ってみたいと思わなかったので、CL で拡張を書ける lem は良い

  • 学習の意味も込めて、vi-mode は使わずにしばらく過ごしてみたい

vi として

  • いざとなったら vi-mode で vi 系のエディタとして使うのもあり
  • 以前は素の vi をずっと使っていた
  • vim も vi 系のエディタの一つでしかないので、lem を vi として乗り換えるのはあり
  • もう一つの vi として使う
  • カスタマイズも楽しそう

emacs 系エディタとして

  • lem は快速
  • 起動も速い
  • フルセットの CL が組み込まれているので拡張性が高い
  • 機能は少ない
  • 情報も少ない
  • ユーザも少ない
  • CL でコードを書けるのは面白い

拡張可能なエディタとして

  • 拡張部分もネイティブコンパイルされるので、速度について心配する必要がないのは良い
  • emacs も vim も拡張部分の動作は遅くなってしまうので

ただのテキストエディタとして

  • 普段から atom や TextEdit.app も使っているので問題ない
  • ウェブブラウザのテキストエリアでテキストを編集したりするし

vi のキーバインドを忘れてしまわないか

  • これまでもシェルのコマンドライン編集は emacs のキーバインドで行なっていた

  • TextEdit.app や Safari のテキストエリアのキーバインドも emacs 系

  • vi のキーバインドと emacs のキーバインドを併用してきたのでおそらく問題はない

  • 忘れてしまいそうなら vi-mode を使う

その他

  • xyzzy は長く開発がストップしていたみたい
  • そういったリスクはあるけど、いま考えても仕方がないし、その時は vim に戻れば問題ない

Lem: Lisp の出力をバッファの中に書き込むのはどうするのか

  • S 式を評価した結果はミニバッファや Lisp バッファに出力される 
  • プログラムを実行したバッファに出力したい

Lem: Lisp のエラー出力がおかしい

  • 地のドキュメントと混ざり合ってしまう
  • Lisp Mode を使っていないからかな
    • 関係なかった

Lem: CLHS のインストール

ここまでは良いけど、その先がわからない。

$ brew install hyperspec
$ brew install w3m

エディタの中でリファレンスを読みたい。

オンラインマニュアルは M-x lisp-describe-symbol で参照することにします。

Emacs の場合は以下のように設定するみたい。

(eval-after-load "slime"
  '(progn
     (setq common-lisp-hyperspec-root
           "/usr/local/share/doc/hyperspec/HyperSpec/")
     (setq common-lisp-hyperspec-symbol-table
           (concat common-lisp-hyperspec-root "Data/Map_Sym.txt"))
     (setq common-lisp-hyperspec-issuex-table
           (concat common-lisp-hyperspec-root "Data/Map_IssX.txt"))))

ダイスロールの演出

Level 1

ただのダイスであれば 1 ~ 6 の間でランダムな数値を作るだけ。

const d1 = () => Math.ceil(Math.random() * 6)

ただし、これだと味気なくてつまらない。

Level 2

こうすると多少雰囲気が出る。

const faces = ["⚀", "⚁", "⚂", "⚃", "⚄", "⚅"]
const d2 = () => faces[Math.floor(Math.random() * 6)]

level 3

リールにするとドキドキ感が出る。

// returns n where min <= n <= max
const rand = (min, max) => {
  min--;
  const range = max - min;
  const ret = Math.ceil(Math.random() * range) + min;

  return ret;
};

const sleep = sec => new Promise(rs => setTimeout(rs, sec * 1000));

// reel
const d3 = async () => {
  let count = rand(2, 8);

  while (count--) {
    let result = d1();
    console.log(result);

    if (count == 0) {
      console.log(`result is ${result} !`);
      return;
    }
    await sleep(1);
  }
};

d3();

ダイスの結果は以前の方式と変わらない。
結局は、乱数を 1 回使って 1 ~ 6 の間の数字を求めているだけ。
それなのに明らかに娯楽性が追加されている。

結果が出るまでの余韻。
リールがいつ止まるかわからない不規則性。
情報量が増えることによる混乱。
数字の連続に意味があるのではないかという錯覚。
ここで止まっていれば... といったような一喜一憂。

本質は変えずに、少し表現方法を変えただけで、心理的な効果が生まれた。

こんな感じで工夫を続けていって、Level 100 くらいのものを作るとしたらどうなるでしょう?

目の出し方も工夫できる

  • ただの数字
  • サイコロ
  • トランプ
  • ルーレット
  • リール
  • あみだくじ
  • ダイスタワー

乱数の決定方法

  • 物理演算

10.12

JavaScript で拡張できるエディタがあると嬉しい

  • ブラウザの JavaScript コンソールのような感じで設定を変更できる
  • 端末エミュレータで動作する

Vim.js

Vim.js : JavaScript port of Vim

JSVI

fork of JSVI - VI in JavaScript

これはウェブブラウザで動作する vi クローンっぽい。

Lem

Lem is the editor/IDE well-tuned for Common Lisp.

Lem にまとめます。

Install

$ brew install roswell
$ ros install cxxxr/lem
$ export PATH=${PATH}:${HOME}/.roswell/bin
$ lem

普段 vi を使っているので、常時 insert mode なのは慣れない感じ。

vi-mode

opt-x vi-mode で vi-mode を開始できる。

jk を押しっぱなしにしてカーソルを移動すると、カーソルの表示がカーソルの移動に追随できないタイミングがある。

  • これは OS のキーリピートの設定を変更したら解消しました

カーソルの移動速度比較

比較対象

  • vi で j キーを押しっぱなしにして端末の一番上から下までカーソルを移動する
  • emacs で Ctrl-n を押しっぱなしにして端末の一番上から下までカーソルを移動する

テスト方法

$ man ascii | col -b > ascii.txt
$ time vi ascii.txt
$ time lem ascii.txt

測定は厳密ではない:

  • ファイルを開く時間も入っている
  • :qCtrl-x Ctrl-c の入力時間の違い

テスト結果

Editor 1 2 3
vim 7.069 6.810 6.707
lem 6.646 6.714 6.752
lem(vi-mode) 6.748 6.863 6.569

カーソルの連続移動速度が遅いわけではなかった。

気づいた点

  • opt-x vi-mode で vi-mode を使用する場合と、~/.lem/init.lisp(lem-vi-mode:vi-mode) を設定して vi-mode を使用する場合ではカーソルの移動速度が異なった
  • vim と比較して、lem ではモードラインの更新が発生するため、画面の更新の遅れやチラツキが発生しているように感じる

前述の通り、キーリピートの設定を変更したところ解消しました。

Lem: モードラインが更新されないようにしたい

モードラインの更新が入ると画面の更新が遅くなるため。
emacs だと (setq mode-line-format nil) でモードラインを消せるみたい。
lem ではモードラインは modeline-format みたいですが、(setq lem:modeline-format nil) しても表示は消えず。

Emacs: モードラインを消す

  • (setq mode-line-format nil)Ctrl-x Ctrl-e

モードラインを消したら画面の更新の遅れがなくなりました。
vi に比べて emacs が遅いと感じていた原因はこれでした。

プラシーボの可能性もありますが、確かに快適になった感覚がある。
あるいは、モードラインが更新されているのを無意識に知覚していたことがストレスになっていたのかもしれません。
(ウェブブラウザのステータスバーでマーキーされると鬱陶しいのと同じ感じで)

普段はカーソルの位置情報は必要ないので、常時消去で問題ない。
ただ emacs は全体的に遅すぎて使えない。

Fengari

The Lua VM written in JS ES6 for Node and the browser

Feggari ではないみたい。

WebML

WebML is to be a Standard ML (SML '97) Compiler that works on web browsers and generates WebAssembly binaries.

SML を WebAssembly にコンパイルする。
Rust で実装されており、nom を使っている。

PreML

PreML is a preprocessor for Standard ML. It’s aim is to extent the language with some useful syntactic sugar.

ML Family Workshop

Proposals for new Basis Library modules

確かに SRFI 的なものがあると嬉しい。
Unicode とか String Interpolation とかではなかった。

SML/NJ: String Interpolation

`^(SML expression)` のように書けるみたい。

10.11

Elm : Custom Operator

これは Elchemy の issues ですが、Elm の Custom Operator は JavaScript にコンパイルされる際に特殊な文字列に置き換えられるみたい。
面白い。

10.10

elm-beam

elm-beam compiles Elm to assembly that runs on the Erlang Virtual Machine.

Elm のコンパイラは Elm のもの(Haskell による実装)をそのまま利用しているみたい。

Elchemy

Write Elixir code using statically-typed Elm-like syntax (compatible with Elm tooling)

Elm のコンパイラが Elm で実装されている。 infix は使ってないみたい。

Elm のソースコードは空白が多い印象(インデントが深い)。

test262

Official ECMAScript Conformance Test Suite

10.9

Left Factoring

昨日の左再帰の変換を (文法 -> 文法') の変換として行うことを Left Factoring と呼ぶみたい。

(Parser -> Parser') の変換をするよりも使いやすそう。

GLL: Generalized LL

Custom Operator の実装

構文木を作成した後に再スキャンする(構文木は書き換わる)。

コンパイラを実装するということは、いつでも実装に手を入れられるということであり、Custom Operator ではなく組み込みの演算子を増やすことも可能ということではある。

Haskell の Custom Operator の実装

(現在も同じかどうかわかりませんが)構文木を作成した後に再スキャンする方式。

10.8

PEG: 左再帰の除去

以下のような文法があったとする。

expression <- expression '+' expression / numeral
numeral    <- '0' / '1' / ...

これは左再帰の文法なので、PEG パーサでパースすると無限再帰になってしまう。

そこで以下のように書き換える。

expression <- numeral expr_tail
expr_tail  <- ('+' expression)*
numeral    <- '0' / '1' / ...

書き換えた結果、左再帰は無くなるので、無限再帰には陥らない。

expr_tail は 0 回以上の繰り返しなので、expression は以下のように展開される可能性がある。

  • numeral
  • numeral + numeral
  • numeral + numeral + numeral ...

これは元の文法と同じなので、安全に変換ができたことになる。
(ただし、構文木としては右再帰になるので、パースした後で左再帰に直してあげる必要がある)

上記の左再帰の除去手順は以下の通り。

  • expressionexpressionexpr_tail に分割する
  • 再帰が発生しない numeralexpression の先頭に移動する
  • numeral は左再帰の原因になっていた expression の代替でもある
  • + expression の部分を expr_tail に移動する
  • expr_tail は 0 回以上の繰り返しにする
  • expr_tail の出現が 0 回だった場合は、修正前の文法の / numeral の部分に相当する結果になる
  • expr_tail の出現が 1 回以上だった場合は、修正前の文法の expression '+' expression の部分に相当する結果になる

この変換を自動で行いたい。

自動変換は以下のような実装になる。

const left = (rec, tail, val) => {
  const parser = lazy(() => seq(val, tail_parser));
  const tail_parser = rep0(tail);

  return parser;
}

// expression <- expression '+' expression / numeral
const expression = lazy(() => left(expression, seq(plus, expression), numeral));

とりあえず個人的にはこれで満足。
調べた限りでは同じことをしている例は見つからなかったので、一般的に必要とされている方法ではないのかも。

PEG: 左再帰

  • Memoize することで左再帰を可能にする(再帰レベルを限定して無限再帰を発生させないようにしている)

  • 再帰レベルの限界は accept される入力の数が増えるかどうかで決める

  • Pegged Left Recursion

PEG: 演算子の優先順位 

  • 優先順位を反映させる仕組みはないみたい

  • パーサーのルールに優先順位を組み込む必要がある

  • 例えば、以下のルールは Sum のなかに Product の参照があるので、Product の方が優先順位が高くなる

Expr    <- Sum !.
Sum     <- Product (("+" / "-") Product)*
Product <- Value (("*" / "/" ) Value)*
Value   <- [0-9]+ / "(" Sum ")"

PEG: 二項演算子

右結合になるので、直すのが大変だった。
transform 関数でパーサーの出力を書き換えている。

優先順位と結合

PEGs in a PEG

PEG のメタサーキュラーコンパイラ (A Metacircular Compiler-Compiler) を作成しているドキュメント。
時間があるときに読みたい。

Parser Combinator のエラーハンドリング

個々のパーサーは自分が扱う範囲の情報しか持っておらず、入力全体を見渡した判断ができない。
そのため、正しいエラーを返すのは難しい。

以下は PEG の Parser Combinator を前提としています。

or のなかに or がある場合

  • 親の or のエラーとするか、子の or のエラーとするか

or のなかに seq がある場合

  • どの枝で発生したエラーを返すべきか

notp

  • 子供のパーサーは成功しているので、エラーの表現が難しい
  • 子供のパーサーが入れ子になっていたり、複雑な構成の場合は正しいエラーメッセージを設定するのが難しい

エラー箇所の決定について

  • 最も単純なのは最長一致
    • 長いトークンが優先される(これは問題ではないかも)
  • cut セマンティクス
    • ここまでパーシングが進んだらバックトラックしないポイントを設定する
    • cut オペレータを越えた先でエラーが発生したら、その枝のエラーとする
  • or のなかの seq が一つでも成功していたらバックトラックせず、その枝を失敗とする

最長一致が適切な感じがします。
Parsec も最長一致らしい(要確認)。

  • エラー位置が 0 文字目だった場合
    • その or の失敗とする

エラー表現について

  • カスタムエラーメッセージを設定する
  • on_error みたいな、エラーメッセージ専用のコンビネータを作成するといいかも
    • ただし、構文規則からパーサーを自動生成するときに、工夫が必要となる

参考

10.7

Eff

Eff is a functional programming language based on algebraic effects and their handlers.

Koka: a function-oriented language with effect inference

The word ‘koka’ (or 効果) means “effect” or “effective” in Japanese

OCaml effects examples

参照透明には興味がないし、深入りしない方がよさそう。

gluon

Gluon is a small, statically-typed, functional programming language designed for application embedding.

どこらへんがどのくらい small なのかはよくわからず。

A mostly functional haskell compiler written in rust

10.6

JSON.Stringify の第 3 引き数でインデントを指定できる

第 2 引き数は null で OK。

ToDo

やりたいこと。

MathML は GitHub Wiki では使用できない

残念。

x 2 + 1

The Little MLer

本日時点で JPY 4,197 or USD 36.84。
ほとんど為替レートの通り。

[追記] JPY 4,240 @ 10/19 -- 値段は変動するみたい

内容は良さそうですが、初級者向けの言語学習教材な感じ。
The Little Schemer のような、言語外の学習要素(再帰や Y Combinator など)はないっぽい。

The Definition of Standard ML

Core (Core Language)、Modules、Program がある。
Core には Bare Language と Derived Forms がある。
Modules にも Derived Forms がある。

Derived Forms は構文糖衣。
例えばリストリテラル [a, b, c] (a :: b :: c) や fun (let rec) は Core の Derived Forms。

10.4

適用順 Y コンビネータ

  • Applicative-Order Y Combinator の訳語
  • Scheme 手習いで使用されている用語
  • Strict (Call-by-Value) な言語用に、Y Combinator の一部を明示的な遅延処理(クロージャ)に書き換えたバージョン
  • Z Combinator のこと

Z Combinator 1 の展開

Z(g) = f => (x => f(y => x(x)(y)))(x => f(y => x(x)(y)))(g)  // 1
     = (x => g(y => x(x)(y)))(x => g(y => x(x)(y)))  // 2
     = g(y => (x => g(y => x(x)(y)))(x => g(y => x(x)(y)))(y))  // 3
     = g(y => (f => (x => f(y => x(x)(y)))(x => f(y => x(x)(y)))(g))(y))  // 4
     = g(y => Z(g)(y))  // 5
     = g(Z(g))  // 6
  1. 引数 g を追加
  2. 引数 g を適用
  3. 前半の式に後半の式を適用
  4. 1 => 2 の逆の変換を適用
  5. 1 の右辺に相当する部分を左辺 Z(g) に置換
  6. 引数 y を適用

Z Combinator 2

const Z = f => (x => x(x))(x => f(y => x(x)(y)))

const fact = Z(f => n => {
  if(n == 0) {
    return 1;
  } else {
    return n * f(n - 1);
  }
});
console.log(fact(5));

Z Combinator 2 の展開

Z = f => (x => x(x))(x => f(y => x(x)(y)))
  = f => (x => f(y => x(x)(y)))(x => f(y => x(x)(y)))  // Z Combinator 1
  // 以下 Z Combinator 1 と同じ

Z Combinator が再帰になる理由

関数 (x => f(y => x(x)(y))) に引数 (x => f(y => x(x)(y))) を適用すると f の呼び出しが無限に増えていくため再帰になる。

Z = f => (x => f(y => x(x)(y))) (x => f(y => x(x)(y)))  // Z Combinator
Z = f => f(y => (x => f(y => x(x)(y))) (x => f(y => x(x)(y))) (y))  // 1
Z = f => f(y => f(y => (x => f(y => x(x)(y))) (x => f(y => x(x)(y))) (y)) (y))  // 2
Z = f => f(y => f(y => f(y => (x => f(y => x(x)(y))) (x => f(y => x(x)(y))) (y)) (y)) (y))  // 3
  1. 前半の式に後半の式を適用
  2. 前半の式に後半の式を適用
  3. 前半の式に後半の式を適用

これもちゃんと動作する。

const Z = f => f(y => f(y => f(y => (x => f(y => x(x)(y))) (x => f(y => x(x)(y))) (y)) (y)) (y));

const fact = Z(f => n => {
  if (n == 0) {
    return 1;
  } else {
    return n * f(n - 1);
  }
});
console.log(fact(5));

10.2

Z Combinator 1

// Z = λf => (λx => f (λy => x x y)) (λx => f (λy => x x y))
const Z = f => (x => f(y => x(x)(y)))(x => f(y => x(x)(y)));

const fact = Z(f => n => {
  if (n == 0) {
    return 1;
  } else {
    return n * f(n - 1);
  }
});
console.log(fact(5));

論理包含で f -> t が t になる理由

綺麗な説明があるわけではなく、そういう定義でないと困ることがあるからみたい。

「論理学入門」講義ノート には説明がなかった。

10.1

Shell: 特定の時間の範囲内に作成されたファイルを検索する

2018.09.30 00:00 ~ 2018.10.01 00:00 の間の場合

% find . -type f -newerBt "20180930" -not -newerBt "20181001"

オプションを覚えるのが大変なので、こういうコマンドが欲しい。

$ findfile -f "20180930" -t "20181001"
⚠️ **GitHub.com Fallback** ⚠️