RustでクライアントサイドWebフレームワークを作っているよ
アメミヤです。
先日、会社でやっているポッドキャスト、ajitofmに出演しました。 (あんまり喋んなかったけど)
id: mizchiさんをゲストに招いての収録でした!収録中も、そのあとのajitingも色々な話が聞けて楽しかったです。
で、今回の話なのですが、収録中の Rustのような言語でUIを記述するのは難しい
というmizchiさんの発言に対し、そのタイミングでは首肯してしまったものの後になって、本当にそうか〜?という気持ちになったため、自分で作っています。
TodoMVC レベルの物が一応動くようになったのでブログを書きますよ。
アプリケーションコードの例
たとえば単純なカウンターはこんなんになる。
#![feature(proc_macro)] extern crate squark; extern crate squark_macros; extern crate squark_stdweb; extern crate stdweb; use stdweb::traits::*; use stdweb::web::document; use squark::{App, Runtime, View}; use squark_stdweb::StdwebRuntime; use squark_macros::view; #[derive(Clone, Debug, PartialEq)] struct State { count: isize, } impl State { pub fn new() -> State { State { count: 0 } } } #[derive(Clone, Debug)] enum Action { Increment, Decrement, } #[derive(Clone, Debug)] struct CounterApp; impl App for CounterApp { type State = State; type Action = Action; fn reducer(mut state: State, action: Action) -> State { match action { Action::Increment => { state.count += 1; } Action::Decrement => { state.count -= 1; } }; state } fn view(state: State) -> View<Action> { let count = state.count.clone(); view! { <div> { count.to_string() } <button onclick={ |_| Some(Action::Increment) }> increment </button> <button onclick={ |_| Some(Action::Decrement) }> decrement </button> </div> } } } fn main() { stdweb::initialize(); StdwebRuntime::<CounterApp>::new( document().query_selector("body").unwrap().unwrap(), State::new(), ).run(); stdweb::event_loop(); }
これがWebブラウザでWebAssemblyで動くの、割とワクワクしませんか。
もうちょっと複雑な、上に貼ったTodoMVCの例はここにあります。
作ったもの
ピュアRustなバーチャルDOM実装
バーチャルDOMの実装は実DOMの知識を持っていません。
2つのバーチャルDOMを比較し、どの要素や属性にどんな操作をするべきなのかを、Diffとして得るだけが仕事になっています。
上の例で impl
している、Action-Reducer-State-Viewを持つAppのtrait
Runtimeのtrait
Appのライフサイクルを管理しつつ、実際にUIを操作する層です。
仕事内容としては
Actionの発生検知 -> Reducerへの引き渡し -> Stateの更新 -> バーチャルDOMの更新 -> Diffのハンドリング
です。
ただし、このランタイムも同様に現実のDOMの知識は持っておらず、Webブラウザへのバインディングは impl Runtime
として別途行う設計になっています。
これは、RustのWebAssembly界隈の変化が速く、特定の環境に依存してしまうと陳腐化することを恐れての物です(今回で言えば後述するstdwebとcargo-web)
stdwebを使ったRuntimeの実装
stdwebは、Rustを使ってブラウザAPIを呼び出すためのバインディングライブラリです。
Diffに応じて、現実のDOMツリーへどのような操作を行うかを実装しています。
JSX風の構文のための、マクロ
proc_macroを使ったマクロの定義で、JSX風の構文をコンパイルタイム時にRustの構文へと変換することができます。
すごい!けれどコンパイル遅い!!
また、JSX風の構文パーサーはpest-parserによって、これまたダイナミックライブラリのコンパイル時に生成しています。 こんな感じのpegを書きました。
課題と今後
とここまで書いたけれど実はこれ、stableはおろか、最新のnightlyですら走らない状態です。
動く最新のnightlyは nightly-2017-12-01
。
というのも、 proc_macro
まわりにガンガン変更が入っていて、マクロが不正なコードを吐いてしまいます。
一番痛いのが↑で、健全マクロなので呼び出したスコープの何かにアクセスできないのは分かるんですが、マクロ中に含めた識別子にもアクセスできない状態。
また、現在のところ、全てのバーチャルDOMツリーに存在する全てのハンドラを設定しなおすDiffを吐いてしまい、パフォーマンスが良くありません。
これは、ハンドラに設定するクロージャがキャプチャしている値の変化を比較できれば、そのハンドラの同一性を確認できるはずで、方法を模索しています。
Appトレイトのインターフェースが妥当かどうかも謎なので、そこも方向を定めていきたい。 特に、reducerやviewは今のところAssociated functionsになってますが、もっと複雑な、APIリクエストをするようなアプリケーションだとメソッドにしたくなるのかなー?のようなことを思っています。 また、reducerがstateをどう受けてどう返すべきなのかもよく分かっていない(&state -> state なのか state -> state なのか &mut state なのか)
あとは、wasm-bindgenというjs-rust間のバインディングツールが良さげで、なんだかコミュニティ的にそっちが主流になりそうな雰囲気なので、WasmBindgenRuntimeを実装しようかとか、 actix-webを使ってサーバーサイドレンダリングを出来るようにしようかなー、とか考えています。
引続きやっていくぞい。
Linuxデスクトップ環境2017
流行っていたので便乗
ディストリビューション
Void Linux です。昨年の頭まではArch Linuxを使っていました。
Void LinuxはNetBSDの元メンテナーが作ったディストリビューションです。
独自のパッケージシステムxbpsや、systemdではなくrunitというdaemontoolsに近いサービス管理が特徴となっています。
BSD文化を感じることのできるLinuxとなっており、シンプルでエレガント、保守性が高いと言えると思います。
パッケージシステムは出来がよく、別のアーキテクチャ用のパッケージビルドがpacmanに比べて楽です。
まぁ、とはいえ最新への追随が早かったりと、Arch Linuxとは似ています。是非触ってみてください。
デスクトップ環境
GNOME3です。これは3.0のリリース直後、ここ5年ぐらいずっと使っています。
実は、GNOME3はjournald, logindというsystemdのコンポーネントに依存しています。
そのため、Void Linuxではこれらをスタンドアロンな代替を使った上でxbpsに収録しています。
これが災いし、GNOMEのバージョン追随は遅れてしまっています。
もはやGNOME3も相当にstableなのであんあり気にはなりませんが。
日本語入力
ibus-skkを使っています。
一時期の状況に比べると、相当改善されましたね。
私はqwertyではなくdvorak配列を常用していますが、現在のGNOME3では最後に選択したxkb配列がibusの配列になるという仕様(?)で、これが便利です。
ターミナルエミュレータ
X Window Systemのための、最少主義なターミナルです。
rxvtのように、もはや使われない端末のエミュレートを省き、コードベースが5000行弱の1ファイルになっています。
コンパイル時に設定をヘッダファイルで書くことで、コンパイル後は小さな可搬性の高いバイナリができあがります。かっこいい。
ただ、最近Rustで書かれたAlacrittyがGPU支援による描画を全面に押し出していて、waylandとの相性に優れていそうなので、移住を考えています。
カラースキーム
paletteというスキームを自作しました。
16色をyamlで定義し、https://github.com/chriskempson/base16というスタックを使うことで、各種アプリケーション向けの設定ファイルを書きだすことができます。
(少し前にビルドシステムに手が入ったため、さっきリポジトリを作った)
エディタ
neovim + vim-plug を、ターミナルエミュレータ機能、タブ機能、分割機能を重用しつつ使っています。
もともとはbyobuを使っていたんですが、neovimで不要になりました。すごいですね。
ウェブブラウザ
vimFxはvimperatorライクな拡張機能です。
vimperatorに比べると細かに触ることなく、ある程度使いやすいデフォルト設定を持っていると思います。
フォント
システムワイドでM+ 1c、ターミナルではM+ 1mnをfontconfigで設定しています。
それでは皆様。2017年も良いLinuxデスクトップライフを。
Rustのコンパイラプラグインで実装を型定義から自動生成しよう
しよう(提案)
この記事はRust Advent Calendar 2016 - Qiitaの9日目の記事となります。
コンパイラプラグイン
Rustはコンパイラであるrustcに対して、ユーザーがプラグインとして機能を追加できる仕組みがあります。 コンパイル時に処理を定義できる仕組みというと、マクロが思い浮かびますが、
macro_rules! foo { (x => $e:expr) => (println!("mode X: {}", $e)); (y => $e:expr) => (println!("mode Y: {}", $e)); } fn main() { foo!(y => 3); }
実行結果
=> mode Y: 3
と上記リンクのドキュメントにあるように、マクロが限られた範囲のASTをパターンマッチして変更するのに対し、コンパイラプラグインでは、こんな感じで、Rustそのものを使ってソースコードのASTを処理し、新たなASTを構築できます。
例えば、Rustでよく使われるシリアライズライブラリであるserdeがありますが、serdeを構成するcrateのうちの1つ、serde_deriveは
#[derive(Serialize, Deserialize)] struct Point { x: f64, y: f64, }
のようにSerialize, Deserializeという属性を指定することで、それぞれser::Serialize, de:Deserializeトレイトに必要なメソッドのASTを、構造体の名前と型から生成する機能を持っています。
今回は、このアトリビュートを独自に定義し、コンパイラプラグインとして使うことのできるcrateを作成しました。
題材
mysqlライブラリである
https://github.com/blackbeam/rust-mysql-simple
のトレイト、FromRowを、構造体に対して実装しようと思います。
#[derive(FromMysqlRow)] struct User { name: String, age: u64 }
のようにすると、SELECT文を発行した結果が格納されているmysql::Rowから、このUser構造体への変換メソッドが自動的に生成されます。
実装
実装は https://github.com/rail44/rust-mysql-derive/blob/master/src/lib.rs だけです。
この題材で実装を始めてから気付いたのですが、現在のrust-nightlyではRFC#1681により、上の方でリンクを貼ったコンパイラプラグインのドキュメントとはプラグイン登録のためのAPIが変わってしまっていました。 そのため、実装の上では既にnightly対応がなされているserdeの
https://github.com/serde-rs/serde/tree/master/serde_derive
https://github.com/serde-rs/serde/tree/master/serde_codegen
https://github.com/serde-rs/serde/tree/master/serde_codegen_internals
3つのcrateを参考にしました。
新しいマクロシステム(macros 1.1と呼ぶらしい)では、lib.rsで
#![feature(proc_macro, proc_macro_lib)] extern crate proc_macro;
と宣言をした上で
https://github.com/rail44/rust-mysql-derive/blob/master/src/lib.rs#L12
#[proc_macro_derive(FromMysqlRow)] pub fn derive_from_mysql_row(input: TokenStream) -> TokenStream { let source = input.to_string(); let ast = syn::parse_macro_input(&source).unwrap(); let expanded = expand_from_mysql_row(&ast); expanded.parse().unwrap() }
のように #[proc_macro_derive(...)]
とアトリビュートを付けた関数を定義することで、独自のアトリビュートを作成し、外部からも使うことができるようになります。
serdeではTokenStreamをパースした上でASTの追加を行っています。 そのパースのためのcrateが
として公開されていたため、ありがたく使わせて頂きました。 expand_from_mysql_row関数がASTを生成する本体の実装になっていますが、大体syn crateのドキュメント通りです。ありがたや。
結果
テストコードでは
https://github.com/rail44/rust-mysql-derive/blob/master/tests/test.rs
#[derive(FromMysqlRow, Debug, PartialEq, Eq)] struct User { name: String, age: u64 }
と書くだけで、
let users: Vec<User> = pool .prep_exec("SELECT * FROM rust_mysql_derive.users", ()) .unwrap() .map(|opt_row| mysql::from_row(opt_row.unwrap())) .collect();
のように、クエリの結果を構造体に変換できるようになっています。
mysql crateのドキュメントにあるような方法 mysql::Row - Rust
pool.prep_exec("SELECT * FROM tmp.Users", ()).map(|mut result| { let mut row = result.next().unwrap().unwrap(); let id: u32 = row.take("id").unwrap(); let name: String = row.take("name").unwrap(); let age: u32 = row.take("age").unwrap(); let email: String = row.take("email").unwrap(); assert_eq!(1, id); assert_eq!("John", name); assert_eq!(17, age); assert_eq!("foo@bar.baz", email); });
に比べて簡単に見えます。見えますよね?たぶん見える。
$ docker run -ti --rm -p 3306:3306 -e MYSQL_ROOT_PASSWORD=my-secret-pw mariadb
などとすれば cargo test
でテストが走ります。
この程度の実装なら型毎に手で書けばよくない?
僕もそう思います
Rustでインタープリターを書いた話
嘘です。
Rustでインタープリターを書いたている話
こんにちは。VOYAGE GROUPの子会社、fluctで学生アルバイトをしているアメミヤです。
日程調整のスプレッドシートにしれっと記入したら、何も怒られなかったので、本記事はVOYAGE GROUP Advent Calendar 2015の11日目となります。
RustはMozillaが開発している、マルチパラダイムの静的型付け言語で、パターンマッチや強力な型推論、ジェネリクスやそれを柔軟にする型クラスに近いトレイトを持っています。
中でも特徴的なのは、変数の所有権やポインターの貸し出しといった概念で、これによりGCを用いずに静的にメモリ領域の寿命を計算することができます。
詳しくは公式サイトや今年のRust Adevent Calenderへ。
最近は落ち着いてきたものの、依然として変更の激しい言語なので、参考にする時はなるべく新しい文書の方がベターです。
今回はLLVM IRへのコンパイラを書き始めたのですが、思っていた以上にLLVM分からないマンだったのでEvaluaterの方を実装してインタープリタになってしまいました。
ビルド・実行
cargo run -- [file]
Cargoについて詳しくは今年のRust Ad以下略
後述しますが、rustcコンパイラへのプラグインを使っているため、この機能の存在するRust Nightlyチャンネルでの実装になっています。
Stableチャンネルではビルドが失敗します。
文法
オレオレ。こんなのを予定(妄想)しています。
fizzbuzz: { is_fizz: i % 3 == 0 is_buzz: i % 5 == 0 fizz: if is_fizz "fizz" "" buzz: if is_buzz "buzz" "" fizz.concat{buzz} } range{min: 1 max: 100}.map{i: fizzbuzz}
いまはこんな程度しか動きません/(^o^)\
i: j / 2 j: 5 k: k + 1 i * (j + 3) - (j / i)
遅延評価戦略(言いたいだけ)を実装しており、後に出てくるjをiの定義で使うことができます。
また、無限に再帰してしまうkは必要がないため評価されません。
なぜ作ったているのか?
現在、社内では読書会/勉強会という形でSICPやTAPL、アンダースタンディングコンピューテーションといった書籍を読み進めているのですが、そのどれもに、言語処理系を実装する章が存在しています。
そこで、実際に手を付ける前に自分で試行錯誤してみたくなってみた。というのが今回の動機になっています。
言語作ること自体初めてですし、お勉強もそっち方面ではないです。なので、コードは地獄です。破茶滅茶です。ゆるして。
使ったCrates
crateはRubyでいうgem、Pythonでいうeggです。
peg
PEG(Parsing Expression Grammar)のRust実装。
PEGって知らなかったんですが、RubyのTreetopもそうだったみたいです。
正規表現に感覚は近いですが、再帰的なマッチャの定義ができるのがミソ。
Rustを拡張したDSLをこんな感じで書くことでパーサーとグラマーが同時に実装できます。便利。
代数的データ型であるEnumとの相性がよく、リテラルに対してのマッチを除いた殆んどがASTの定義に即した記述になりました。
ここら辺、型定義からパーサを生成する何か?を作れそうな夢が広がります。
Rustを拡張したDSL
をコンパイル時に評価するために、pegはコンパイラプラグインを提供しています。ここがNightlyでないと動かない理由。
docopt
docoptはPythonやGoでよく使われるコマンドラインツールを作るためのパッケージで、ドキュメントからフラグやサブコマンドのパーサを生成してくれるナイスなやつです。
こちらもドキュメントからパーサを定義する処理をコンパイル時に走らせるため、コンパイラプラグインとして提供されています。
やったこと
四則演算子、変数、多項式、変数定義のための環境、変数に結びつく値の遅延評価
やってないこと
ブロック、比較、条件分岐、マサカリを防ぐためExecuterという名前をEvaluaterに変える(フセゲナカッタ
やりたいこと
型の静的検査
いまのところ苦労した点
多項式のパース
ここからが多項式のパーサ定義なのですが、苦労ポイントでした。
単に数字リテラルのみの多項式ならさくっと書けたものの、項にもまた変数やブロックの評価といった式を書けるようにするのにハマった時間が長かったです。
結局、式の優先順位を正しく整理できていなかったのが原因で、単に
3
といったコードも Add(Mul(Pri(Atom(Ltr(Num(3))))))
のように構文木を作らなければならないことに気付きました。
Rustの変数のとりあつかい
Rustでなにか書く時に毎回ハマってる気がする。きのせい?
環境、Envを各executeメソッドで持ち回す方法でもハマりました。
Envは変数の名前とそれに紐付く式のハッシュマップを持っていて、名前が見つかれば式を評価した値で置き換えるために持ち回します。
spectr/executer.rs at 9dcc2061ef23cf653cc8c40fac25abb9f02ac76c · rail44/spectr · GitHub
枝を複数持っている構文木のノードでは、それぞれに対してexecuteを呼びだす必要があるため、当初は fn execute(self, &Env)
といったメソッドの定義でした。
この&Env
はBorrowed Pointerと呼ばれるもので、変数への参照を"貸しだし"、それを渡した関数やメソッドが終了次第開放されるというものです。(Lifetimeに関する説明は省きます)
ただ、今回作ろうとしている言語は、式は必要になったタイミングで遅延評価され、同じ環境で同じ式は1回しか評価されないという戦略を取ろうとしていたのがアダになりました。
遅延評価をしたタイミングでEnvの持つ別のハッシュマップへとその値を格納する必要が出てくるので、Envへの変更権限を持たない&Envの持ち回しでは実現できなかったのです。
そこで、execute関数へとEnvを所有権ごと値渡ししてしまうことにしました。executeはEnvを返すことで所有権を返還する、といった方法です。
let (hoge_value, env) = try!(hoge.execute(env));
fuga.execute(env).map(|fuga_value| (hoge_value, fuga_value))
ちょっと不恰好に見えますが、&mutを使うよりも見通しが効きますし、ここら辺のオーバーヘッドを気にするのはrustcの仕事で、僕の懸案ではないと割り切りました。
まとめ
Rustむずい(小学生並みの感想) 言語開発たのしい(小学生並みの感想)
明日、12日目は@takeru911の担当です。お楽しみに!
ISUCON2015で仕込んでいたネタ(h2o + mruby)の供養をする記事
ISUCON2015予選お疲れさまでした。
予選関連エントリとしてはかなり出遅れてしまいましたが、自分がISUCONに向けて仕込んでいたものの、日の目を見なかったネタの供養をしたいと思います。
燃えろよ燃えろ。
(自宅でBeagleBoneBlackつかって動かしてたブログが消滅してたのでこっちで書くジャバよ。)
参加チーム
アルパカ三姉妹
です。パカー。
ISUCONへの参加が初めてなのは自分だけだったので、足を引っぱるまいと気負いが凄かったと反省。
仕込んでいたもの
以前からmatsumoto-rさんのブログエントリ(その1, その2)によって、試したい気分が高まっていたh2oのmruby拡張をネタに一発逆転を目論み、sinatraアプリの一部ルーティングをmrubyで書けるような何かを作ろうとしました。
結果、
の2つのmrbgemsと、これら+sinatraアプリを移植するために必要そうなmrbgemsを組みこんだh2oのフォーク
を作りました。
mruby-r3
Cで書かれた高速なルーティングライブラリR3のバインディングです。
今回に必要そうなものしか書いていません。
mruby-rack-r3
最近、h2oのmruby拡張のインターフェースがrack準拠になっていたため、includeすることでsinatraライクなDSLを使えるようにするモジュールです。
mrubyオンリーのmrbgemsです。
それぞれREADMEをご覧ください。
なぜ使わなかったか
アプリケーションコードの改善で沼に嵌ってしまい、余裕がなかった。
(それでも使ってみればよかったのでは???????)
ベンチマーク
これだとコード達が浮かばれないのでベンチマークを取って供養します。
ベンチマークに使った環境はGCEのn1-highcpu-16、ubutu-15.04です。
https://github.com/rail44/h2o/tree/mrub-rack-r3github.com
↑のブランチ上で
./install_deps_ubuntu.sh cmake -DWITH_MRUBY=ON . make h2o
とすることで、mruby-rack-r3とおまけにmruby-redisが同梱されたh2oバイナリが作成できます。
./h2o -c examples/h2o-mruby/h2o.config
とすることでベンチマーク用のサンプルアプリケーションが立ち上がります。 アプリケーションコードはこんな感じ。
class App include Rack::R3 def redis @redis ||= Redis.new('127.0.0.1', 6379) end get '/hello/{first_name}/{last_name}' do |f, l| [200, {'content-type' => 'text/plain; charset=utf-8'}, ["Hello #{f} #{l}!\n"] ] end get '/incr' do [200, {'content-type' => 'text/plain; charset=utf-8'}, ["#{redis.incr('hoge')}\n"] ] end end App.new
また、これと同等なsinatraアプリケーションを rubyapp_for_bench
に書きました。
h2oの並列ワーカー数4に対し、pumaの並列ワーカー数16、最大スレッド16と大きく取ることで、pumaのデーモンが足らなくなることを防いだ上で性能を比較してみます。
パラメータとして受けとった文字列を結合するエンドポイント
satoshi@train-isucon5-rail44 ~/h2o> curl -D - http://localhost:8000/hello/Satoshi/Amemiya HTTP/1.1 200 OK Date: Thu, 01 Oct 2015 08:19:57 GMT Server: h2o/1.5.0 Connection: keep-alive Content-Length: 23 content-type: text/html;charset=utf-8 x-xss-protection: 1; mode=block x-content-type-options: nosniff x-frame-options: SAMEORIGIN Hello Satoshi Amemiya!
sinatra版
Running 30s test @ http://localhost:8000/hello/Satoshi/Amemiya 4 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 4.79ms 3.36ms 58.19ms 75.05% Req/Sec 5.54k 397.66 8.34k 83.42% 662753 requests in 30.09s, 174.45MB read Requests/sec: 22027.56 Transfer/sec: 5.80MB
mruby-rack-r3版
Running 30s test @ http://localhost:8000/hello/Satoshi/Amemiya 4 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 1.23ms 5.15ms 202.99ms 99.81% Req/Sec 24.25k 1.89k 38.18k 64.78% 2905104 requests in 30.10s, 559.65MB read Requests/sec: 96514.58 Transfer/sec: 18.59MB
はやいo(^^)o
Redisへのアクセスが発生するエンドポイント
satoshi@train-isucon5-rail44 ~/h2o> curl -D - http://localhost:8000/incr HTTP/1.1 200 OK Date: Thu, 01 Oct 2015 08:23:21 GMT Server: h2o/1.5.0 Connection: keep-alive Content-Length: 1 content-type: text/html;charset=utf-8 x-xss-protection: 1; mode=block x-content-type-options: nosniff x-frame-options: SAMEORIGIN 1⏎ satoshi@train-isucon5-rail44 ~/h2o> curl -D - http://localhost:8000/incr HTTP/1.1 200 OK Date: Thu, 01 Oct 2015 08:23:23 GMT Server: h2o/1.5.0 Connection: keep-alive Content-Length: 1 content-type: text/html;charset=utf-8 x-xss-protection: 1; mode=block x-content-type-options: nosniff x-frame-options: SAMEORIGIN 2⏎
sinatra版
Running 30s test @ http://localhost:8000/incr 4 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 5.12ms 3.56ms 69.91ms 74.28% Req/Sec 5.20k 425.08 7.68k 75.83% 621764 requests in 30.06s, 152.88MB read Requests/sec: 20684.90 Transfer/sec: 5.09MB
mruby-rack-r3版
Running 30s test @ http://localhost:8000/incr 4 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 2.32ms 2.68ms 199.00ms 99.95% Req/Sec 11.06k 1.39k 14.51k 58.72% 1325187 requests in 30.10s, 234.01MB read Requests/sec: 44026.30 Transfer/sec: 7.77MB
早いものの、先ほどのエンドポイントに比べ、速度の向上具合が緩やかになっています。
ここからは、今後時間ができたときにじっくり考えたい内容ですが、mrubyのコード中でredisへのアクセスが発生した際にh2oがそのスレッドを開放できず、イベントループの恩恵が得られてないのではないかと仮説を立てています。
(どうやって確かめればいいのだろう)
まとめ
ISUCONく"や"し"い"
日本語入力環境SKKを使う
今まで、日本語入力にはibus-mozcを使っていたのですが、ふとSKKを使ってみたくなり早速チャレンジしてみました。
理に適っているのは分かるんですが、今までと全く違う入力方法に戸惑っています。なので、入力の練習も兼ねて環境の作成手順をブログに書くことにしました。
linuxの場合はibus用のモジュールがあるのでインストールは簡単。ibus-skkとかそういう名前のパッケージを入れれば大丈夫でした。
他のOSでも使う方法があるみたいなので……ググってください。
で、使ってみたんですが、!マークがひらがなモードの時も半角になってしまいます。
調べてみると、~/.config/ibus-skk.jsonを編集すれば定義を変更できるという文献がいくつか見つかったんですが、ibus-skkの仕様変更で変換プログラムはlibskkとして分離された様子。
ではどうするかと言うと、~/.config/libskk/rules/以下に新しくプロファイルを作ればいいみたい。
参考
mkdir -p ~/.config/libskk/rules
cd ~/.config/libskk/rules
cp -r /usr/share/libskk/rules/default ./user
デフォルトのプロファイルをそのままコピーして編集することにします。
コピーしたディレクトリの中には2つのディレクトリと1つのJSONファイルがあり、それぞれ
です。 まず、metadata.jsonを開くと、
{
"name": "Default",
"description": "Default typing rule"
}
こんな感じになってるので、nameを"User"、descriptionを"My typing rule"とかに書き換えます。
後はrom-kana/default.jsonを色々弄って、ibus-skkの設定画面からUserプロファイルを指定してやれば設定が反映されます。
(ibusの再起動を忘れずに!)
僕は、keymap/hiragana.jsonを編集して、ひらがなモードの時の他のモードへの切り替えを無効化したりしました。
これを書いてる間に大分SKKに慣れて来た感じがあります。手書きで文章を書くような変換が持ち味で、タイピングが非常に心地いいです。是非試してみてください!