読者です 読者をやめる 読者になる 読者になる

Schemeの紹介と内包表記マクロ

Scheme

LispとS式

SchemeCommon LispのようなLisp系言語の強みは強力なマクロ機能にある。LispではプログラムはS式と呼ばれる形式で記述され、それは、Lispがデータとして扱うリストと全く同じ構造を持っている。そのため、LispがデータとしてLispのプログラムを簡単に処理できるのだ。マクロを使うと、特定の問題に特化した、新しい言語とさえ呼べるような機能 (DSL) を簡単に作成して使うことができる。

Schemeのプログラムは基本的にはすべて以下のような構造を持っている。これ自体がS式であり、関数や引数の部分にはまたS式を入れることができる。関数の呼び出しでは引数は先に評価されて関数に渡されるが、マクロでは評価前の式がそのまま渡される。

(関数 引数 引数 ...)
(+ (apply * (iota 10 1)) (square *e*)) ; プログラムの例
1 ; 括弧なしの1つの式もS式

Schemeとマクロ

Schemeにはマクロ展開時の変数名の衝突を自動的に回避してくれる健全なマクロが用意されている。(意識的に衝突を起こしたり、展開時の環境に変数を束縛したりしたい場合は、処理系によっては、explicit renamingによるマクロやdefine-macroによるマクロなども利用できたりする。) syntax-rulesは独自のパターンマッチ言語を使ってコードを置き換える感じなので、簡単だがScheme自体でリストを操作していないという不満もあるらしい。R7RS-largeで入るとうわさされているexplicit renamingによるより低レベルなマクロではリストをより直接操作できる。

最近の言語では大抵用意されていたりするリストの内包表記。SchemeでもSRFI 42に用意されているが、なんとなく馴染む構文が欲しかったので実装してみた。また、Haskellのように[1..10]みたいにリスト生成したり、Fortranのようにa(2:5)でリストのスライスが欲しかったりしたのでこれも書いてみた。

(import (scheme base) (srfi 1))

; リスト生成マクロ
(define-syntax l:
  (syntax-rules (..)
    ((_ e) (l: 0 .. (- e 1)))
    ((_ s .. e) (if (> e s) (l: s (+ s 1) .. e) (l: s (- s 1) .. e)))
    ((_ s t .. e) (let* ((diff (abs (- t s)))
                         (start (min s e))
                         (end (max s e))
                         (lst (iota (+ 1 (quotient (- end start) diff)) start diff)))
                    (if (> e s) lst (reverse lst))))))

; リストのスライスを取るマクロ
(define-syntax l/s
  (syntax-rules (:)
    ((_ l s : e) (l/s (l/s l : e) s :))
    ((_ l s :) (drop l s))
    ((_ l : e) (take l (+ e 1)))
    ((_ l i) (list-ref l i))))

