Scala日記

Scalaの備忘録。ときどき研究の話。

キーボードについて考えてみました (イントロダクション)

きっかけは、去年の半ばに研究室OBの人が「左右分割型の自作キーボードを使っている」と話してくれたことでした。もともとErgoDox などの存在は知っていたし、もう何年も前から Pontus が Ortholinear なキーボードを使っていたのを知っていたので、なるほどいろんなものが世の中にはあるなあ、くらいには思っていたところでしたが、これをきっかけに少し真面目にキーボードというものを考えるようになりました。

f:id:yuimat:20200809084229p:plain
ErgoDox

f:id:yuimat:20200809084645p:plain
Ortholinear Keyboards

で、考えていくと、ふと、興味深いことに気づきました。自分は今まで、自分が日常的に使う道具については一つ一つ時間をかけて、こだわりを持って選ぶという方針でやってきたつもりでした。それは例えばペンやペン立て、書類ケースなどの文具だったり、スピーカー、イヤホン等の音響機器、ひいてはパソコンの画面をきれいにするクリーナーなんかもこだわりのものを持っていたり、様々です。

f:id:yuimat:20200809085636p:plain
Tent Display Cleaner ブナ

displaycleaner

それなのに、なぜキーボードについては今までそのような考えに至らなかったのでしょう。これまで何年も情報系の仕事をしてきて、毎日最も使っている道具は間違いなくキーボードとマウスであるにも関わらず、これらの道具を非常に限られた選択肢の中から盲目的に選んできたのです。

とはいえ、それまで使っていたものは HHKB Professional 2 といういわゆる既製品では最高級クラスのものだったので、特段の不便さはなく、むしろその「限られた選択肢」のなかでは最高の体験であったことは間違いありません。

f:id:yuimat:20200809100532p:plain
HHKB Professional 2
Happy Hacking Keyboard | HHKB Professional2 | PFU

ただ一方で、この時に同時に思い至ったのは、最近のパソコンのキーボードというものは、もはや現在の形式にとどまる必然性が存在していないだろうということです。これだけOSもハードウェアもソフトウェアも進化しているなかで、それぞれのキーに最初から固定の文字配列が印字されているキーボードを使う理由は特にありません。例えば、スマホは全画面タッチパネル化して以来、それまでのハードウェアによる制約から開放され、その入力方式はかなり自由になりました。ノートPCやデスクトップPCも別にそうであってもよいのです。キーボードが現在の形に定着して以来おおむね20〜30年ですが、例えばピアノの鍵盤なども長い音楽の歴史の中で色々揉まれているわけで(浜松の楽器博物館では、その系譜がよく分かります。)、高々数十年の歴史で定着しているものなど、ただの通過点にしか過ぎないはずです。この気付きは、非常に大きな発想の転換を僕にもたらしました。

そうして、PCのキーボードは、その必然性がなくなってから何年もの間イノベーションの余地があるにも関わらずイノベーションが止まっている、いわゆる一時期前の電話みたいなものだな、と思うようになりました。そして、このように考えていった結果、冒頭の彼が紹介してくれた「自作キーボード」というジャンルは、まさにそこにユーザーが自らの手でイノベーションを持ち込もうとする、非常に画期的な試みだと見ることができたのです。

f:id:yuimat:20200809092021p:plain
「自作キーボード」の検索結果

調べてみると、自作キーボードはキースイッチの配置も普通とは違うし、キー配列も自由に書き換えることができ、またShiftのように違う文字を打てる「レイヤー」という機能で同じキースイッチに4つほど違う文字を割り当てることができたりと、かなりカスタマイズのやりがいがあり、夢が膨らんできます。何はともあれ、これはとりあえずやってみるしかない、となりました。

こうして、僕の自作キーボードの旅が始まりました。旅は2019年の後半に始まり、今年の春にひとまず一段落したので、この辺りでまとめて記事にしてみようと思います。旅に出てみての感想は、単に「自分にピッタリのものができた、やったー!」というだけには留まらず、実にいろんな気付きがありました。例えば、情報発信のマイクロ化、出版のマイクロ化、製品開発のマイクロ化。これは本当にすごいことだと思いました。これも詳しくは後ほど書きます。

少し話が長くなるので、シリーズ化して分けて書こうと思います。

キーボードについて考えてみました シリーズ

  • その1:自作キーボードキットをベースに、オリジナルのキーボードを試作してみる
  • その2:キー配列再考 〜オリジナルキー配列 Clandor の開発〜
  • その3:キーボードにロータリーエンコーダーを付けてみる
  • その4:キーボードやキーキャップ、手首の高さについて考えてみる

最終的には、 こういうものができました。少しずつ書いていきたいと思いますのでお楽しみに。