; 内包表記マクロ
(define-syntax l/c
  (syntax-rules (: <- .. :=)
    ((_ lm : (x <- xs) lc ...) (apply append (map (lambda (x) (l/c lm : lc ...)) xs)))
    ((_ lm : (x <- s .. e) lc ...) (l/c lm : (x <- (l: s .. e)) lc ...))
    ((_ lm : (x <- s t .. e) lc ...) (l/c lm : (x <- (l: s t .. e)) lc ...))
    ((_ lm : (d := ds) lc ...) (let ((d ds)) (l/c lm : lc ...)))
    ((_ lm : c lc ...) (if (not c) '() (l/c lm : lc ...)))
    ((_ lm :) `(,lm))))

実行例。

; 直角3角形を作る10以下の自然数の組とその面積
(l/c (list a b c area) : (a <- 1 .. 10) (b <- 1 .. a) (c <- 1 .. b) (= (* a a) (+ (* b b) (* c c))) (area := (/ (* b c) 2)))
=> ((5 4 3 6) (10 8 6 24))
; リストの生成
(l: 10) => (0 1 2 3 4 5 6 7 8 9)
(l: 1 .. 3) => (1 2 3)
(l: 2 4 .. 10) => (2 4 6 8 10)
; スライス
(l/s '(1 2 3 4 5) 1 : 3) => (2 3 4)
(l/s '(2 4 6 8 10) 2 :) => (6 8 10)

引数の評価については考えていないが、副作用を使わなければ大丈夫なはず。とりあえず動くので良しとしよう。こんな風に簡単に言語機能自体を拡張できるのを見るとやっぱりLispは楽しいと思う。

SchemeCommon Lisp

SchemeCommon Lispは現在でも広く利用されているLispの2大方言で、それぞれにたくさんの実装が存在する。もしかするとLispというとインタプリタのイメージが強いかもしれないが、コンパイラの実装もたくさんある。SBCLなどはコンパイルすればC並みの速度が出るのではないだろうか。Common Lispは使える機能は最初からとりあえず全て用意した大きな仕様で、Schemeは逆に(そこから全てを構成できる)より小さな機能を用意している気がする。また、Common Lispでは変数と関数の名前空間が分かれているのに対してSchemeではそれが同一なのも大きな違いだ。Schemeは変数と関数を同じように扱えるので関数の定義や高階関数も変数と同様に扱える。

(define *e* 2.718281828459045)
(define square (lambda (x) (* x x)))
(define (apply-f f x) (f x)) ; 関数定義用の構文も用意されているが、意味は無名関数の定義と束縛と同じ
(apply-f square *e*) => 7.3890560989306495

R7RSとSRFI

Schemeの最新の規格はR7RSで、R7RS-smallが2014年に策定され、R7RS-largeの策定作業が2017年現在も進められている。また、SchemeにはSRFIと呼ばれる標準ライブラリの規格があり、処理系が対応していれば利用できる。R7RSに対応した処理系は色々あるが、個人的におすすめなのは以下の3つ。

  • Gauche : 実用的な独自の拡張機能が充実している。
  • Sagittarius : SRFIへの対応度が高い気がする。
  • Chibi-Scheme : R7RSの策定グループの1人が作った参照実装なので、仕様を確認したい時に使える気がする。ただ深い再帰で止まってしまう。

vim-plugでSwiftとRustのプラグインを導入

vim

vimにSwiftとRustのシンタックスカラーリングが欲しかったのでvim-plugでプラグインをインストールしてみた。vimプラグインマネージャは色々つくられているけれど、vim-plugはファイル1つで導入でき、記法もシンプルで軽いようです。

GitHub - junegunn/vim-plug: Minimalist Vim Plugin Manager

vim-plug のインストール

GitHubの公式ページに書かれているコマンドをコピペして実行する。プラグインvimファイルをautoloadディレクトリにコピーするだけ。とても簡単です。

curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

プラグインのインストール

1. .vimrcファイルに以下のようなコマンドを追加する。

call plug#begin('~/.vim/plugged')

Plug 'rust-lang/rust.vim' " Rustのプラグインを追加
Plug 'keith/swift.vim' " Swiftのプラグインを追加

call plug#end()

2. vimを起動し、:PlugInstallを実行する。

Plugコマンドでは、GitのURLやレポジトリ名、ファイルへのパスなど色々な方法でプラグインの指定ができるようです。

遅延読み込み

プラグインをたくさんインストールすると、起動が重くなってしまうことがある。そこで、vim-plugでは、特定のファイルタイプが開かれた時や、特定のコマンドが実行された時など、必要に応じてプラグインを読み込むような設定にすることもできる。

call plug#begin('~/.vim/plugged')

Plug 'rust-lang/rust.vim', { 'for': 'rust' } 
Plug 'keith/swift.vim', { 'for': 'swift' }

call plug#end()

プラグインの状態は:PlugStatusで確認できる。

vimのコマンド

vim

vimの紹介

ブログに書けば忘れない気がする。vimUnix系のOSなら大抵どこでも最初から入っている軽量なエディタ。普通のエディタと同じように使える入力モードと、コマンドを打つことで操作するコマンドモードがあって、iとEscで切り替えられる。最初は奇妙に感じるかもしれないが、慣れるとコマンドモードでの編集がとても便利なことに気づくはず。

基本コマンド

vi [file] "fileを開く
view [file] "fileを読み込みモードで開く
i "入力モードに (insert)
Esc "コマンドモードに
:w "保存 (write)
:q "終了 (quit)
:e [file] "fileを開く (edit)
:e! "変更をすべて取り消す
:q! "変更をすべて取り消して終了
:wq! "読み込みモードで開いていても書き込んで終了

[]はオプションまたは変数とする。:wと:qで:wqのようにコマンドは大体組み合わせて使える。!を付けると、強制的に実行する意味合いが付加される。また、:付きのコマンドは最後にEnterをタイプする。

:r [file] "現在の位置にfileを読み込み
:![cmd] "シェルのcmdを実行

移動

vimの強みのひとつは、コードやテキストの構造をある程度理解してくれるので、構造単位で操作ができること。後で紹介する編集コマンドを移動コマンドと組み合わせることで、その範囲のテキストを一気に編集することができる。

←↓↑→ "それぞれ左下上右に移動
hjkl "それぞれ左下上右に移動
w "次の単語に
b "前の単語に
% "対応する括弧に [] () {} などの対の間で移動する
0 "行頭に
$ "行末に
^ "最初の空白でない文字に
{ "前の段落に
} "次の段落に
Ctrl + F "次の画面に
Ctrl + B "前の画面に
gg "1行目に
[num]G "num行目に
G "最終行に

また、コマンドは数字と組み合わせることで繰り返せる。

5j "5行下に移動
7w "7語右に移動

ここまでの機能でも、入力モードしか持たない普通のエディタよりも強力そうな気がしてくる。覚えることは多いが、一度覚えてしまえば操作はとても速くなるはず。

編集

実は入力モードへの切り替えが行われるのはiだけではない。

i "現在の位置から入力
I "行頭から入力
a "次の位置から入力
A "行末から入力
o "次の行から入力
O "前の行から入力
r "1文字を置換 (上書き)
R "置換 (上書き) モードに Escでコマンドモードに戻る
c[移動コマンド] "移動範囲を置換 その範囲の文字が消されて入力モードに
S "行を置換 現在の行が消えて入力モードに
J "次の行と現在の行を連結
~ "大文字と小文字を変換
x "1文字削除
d[移動コマンド] "移動範囲を削除
dd "現在の行を削除

どちらのモードにいるのかわからなくなったら、とりあえずEscを押せばいい。

u "undo 1つ前の変更を取り消す 繰り返せる
Ctrl + R "redo uで取り消した変更を取り消す 繰り返せる
. "直前のコマンドを繰り返す

ここまでくるともう後戻りはできないのではないだろうか。普通のエディタを使っている時にも、vimのコマンドが使いたくなってしまうかも。

ヤンク (コピー) とプット (ペースト)

コピーとペーストはヤンクとプットと呼ばれている。削除したテキストは自動的にヤンクされておりプットできる。

y[移動コマンド] "移動範囲をヤンク
yy "現在の行をヤンク
p "現在の位置にプット

vimにはaからzの名前付きのバッファがあり、名前を指定してヤンクやプットすることもできる。コードがvimモードなので"以降がすべてコメントになってしまった。

"[name]y[移動コマンド] "移動範囲をnameバッファにヤンク
"[name]p "nameバッファをプット

検索と置換

ここではregはすべて正規表現が使える。

/[reg] "正規表現regを検索
n "次の一致した検索結果に
N "前の一致した検索結果に
:/[reg] "regを含む行を検索
:g/[reg] "regを含むすべての行を検索
:g/[reg]/d "regを含むすべての行を削除
:s/[reg]/[to] "regをtoに置換
:s/[reg]/[to]/g "現在の行のすべてのregをtoに置換
:%s/[reg]/[to]/g "ファイル中のすべてのregをtoに置換
:noh "検索のハイライトを消す

:コマンドは行の位置や範囲の指定に使うことができる。編集コマンドと組み合わせることで、指定された行の範囲を編集できる。

:[start],[end][編集コマンド] "start行からend行までを編集
:7,$d "7行からファイル末尾までを削除
:0,.y "ファイル先頭から現在の行までをヤンク
:%y "ファイル全体をヤンク

使える正規表現は以下のようなもの。

[0-9] "数字
[a-z] "文字
\+ "1回以上の繰り返し
\{n} "n回の繰り返し
\{n,m} "n回以上m回以下の繰り返し
\s "空白文字
\t "タブ
\n "改行

画面分割とタブ

vimでも画面を分割したりタブを使ったりできる。

:sp "水平方向に分割
:vs "垂直方向に分割
Ctrl + h "左の画面に移動
Ctrl + j "下の画面に移動
Ctrl + k "上の画面に移動
Ctrl + l "右の画面に移動
:tabnew "新しいタブを開く
:gt "次のタブに
:gT "前のタブに

カーソルがいる時、それぞれの画面やタブは、1つのスクリーンの時と同じように操作できる。

.vimrcファイルの編集

ホームディレクトリに.vimrcファイルを作成することで、vimの起動時に様々な設定を行うことができる。vimのインストール先ディレクトリなどにvimrc_example.vimなどがあれば、まずはこれを~/.vimrcとしてコピーすると良い。.vimrcの設定内容についてはいずれ書き足したい。

vimとAtom

Editor vim

コードを書くのにずっとeclipsevimを使ってきたのだが、最近Swiftを書いてみたくなったのをきっかけにXcodeも使いはじめた。そうしたら新しいエディタも色々と試してみたくなった。

Sublime TextLight TableBracketsAtomなどなど、vimのかわりに使ってもいいかなと思えるくらい高機能で拡張性にも優れたエディタが最近では色々と開発されているらしい。ここではAtomを使ってみることにした。AtomGitHubがつくったエディタで基本的にC++CoffeeScriptで書かれているらしい。コミュニティによって開発された拡張パッケージを追加していくことで、IDEのような機能も追加していくことができる。

言語のシンタックスなどはlanguage-haskellなどの名前のパッケージでだいたい提供されているようだ。最初に入れてみた言語対応以外のパッケージは以下のようなもの。

少し重いのとアップデートのたびに色々な拡張パッケージが動かなくなったりするらしいのが難点だが、綺麗だし高機能だし直感的に使えるし、vim-mode-plusを入れれば操作性もvimとそんなに変わらないので気に入った。しばらく使ってみようと思う。