f:id:yuimat:20200809094930p:plain

f:id:yuimat:20200809095123p:plain

Matplotlib (with seaborn) で出力するPDFの下のほうが切れてしまうときは bbox_inches='tight'

Scalaとは関係ないですが…。探すのに苦労したので。 結局は学生さんに教えてもらいました。

import pandas as pd
import seaborn as sns
from matplotlib import pyplot as plt

data = ['some data points']
df = pd.DataFrame(data=data, columns=['model', 'score'])

sns.set_context("talk",1, {"lines.linewidth": 1})
plt.xticks(rotation=60)
plot = sns.boxplot(x="model", y="score", data=df)
plt.xlabel(' ')
plt.figure(figsize=(6, 5), dpi=300)
plot.figure.savefig('sample.pdf', bbox_inches='tight')

Argmax: Seqが関数であることを利用してmax値のインデックスを簡潔に取る

以前、ScalaSeq が関数であることを書きましたが、

ym.hatenadiary.jp

これを利用して、最大値のインデックスを取得する関数を簡潔に実装することが出来ます。

val seq = scala.util.Random.shuffle(1 to 10)
seq.indices.maxBy(seq)

美しいですね。

maxBy高階関数で、数式記号で言うところの argmax に相当します。ここでは、seq のインデックスを seq(という関数)で seq の中の実際の値に変換して、その値が最大になるようなインデックスを返しています。

Seq の拡張メソッドとして実装したい場合は以下のようにすればOK。

argmax in Scala

None を含むオブジェクトを Java の deep clone ライブラリで clone すると複数の None オブジェクトができる。この None は case 文で match しない。

完全にハマったので覚書。 ディープクローンを手軽にやりたくて、このライブラリを使ったはいいのだけど、

github.com

突然 case 文で Nonecase None => にマッチしなくなった。 そんなことがありうるのかと思って調べてみたら、このあたりで議論されていた。おおよそ同じ現象と思われる。

「Two scala.None$ references do not match」 Google グループ

手軽にやりたいなら、多少遅いが、シリアライズ → デシリアライズが無難。

基本コンストラクタの引数は他のメソッド内で使わない場合 private field として保持されない

入門書で明示的に教えてくれないシリーズその2。

class A(s:String){
    def i = s.toInt
}

class B(s:String)

class C(s:String){
    val i = s.toInt
}

上のようなコードにおいて、Asが private field として保持されるのはみんな知っているが、BCsが private field として保持されるのかどうかを明示的に記述している文献はなかなか見当たらない。 コップ本の基本コンストラクタの項目には、基本コンストラクタの引数は「必要なら、渡された値でフィールドを初期化し」や「クラス本体で使われないフィールドは保持『しなくてよい』」などと書かれているが、「しなくてよい」という書き方は定義としてはなんとも曖昧で実際の動作が気になる。

本当のところどうなっているのかいろいろ調べたが、文献からは確信が持てずじまいだったので、以前書いた

ym.hatenadiary.jp

で自分で調べてみた。やり方は

scala -Xprint:constructors

として、上のコードを張り付け、コンパイル結果を見る。すると、表題の結論であることが分かる。 ちなみに、同じ研究室の方に調べてもらったところによると、Martin Odasky 曰く、

"(メソッドで使われている場合は private field になり、使われていない場合は基本コンストラクタの処理が終わった時点で捨てられるという推測は) That's all true, but it should be treated as an implementation technique. That's why the spec is silent about it. "

とのこと。つまり、この仕組みは特にScalaの仕様に含まれているわけではないということ。 どのようにプログラミングを行うかという作法に関わる結構重要な決め事のようにも思えるのだが、何を仕様に含めるべきかという理念の違いなのだろうか。

参考:

  1. Scala Reference and Primary Constructor Parameters | The Scala Programming Language

Option型のコレクションから Some(x) の値 x だけを取り出すには flatten

val opts: Seq[Option[Int]] = Seq(Some(2), None, Some(3))

のようなものから値のあるものだけを取り出して、

List(2, 3)

を作りたいときに、Scalaを覚えたての頃はやり方がよく分からなくて、最初にやっていたのが

opts.filter(_ != None).map(_.get)

だった。その後、Scalaがだいぶ書けるようになった頃に

for(Some(x) <- opts) yield x

やら、

opts.collect{ case Some(x) => x }

となり、最終的にたどり着いたのが

opts.flatten

である。標準ライブラリを理解してくると自然に行き着くことではあるのだが、 それにしても、このようなことは何故か誰も教えてくれない。初学者の頃にこれを書こうとする度に苦労したので、これからScalaを学ぶ人向けに。