プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

Javaプログラマが今日から始めるC#ぼっち勉強会 その1

 話は聞いたぞ、人類 Javaは滅亡する!

 な、なんだってーーーー


 というわけで、Javaヤバイ ボラクル マジオニ と言いつつ既に数ヶ月。いい加減デブい重い腰を上げ、Javaから別言語への移行を本気で検討する必要があるでしょう。

 私の場合、殆どデスクトップアプリケーションにJavaを利用しています。今回の無償サポートを打ち切るというOracleの姿勢は、Web屋は急いでJavaから切り替える必要もないでしょうが、私にとっては割と直撃ダイレクトアタック攻撃なのです。


 

C#、キミに決めた!

 Javaから他のどの言語に移行するのかですが、無論JVM言語であるScala、Kottlin、Groovyは除外です。
 となると、基本的にはC++、C#、Rust、Go、JavaScript、Python辺りになるでしょう。

 Pythonは有力候補ですが、前にも言った様に型を明示しない言語は好きじゃない。(最低限関数の型ぐらいは書きたい) JavaScriptも同様。

 C++は普段から使っていますが、メインにするかというと、Javaというファイルや文字列の扱いやすいぬるま湯に浸かってきた私としては、メインにはしたくない。一度ファイルや文字列からC++のオブジェクト世界に入ってしまえば、そんなに苦は無いんですが、やっぱりそこが苦しい。UTF-8以外の文字コードがすべて滅べば良いのに。あと、filesystemはやく実装してくれぇ。Boostを使うとビルドに何をリンクすれば良いのかわかりにくいんだよなぁ。Mavenみたいに自動ビルドシステムがC++にも欲しい。そういやCONANはまだ使ったことない。

 Rustは今アツい言語ですが、ライブラリの数の不安や、学習コストの高さから、ひとまず移行する先としては除外しました。

 残るはGoとC#ですが、決め手はClojureでした。
 なんと、JVM言語のClojureってClojure CLRという.NET上でも動く実装があるらしい。Clojure Scriptは知ってたけど、Clojure CLRは知らなんだ。

 というわけで、どこまでClojure CLRと連携出来るか分かりませんが、.NET言語であるC#を学ぶことにしました。

開発環境

 普通に考えればVisual Studioなのでしょうが、Emacsキーバインドにするのが面倒くさい。
 ていうか、会社とかで使おうと思ったらVisual Studio Expressになってしまうので、何とか避けたい。最終手段にしたい。

 Emacs、ないしVisual Studio Codeで良い感じの開発環境を整えられないのか、ひとまず足掻いてみましょう。
 駄目だったらVisual Studioのキーバインドを泣きながらEmacs化する作業が待っています。(昔Emacsキーバインド化の拡張入れたらやたら不安定になって、削除した)

.NET Coreのインストール

Microsoft .NET

 .NET Coreと.NET Frameworkがあって、FrameworkはWindows専用。(GUIとかも作れる模様。)
 Linuxでも利用したいので一先ずは.NET Coreを選択してインストール。バージョンは2018年現在で最新版の.NET Core 2.1です。
 チュートリアルに従ってダウンロード&試しに動かしてみる。


f:id:nodamushi:20180721210435p:plain:w320
f:id:nodamushi:20180721210456p:plain:w320
f:id:nodamushi:20180721210556p:plain:w320

f:id:nodamushi:20180721210731p:plain

「dotnet run」って実行すると、Helo Worldが表示されました。無事動かせたようですね。

Javaとプロジェクト構成の違い:サンプルプロジェクトの作成

 MavenとかGradleなどでプロジェクトを作成するとsrc/mainとsrc/testが作成されて、一つのプロジェクトでメインコードもテストコードも両方管理しますね。

 しかし、.NETではそれぞれを別のプロジェクトとして作成し、それらのプロジェクトをソリューションという単位で管理するようです。
 EclipseのWorkspaceなどがソリューションに近いでしょうか。

 

 というわけで、最初のサンプルプロジェクトを作ってみました。

mkdir tmp
cd tmp
dotnet new sln
dotnet new console -o sample
dotnet new xunit   -o sampleTest
dotnet sln add sample sampleTest
cd sampleTest
dotnet add reference ../sample

 上記のコマンドは以下の様な意味になります。

  1. tmpディレクトリをソリューションとする。(dotnet new sln)
  2. コンソールアプリケーションプロジェクト sampleをtmpの下に作成する。(dotnet new console -o sample)
  3. xUnitテストプロジェクト sampleTestをtmpの下に作成する。(dotnet new xunit -o sampleTest)
  4. ソリューションに作成したプロジェクトを追加する。(dotnet sln add sample sampleTest)
  5. テストプロジェクトからメインのプロジェクトが見える様に参照を追加。(cd sampleTest→dotnet add reference ../sample)

f:id:nodamushi:20180722015109p:plain:w320

Emacsの開発環境: omnisharp-emacs

 例え不毛だったとしても、一先ずはEmacsでの環境を構築してみます。
 Emacs………というか、VimやVisual Studio Codeでもそうみたいですが、OmniSharpというスゴイナニカを介して補完候補表示や、リファクタリング等の機能を実現するようです。この辺はC++のirony-serverと同じ機構なのでしょう。

インストール手順

  1. package-install→omnisharpをインストール
  2. omnisharp-install-serverコマンドを実行してOmniSharpをインストール

 omnisharpの細かい設定は後々オレノサイキョー化するとして、一先ずはこの程度の設定で十分でしょう。

(use-package omnisharp
  :init
  (add-to-list 'company-backends  'company-omnisharp)
  (add-hook 'csharp-mode-hook
            (lambda ()
              (omnisharp-mode)
              (flycheck-mode))))

emacs-omnishaprを使ってみる

 で、先ほど作成したsample/sampleTestにあるC#ファイルを開きます。開いたらomnisharp-start-omnisharp-serverでOmniSharpを起動します。ソリューションファイルはtmpの下のtmp.slnを選択します。

 適当にプログラムを書いてみると、以下の図の様に、補完を表示したり、構文エラーの場所に破線が出たり、helmでどこから参照されてるかの一覧出したり、シンボル検索したり、中々リッチな環境です。

f:id:nodamushi:20180722021735p:plain:w320

f:id:nodamushi:20180722024750p:plain:w320

 しかし、Eclipse等の様に、エラーに対してどう修正すれば良いのか、までは提案してくれない。(※Visual Studioでもしてくれなかったわ)
 そして、正直ドキュメント書きにくい。Visual Studioみたく、///を書いたらparamとかreturnを自動生成して欲しい。summaryだけじゃ足りない。

 後、色々調べていると、omnisharp-unit-testとかいう関数を使ってる古い記事があるんだけど、そんな関数見つからない。どうすりゃいいの。
 仕方がないから、compileコマンドから"dotnet run"とか"dotnet test"で起動してみれば、まぁ、何とか起動は出来る。

f:id:nodamushi:20180722024318p:plain:w320

 とはいえ、ちょっとしんどい。
 Eclipseみたく現在開いてるファイルのmain関数を実行するとかも出来ない。
 なら、最低限replが欲しいので、Visual Studio Communityをインストールしたら入ったcsiをcomint-runで動かしてみたけど、文字化けするし、複数行入れると変になるし、あまり上手く動作してくれなかった。むぅ………


 というわけで、C#をEmacsで開発するのは、エディタとしてみると中々リッチだけど、IDEとしてみると不十分な感じです。



 ならば次はVisual Studio Codeかな………。明日へ続く。
 

Pythonが好かん

 いや、もうね、昔からずっと思ってるんだけどね、言いたかった。Pythonが好かん。

 

Pythonのここがいい

 Pythonが好かんとか言いながら、相変わらずPython使ってるのには、やはりPythonにはPythonのメリットがあるからだ。
 それは1にライブラリの多さ、2に手軽さ、3に(Clojureに比べて)起動が高速。(いや、まぁ、Clojureが遅すぎるだけだけど。)

 大した処理をしないスクリプトならGaucheやBash、AWKで済むけど、やっぱライブラリが使いたいとかなると、手軽さと起動の速さとLinuxで準備しなくても使えるという点から、基本的にはPerlかPythonになってしまう。
 今更もうPerl5とかあれだ(もう10年使ってないよ)
 数値解析したいとなるとPython以外の選択肢を教えて欲しい。

 結局Python一択だ。

 

Pythonのここが嫌い

filter、mapが致命的に糞

 Clojureで以下の様に書けるmapやらflattenやfilterの連打をPythonで気持ちよく書けない。しかも、map(lambda x:x+1,array)とか読みにくい。内包表記なんてもっと読みにくい。
 むしろ、言語的に糞とよく言われるJava以下だ。JavaのStreamは気持ちいいと思う。
 

(->> some-data
        (map #(% :value))
        (flatten)
        (filter #(= (bit-and % 1) 0))
        (map #(* % 10))
        (take 10))

 

統一感がない

 いつも思うのだが、どうもPythonは言語全体として統一感がない。

 その一番の理由が組み込み関数だ。strだとか、lenだとか、皆は使っていて疑問に思わないのだろうか。なぜ関数とメソッドが言語レベルでチャンポンになっているのだ。
 いや、まだ関数とメソッドならまだしも、更にin、and等の様に演算子までチャンポン。

 いつも毎日Pythonを触ってるというのなら、覚えてしまって気にならないのかも知れない。だが、使うのが月に1回ぐらいだと、何が組み込みで何が関数なのかとか、そんなものを記憶しろというのが無理だ。IQ10あるかないかの低性能な私の脳には、記憶力なんて高等な機能は備わっていないのだ。

 一方で、Clojureを見よ。全部関数かマクロだ。悩むことがない。何か処理がしたければ、とりあえず補完を表示して、とりあえず何かそれっぽい単語で絞れば良いのだ。

 だいたい、lenだとかstrだとかその中途半端なatoi的な省略も気に入らない。length、stringとしてくれ。え?Clojureのstr?Clojureは良いんですよ(´・∀・`)ダブスタ

 変数のスコープも意味が分からない。同じスコープ内にいるつもりなのに、変数が見える書き方と、見えない書き方がある。この間ハマった。グローバル変数は読めるのに書けない。でも、グローバル変数としてアクセスしていなければ、ローカル変数として書き込める。すなわち、文法が文脈依存だ。意味が分からない。書けないなら読めないべきだし、読めるなら書けるべきだ。どう考えても統一性がない。変数宣言の構文がないのが最大の癌なのだろう。

 もう一つ、無名関数の統一感の無さもヤバいと思う。

def method(arg1,arg2):
    pass

reduce(lambda arg1,arg2: arg1+arg2; , list)

 この構文を考えた奴は何故()のありなしを変更したのか。実に読みにくいと思う。どっちかに統一してほしい。


オブジェクト指向が邪魔

 私はオブジェクト指向が大好きだ。JavaやC++を愛している。TypeScriptも好きだ。C#とScalaは残念ながらあまり使う機会が無かった。
 何にせよ、オブジェクト指向自体は全く批判する気が無い。

 だが思うのだ。オブジェクト指向のメリットはカプセル化やポリモーフィズムなどなど、色々あるが、最大のメリットって、やっぱ補完だと。
 予めキッチリかっちりインターフェースでメソッド宣言しておき、実際に実装する時のメソッドの使い方や何があったかなんかは、補完で出せば良いのだ。

 すなわち、型がエディタにとってわかりやすいからこそ、オブジェクト指向は素晴らしいのだ。


 翻って、Pythonはどうだろう。私はjediを使っているが、エディタの補完がしょぼい。しかも何か動作がもっさりする。何の為のオブジェクト指向だ。Type Hints?残念だけどデフォルトでPython3入ってないんだよね。デフォルトで入ってるの2系なんだよね。それも2.6とか。

 むろん、Pythonで大型のプログラムを書く人は、オブジェクト指向の型以外のメリットを十分に享受しているのだろう。
 だが残念だが、私はスクリプト的にしか使っていない。データ構造?dictかtuplか配列しか使ったことがない。class?Pythonでクラスを書いたことがない。

 なのに、クラスのメソッドと、関数を使い分けさせられる。勘弁してくれ。全部関数にしてくれ。あぁ、Clojureは素晴らしいなぁ。(起動遅い & Javaのメソッド呼ぶ時は補完でわかりにくい)

 

 

まとめ

 結局の所、私はPythonを使い慣れていない、使う機会が少ないが故に、記憶が出来ないのに、言語に統一感がなく、チャンポンなせいで一々ドキュメントを引くのが面倒くさい、というのがPythonが嫌いな一番の原因だろう。
 ちょっとしたことが組み込み関数だったり、演算子だったり、構文だったり、メソッドだったりで分かれているのが非常に面倒くさい。時々ペロッとスクリプト書くなら、手軽さならBashやGauche、処理に時間がかかるなどで、起動コストを気にしなくて良いならClojureが一番だと思う。

 が、やはりそこそこの速度とライブラリの充実さでPythonを選ばざるを得ないから、時々こうやってストレスが溜まるのだ。

Razer TARTARUS V2をRazer synapse3無しで動かす

 昨日不満をぶちまけたRazer TARTARUS V2だが、悪いのはソフトであって、ハードではない。
 ソフトを憎み、ハードを憎まず。


 ハードは物がなければどうにもならないが、ソフトなら何とかならないこともないはずだ。

 

HID macros

 ここでまず候補に挙がるのが、HID macrosだろう。(※AutoHotkeyLowLevelKeyboardProcによるフックで動くので、デバイスごとの設定はできない。)

 だが、ここで問題が。私のWindows 10ではHID macrosが動かないのだ。

 もう開発されていないし、メンテナンスもされていないので仕方がないが、何故だ。

 

HidKeySequence

 HidKeySequenceという複数のデバイスを認識し、それぞれにマクロを割り当てるソフトがあるらしい。
 これだ!と勇み足でダウンロードボタンを押すが、私がアクセスブロックされて、ダウンロード出来ない。何故だ。

 私は何か悪さをしたのだろうか。
 人から拒否されるなんて、そんな…思い当たる節が多すぎるが、HidKeySequenceは昨日知ったのでまだ悪さはしてないと信じたい。

 
 

 公式はウンコ。AutoHotkeyは駄目。HID macrosは動かない。HidKeySequenceは拒否された。
 残念だ。四面楚歌だ。ここで試合終了です。

 

 

パンがないならパンを焼けば良いじゃない

 と、普通ならもう諦めて、大人しくTARTARUS V2(1万円)をそっとゴミ箱に捨てるか、Razer synapse3と共に生きる覚悟を決めるのだろうが、残念だが私は糞ニートだ。
 ここで我を通せぬ様では糞ニートなんてやっていられない。こんなところで折れる様では、老後が心配になってしまうではないか。親をも恐れぬ精神で立ち向かう義務がある。

 さて、長い前置きはここまでにして、もうこうなってしまった以上、自分でプログラムするしかない。
 が、ここでも問題がある。

 Windowsにおいて、キーボード入力をフックするにはLowLevelKeyboardProcを使えばいいのだが、これだとAutoHotkeyと同様でデバイスを認識出来ない。デバイスを認識したいならRaw Inputを使う必要がある。

 なら、これら二つを組み合わせれば良い。これは完全に正しい。これらの情報のどちらが先に来るのか分からない、という点を除けば。

 正確に認識するには入力をバッファリングやブロックしたりと、かなり面倒だ。

 

 だったら、もう直接デバイスの入力を奪ってしまえば良いのだが、これをするにはドライバを実装する必要がある。
 これも面倒だ。

 

Interception

 流石にドライバは書きたくないし、WinAPIも叩きたくない。
 どうにかならないかと探していたら、Interception(GitHub)という、マウスやキーボードの入力を傍受し、書き換えたり送信したりが簡単に出来るCライブラリを見つけた。

 仕組みとしてはInterceptionが提供する仮想ドライバが各マウスやキーボードの入力を制御する機能を提供し、ライブラリ利用者はその仮想ドライバを操作する。

 なお、ドライバは商用版ではソースコードが提供される様だが、フリー版では非公開だ。
 ドライバが安全かどうかは信じて使うしかない。暫く通信を監視してたけど、怪しげな通信は無かったと思うよ。(もし、この記事を真似て自作する場合は、自己責任でお願いします。)


 

 さて、先ずはRazer TARTARUS V2の入力をすべて奪ってみる。(なお、TARTARUS V2のHIDは環境に合わせて変更してしてください。)

#include "interception.h"
#include <windows.h>
#include <iostream>
#include <wchar.h>
using namespace std;

//環境秘合わせて変更してください。
const wchar_t KEYBD_HID[] = L"HID\\VID_1532&PID_022B&REV_0200&MI_00";
const wchar_t MOUSE_HID[] = L"HID\\VID_1532&PID_022B&REV_0200&MI_02";

int main(){
  // 高優先度化
  SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);

  //Interceptionのインスタンス生成
  auto context = interception_create_context();
  if(!context)return 1;
  interception_set_filter(context, interception_is_keyboard, 
                          INTERCEPTION_FILTER_KEY_DOWN | 
                          INTERCEPTION_FILTER_KEY_UP |
                          INTERCEPTION_KEY_E0|
                          INTERCEPTION_KEY_E1);
  interception_set_filter(context, interception_is_mouse,
                          INTERCEPTION_MOUSE_WHEEL|
                          INTERCEPTION_FILTER_MOUSE_MIDDLE_BUTTON_DOWN|
                          INTERCEPTION_FILTER_MOUSE_MIDDLE_BUTTON_UP
                          );


  //  TARTARUS V2を探す
  InterceptionDevice keyboard=INTERCEPTION_MAX_DEVICE,mouse=INTERCEPTION_MAX_DEVICE;
  wchar_t buf[500];
  for(size_t i = 0;i<INTERCEPTION_MAX_KEYBOARD;i++){
    InterceptionDevice d = INTERCEPTION_KEYBOARD(i);
    if(interception_get_hardware_id(context, d,buf, sizeof(buf)) && 
       wcscmp(KEYBD_HID,buf)==0){
      keyboard = d;
      break;
    }
  }
  for(InterceptionDevice i = 0;i<INTERCEPTION_MAX_MOUSE;i++){
    InterceptionDevice d = INTERCEPTION_MOUSE(i);
    if(interception_get_hardware_id(context, d, buf,sizeof(buf)) &&
       wcscmp(MOUSE_HID,buf)==0){
      mouse = d;
      break;
    }
  }
  if(keyboard == INTERCEPTION_MAX_DEVICE || mouse ==INTERCEPTION_MAX_DEVICE){
    interception_destroy_context(context);
    return 1;
  }
  
  // 入力を処理する
  InterceptionDevice device;
  InterceptionStroke stroke;
  while(interception_receive(context, device = interception_wait(context), &stroke, 1)>0){
    if(device == keyboard){
      InterceptionKeyStroke& s = *(InterceptionKeyStroke *)&stroke;
      cout << "Keyboard Input "
           << "ScanCode="<< s.code 
           << " State="<< s.state<<endl;
    }else if(device == mouse){
      InterceptionMouseStroke& s = *(InterceptionMouseStroke *)&stroke;
      cout << "Mouse Input" 
           << " State="<<s.state
           << " Rolling="<<s.rolling
           << " Flags="<<s.flags
           << " (x,y)=("<<s.x<<","<<s.y<<")"
           << endl;
    }else{
      //他のデバイスの入力は通過させる
      interception_send(context, device, &stroke, 1);
      if(interception_is_keyboard(device)){//Escapeで終了
        InterceptionKeyStroke &s = *(InterceptionKeyStroke *) &stroke;
        if(s.code == 1) break;
      }
    }
  }

  interception_destroy_context(context);
  return 0;
}


 これを以下のコマンドでコンパイル。

g++ main.cpp -linterception -Ldllのあるディレクトリ

 生成されたa.exeを実行してみる。(dllは読み込める様にしておくこと)
f:id:nodamushi:20180617154501p:plain

 おぉ、TARTARUS V2の入力部分は、「Keyboard Input~~」、「Mouse Input~~」と出るが、他のキーボードで「hokanoki-bo-do(他のキーボード)」と打ったところはそのまま出力されている。
 確かにTARTARUSの入力を認識し、その入力を奪うことが出来た様だ。

 入力が奪えたなら、今度は入力を変更してみる。TARTARUSの1キーが押されたら「nodamusi」、2キーが押されたら「Ctrl+Z×3」を入力する様にしてみる。
if(device==keyboard)の中に以下を追加。

      if(s.code == 2) {
        // 1キー
        s.code = 49;//n
        interception_send(context, device, &stroke, 1);
        s.code = 24;//o
        interception_send(context, device, &stroke, 1);
        s.code = 32;//d
        interception_send(context, device, &stroke, 1);
        s.code = 30;//a
        interception_send(context, device, &stroke, 1);
        s.code = 50;//m
        interception_send(context, device, &stroke, 1);
        s.code = 22;//u
        interception_send(context, device, &stroke, 1);
        s.code = 31;//s
        interception_send(context, device, &stroke, 1);
        s.code = 23;//i
        interception_send(context, device, &stroke, 1);
      }else if(s.code == 3){
        // 2キー
        s.code = 0x1D;//CTRL
        interception_send(context, device, &stroke, 1);
        s.code = 44;// z
        interception_send(context, device, &stroke, 1);
        if(s.state == 0){//キーを押した場合は更に追加で2回押す
          s.code = 0x1D;//CTRL
          interception_send(context, device, &stroke, 1);
          s.code = 44;// z
          interception_send(context, device, &stroke, 1);
          s.code = 0x1D;//CTRL
          interception_send(context, device, &stroke, 1);
          s.code = 44;// z
          interception_send(context, device, &stroke, 1);
        }
      }

 で、試しに実行してみる。(1を押す→Enter→1を押す→2を押す)
f:id:nodamushi:20180617155946p:plain

 おぉおお!
 入力を奪えて、異なる入力を突っ込むことが出来た。もう何も怖くない。

 ちなみに、このプログラムのメモリ使用量は600kbだ。-O3 -Wl,--subsystem,windows -mwindowsをオプションに入れてコンパイルすると300kb程度になった。ドライバがどの程度のメモリを使ってるか分からないが、Razer synapse3の360Mbと比べるべくもないだろう。変な情報も送信しないし。

 むろん、よく出来たソフトに比べれば、直にCを書くのは面倒くさい。ソフトごとにキーバインドを変えたければ、それように作り、手動で起動、終了をするか、アプリケーションを自動認識する機能を作る必要がある。ちょっと面倒だ。

 だが、ドライバを書いたりWindows APIを直に弄るより圧倒的に楽だし、C言語だからやりたい放題だ。
 例えば、他の矢印ボタンが押されてる間は、他の矢印ボタンは無効化とか、最初の矢印ボタンに変更なんてことも可能だ。


 とりあえず、これで快適なRazer TARTARUS V2環境を手に入れることが出来た。
 Razer TARTARUS V2は今、神となったのだ(`・д´・ )

Razer TARTARUS V2 ハードは完璧、ソフトは酷すぎる

>追記:Razer TARTARU V2をRazer synapse3無しで動かす


 Razer TARTARUS V2という左手用キーボードを購入したので、そのレビューというか、愚痴。

 最近、Logicool アドバンス ゲームボードG13rの調子が悪く、新しい左手用キーボードが必要になりました。
 で、Razer TARTARUS V2というゲーミングキーボードのフィット感がヤバく、その場で衝動買い。お値段1万ちょっと。

 なお、当方PCでゲームはしません。CADや3Dモデリングに使うつもりで購入しました。


 この親指部分がとても気に入りました。


 なお、注意点としては、この十字キーは、目で見て右側が上ボタン、左側が下ボタン、上側が左ボタン、下側が右ボタンになっています。
 親指を押し込むと上、引くと下という思想の設計なのでしょう。ユーティリティで変更出来るので特に気になりませんが。

 

 全体の見た目。

 個人的にはホイールは若干位置的に回しにくく、15番のキーの位置にホイールが欲しいかなと思います。
 
 

 ホイールは若干不満がありましたが、全体的に押しやすく、安定感があり、G13rの親指のレバーみたいに細くないので強く押しても痛くなく、とても良い作りだと思います。
 

ソフトが壊滅的

 

 この手のキーボードはユーティリティで対象アプリケーションに合わせて、キーの機能を変更する必要があります。
 そのユーティリティはTARTARUS V2をUSBにさした時点で自動的にインストール開始されます。ちょっと時間がかかるけど、楽で良い。

 と、ご機嫌だったのはここまで


 ………ふぁっ?

 ちょっと待ってほしい。アカウント登録してサインインしなければ設定が出来ない。ローカルだけで使うつもりなのに。

 何故、一々アカウントを登録せねばならないのか。一ユーザーとしてはデバイスを使いたいだけだ。1万円払ってなお、まだ個人情報が欲しい、よこせと言われている様にしか思えない。

 この時点で不信感が非常に漂い始める

 ここで、ちらっとタスクマネージャーを見てみる。

f:id:nodamushi:20180616114135p:plain

 うぅん!?合計メモリ使用量360Mb?高々キーボードのユーティリティの為に!?

 私の貧弱なPCは16Gbしかメモリ積んでないから、これの為だけに2%強のメモリ使用量だ。
 大した量じゃないと言えば大した量じゃないが、Firerox、Clojureの開発やら、GradleやらEclipseやVisual Studio、仮想マシン、RAM Disk、Emacs、Inkscapeなどのヘビー級のプロセスを何個も起動して放置してる私としては、メモリが不足がちなので、ちょっと勘弁して頂きたい。まぁ、不足しがちな主原因はRAM Diskですが。

 うぅわぁ~………


 かなりゲンナリしながら渋々アカウントを取得し、設定をしてみました。

 設定ユーティリティはよく出来ていると思います。特段迷うこともなく私のやりたい設定は出来ました。若干マウスクリックが多くて面倒くさいと思ったけど。
 光るのが嫌いな私としては、ライティングをオフに出来る点も素晴らしい。(というか、何で何でもかんでも光らせようとするんだろ。)

 

 さて、ここで気になるのは、この重たい常駐ソフトを起動していなければこのデバイスが使えないのかどうか。
 何の情報を送ってるのか知らないが(これかな?)、Amazon AWSと通信してるし気持ち悪いので出来るなら切りたい。

 タスクバーのアイコンを右クリックするとメニューの中に「すべてアプリを終了する」というアイテムがあったのでこれをクリック。

f:id:nodamushi:20180616121618p:plain

 この状態で色々設定したホームポジションの横一列を打ってみると「asdf」となった。
 残念ながら、これはデフォルトのキーバインドです。ソフトを起動していなければ動かない

 嫌だなぁ………。


 しかし、ここでさらにタスクマネージャーを見て驚愕の事態に気がついた。
f:id:nodamushi:20180616122349p:plain
注:「すべてのアプリを終了する」を押した後


 終了してない。しかも、タスクを殺しても、勝手に復活する。
 Game Manager ServiceとCentral Serviceが相互に起動し合う形になってるのか、これら二つが復活するのだ。
 それはもう、ゾンビプロセスってこういう意味だったっけ?てレベルで復活する。Cortanaレベルで復活する。

 Game Manager Serviceは高速で再起動するので、Central Service→Game Manager Serviceと殺せば、殺せるが、ちょっともたつくとゾンビアタックされる。

 

 神奈川県警サイバー犯罪対策課によると、WebページのJavaScriptで30%程度のCPU計算能力を使って採掘をする行為は、人格を疑うレベルの犯罪行為らしいので、私の意図に関係なく150Mbの記憶容量を奪い、しかも通常に終了する手段を持たないこれは警察レベルで言えば、ウィルスだと言っても良いはずだ。この記事のタイトルは「Razer TARTARU V2 ハードは完璧、ソフトはウィルス」と書きたいぐらいだ。

 

結論

 

 ハードは、正直ソフトの悪印象を考慮した上で言って、素晴らしい。強いて言うなら、ホイールの位置と、親指にもう2個程ボタンが欲しいぐらいか。
 3Dモデリング、CAD、ペイントソフト、さまざなま場面で活躍出来るポテンシャルを持っている。
 私が使用していたLogicool ロジクール アドバンス ゲームボード G13rと比較しても、使いやすく、非常にハードだけはお勧め出来る。1万円の価値はある。

 だが、ソフトを込みで考えると、ちょっと考えるべきだ。[糞ソフト]Razer Synapse3 について語るスレ[プロセス激重]での報告を見ると、様々なバグが存在する様だ。

 ちなみに、私はスリープから復帰するとRazer TARTARUS V2のプロファイルがデフォルトに戻り、変更が全く効かなくなるという症状が出ている。再起動しないと直らない。スリープとは何だったのか。

 私は衝動買いし、自力でソフトは解決(追記参照)することでTARTARU V2は神と化したが、買うかどうかは情報をよく集め、よくよく吟味してから購入することをお勧めする。

>追記:Razer TARTARU V2をRazer synapse3無しで動かす

 

nio.Pathの小さなユーティリティライブラリを公開してみた

 Google guava………便利だよね。Appache Common………便利だよね。

f:id:nodamushi:20180506204944p:plain

 ………まぁ、デカいとは言わないけど、ちりも積もればなんとやらだよね?


f:id:nodamushi:20180506204654p:plain

 ………でかすぎね?

 そう、私は言いたいのだ。世の便利なJarファイルはデカい。デカすぎる。
 ちょっとした内製ツールをウッカリ慣れてるJavaやClojureで作ってしまって、他の人達が単体で使える様に共有サーバー(遅い)へzipして置くと、なんだかんだ言って容量を食うのだ。今、この文章を打った時に、「なんだかんだ言って面積を食うのだ」と書いてしまった私は論理に毒されてきている気がするぞ。

 

 果たしてGoogle guavaの一体どれほどのクラスを使ったことがあろうか。Appache Common IOのどれほどを使ったことがあろうか。Eclipse Collectionの、ホゲホゲの、モゲモゲの、ピヨピヨの世に数多あるライブラリの一体何パーセントを使っているというのだ。恐らく数kb分しか使ってない。そのために100kb?ウンMb?肩身が狭い!

 
 

github.com.nodamushi:common.paths


 と、私が叫んでいると言うことは、案外同じ事思ってる人って居るんじゃねぇの?ってことで、普段よく使ってるPathに関する関数を集めて、まとめてみた。GitHub - nodamushi/common.paths: A small utilities for java.nio.file.Path.

 コンセプトは地味でコンパクトだ。
 クラスは内部クラスを除けば地味に一個しかない。Jarファイルは地味に13kbyte。まぁ、これぐらいなら地味に許されてもいいんじゃないだろうか?

 あと、地味にnio.Pathをそのまま使うライブラリってあまりない気がする。Stringか、Fileだよね。

 

地味にMavenに置いた

 私が作った物使う阿呆いねぇだろ、ってことで、何か作ってもソースコード公開するだけで放置がデフォルトなんだけど、今回のは地味に個人的に何処でも使いたいので、地味に登録してみることにした。

<dependency>
  <groupId>com.github.nodamushi</groupId>
  <artifactId>common.paths</artifactId>
  <version>1.0.0</version>
</dependency>

 初めてやってみたが、ぶっちゃけ、置けないだろって思ってたら、置けてしまった。びっくりだ。
 repositoriesを書かなくても、dependencyだけで、ネットから自分のライブラリが勝手にダウンロードされるというのは、妙な気分だね。

 参考にしたページ:

 GPGはMSYS2ので普通にいけた。

後の祭り

 私は普段、ライブラリ系のプロジェクトのartifactIdはgroupIdとくっつけると基準パッケージになるようにつけてます。パッケージ名がhoge.moge.piyoで、groupIdがhogeなら、artifactIdはmoge.piyoって感じ。一律に付けられるからこの命名は楽

 なんだけど、よくよく考えたら一般的にはhamcrest-allとかみたいになってるんだよね。
 後で気がついたぜ。

 まぁ、もうやっちゃったモンは仕方がない。気にしないぜ。(´・∀・`)

地味に便利な関数達

 地味に便利な関数を簡単に紹介していこう。

 

newBufferedReader

 地味によく使う。Files.newBufferedReaderと違って、UTF-8のBOMを自動的に無視するBufferedReaderを作成する。ついでにUTF-16にも対応した。

try(var r = NPaths.newBufferedReader(utfFile, StandardCharset.UTF_8){
  var line = r.readLine(); //自動的にBOMは無視される
}

 

replaceExtension

 地味に割と使う。Pathのファイル名から拡張子だけ変更するときって稀によくある。

var txt = Paths.get("/hoge/piyo.txt");
var csv = NPaths.replaceExtension(txt,"csv"); // /hoge/piyo.csv
var ext = NPaths.getExtension(txt); //拡張子取得もあるよ。

 ファイル名の先頭に文字追加したり、っていうのもあるよ。

 

getParent

 地味に意識して使う。Pathの親を取得する時に、決してnullを返さない。
 面倒くさがってpath.getParent()ってやってて、findBugもせずにヌルポが発生した時の悲しさよ。

var path = Paths.get("a.txt");
var nullpath = path.getParent();//null
var nonnull = NPaths.getParent(path); // empty path
// parentがnullでも大丈夫なresolve
resolve(nullpath"b.txt"); // b.txt

 
 

walkFiles

 極稀に使う。FileVistorのvisitFileだけを実装することで動かす。潜るディレクトリの深さも指定可能。
 とりあえずディレクトリを再帰的にファイルを集めたいとか、再帰的にファイルだけ何か処理したい時に便利。

List<Path> list = new ArrayList<>();
NPaths.walkFiles( dir, -1 ,(path,attr)->{
  list.add(path);
  return FileVisitResult.CONTINUE;
});

 

iterator

 あんま使わないかな。
 デフォルトのPathのイテレータとの違いは、ルートも一つの要素と見なす。デフォルトのイテレータの様に名前だけにすることも可能。

for(Path p :NPaths.iterator( Paths.get("C:/a/b/c") ){
  //  p:
  // 1回目: C:/
  // 2回目: C:/a
  // 3回目: C:/a/b
  // 4回目: C:/a/b/c
}

 

まとめ

 もっと小っちゃいモジュールで世の中が溢れると、良いですね。
 といいつつ、結局面倒くさくなって大きな奴を使うんじゃないんかな。

EmacsのIronyがタイムアウトする場合の対処法

 EmacsのIronyがサーバーエラーも何も返さず、タイムアウトするという現象に悩み、何とか解決しました。

バージョン

Emacs25
Irony :20180418

現象

 company-ironyで補完を仕様とすると、サーバーエラーも返さず、タイムアウトする。タイムアウトの時間を10秒など伸ばしても変化無し。

 Irony Serverを単体で動かせば、正常に動作する。

 

原因

 start-process-shell-commandで使われるshellがtcshである。
 (list-processesでプロセス一覧出して、Ironyのコマンドがtcshから始まっているかいないかで確認可能)

 

解決方法

 適当にbashなどのshellに変更する

(setq shell-file-name "/bin/bash")

 

 ひとまず私はこれで直りました。

JAXBをEclipseLink MOXyに移行する

 JavaでXMLを処理しようと思えば、やっぱJAXBですよね。便利ですもんね。

 ですが、このJAXBがJava9で非推奨になり、早くもJava11で完全削除されることになりました。早くねぇ?1年しかないんですけどぉ?
 けっこう呑気していたnodamushiも一瞬巨大に見える程のOracle圧力にはビビった!!

 というわけで、太った 重い腰をいい加減あげて、脱JREのJAXBをしてみました。


EclipseLink MOXy

 脱JREのJAXBと言いましたが、何も脱JAXBをする訳ではありません。外部実装に移行します。

 その選択肢の一つがEclipseLink MOXyです。(選択肢の一つと言いましたが、他の選択肢があるのか知りません。)

 MOXyはXMLだけじゃなくて、JSONのバインディングにも対応しているそうです。
 また、XSD→Javaに変換するコンパイラもちゃんとあります。(jaxb-compiler)

 今の所、xjcはJDKに付属していますが、JDK11で無くなると思うので、EclipseLink Installer Zipをダウンロードして、適当な場所に展開し、binディレクトリにパスを通してください。
 (展開するだけな気がするんだけど、Installerってどういうことなんだろ?)

f:id:nodamushi:20180401171240p:plain:w320

XML Schema をJavaクラスに変換する


 試しに、XML Schemaファイルperson.xsdを作成し、Javaクラスに変換してみます。構造は以下の図の様になっています。

f:id:nodamushi:20180401173218p:plain

<?xml version="1.0"?> 
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <xsd:element name="persons">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element ref="person"  minOccurs="0" maxOccurs="unbounded"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>

  <xsd:element name="person">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="name" type="xsd:string" minOccurs="1" maxOccurs="1"/>
        <xsd:element name="age"  type="xsd:unsignedInt" minOccurs="0" maxOccurs="1"/>
        <xsd:element name="sex"  type="sexType" minOccurs="0" maxOccurs="1"/>
      </xsd:sequence>
      <xsd:attribute name="id" type="xsd:string" use="optional"/>
    </xsd:complexType>
  </xsd:element>
  
  <xsd:simpleType name="sexType">
    <xsd:restriction base="xsd:string">
      <xsd:enumeration value="female"/>
      <xsd:enumeration value="male"/>
    </xsd:restriction>
  </xsd:simpleType>
</xsd:schema>


 上記のperson.xsdをJavaに変換します。
 xjcとの差を確かめる為に両方で変換してみました。(person.xsdはsrc/main/xsdディレクトリに配置しています)

xjc -no-header -encoding utf8 -d src/main/java -p test.xjc src/main/xsd/person.xsd
jaxb-compiler -no-header -encoding utf8 -d src/main/java -p test.eclipselink src/main/xsd/person.xsd

 生成されたファイルのdiffをとり、違いを見てみました。

xjc EclipseLink diff
test/xjc/ObjectFactory.java test/eclipselink/ObjectFactory.java packageの名前が違うのみ
test/xjc/Persons.java test/eclipselink/Persons.java packageの行のみ違う
test/xjc/Person.java test/eclipselink/Person.java packageの行のみ違う
test/xjc/SexType.java test/eclipselink/SexType.java packageの行のみ違う
test/eclipselink/jaxb.properties xjcでは生成されない


 上記の結果を見る限り、生成結果は全く同じなようです。安心してそのまま移行出来ますね。

 なお、EclipseLinkのほうでは生成されたjaxb.propertiesファイル(中身は以下の一行)は、XMLを読み込み、各クラスのデータにバインドする処理を行うクラスをどこから取得するのかという設定ファイルになります。

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

 JDK11からはJAXBが同梱されない為、この設定ファイルは必須になります。


 

 

MOXyをプログラムで使ってみる

 上記で生成したpersonクラスを実際にXMLを読み出して、バインドしてみます。

 環境は以下です。

  • JDK:Java10
  • ビルドツール:Gradle

 まずは、build.gradleのdependenciesに以下の二行を追加します。versionは適宜変更してください

compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0'
compile group: 'org.eclipse.persistence', name: 'org.eclipse.persistence.moxy', version: '2.7.1'

 

 gradle initで生成されたAppクラスを以下の様に実装。
 長々と書いていますが、やってることは単純。

  1. XMLを読み込んで、xjcとjaxb-compilerで生成したクラスにバインド
  2. test.xjc.Personとtest.eclipselink.Personでは、同じクラス名で使いにくいので、App内で定義したPersonクラスに変換
  3. System.out.printlnで出力
  4. エラーがあった場合はFailとだけ表示
import java.io.File;
import java.lang.reflect.Method;
import java.util.List;
import java.util.stream.Collectors;
import javax.xml.bind.JAXB;

public class App {

  public static void main(String[] args) {
    File xml = new File("./src/test/resources/test.xml");
    try {
      System.out.println("-------XJC------------------");
      loadAndPrint(xml,test.xjc.Persons.class);
    }catch(Exception e) {
      System.out.println("Fail");
    }
    try {
      System.out.println("--------EclipseLink---------");
      loadAndPrint(xml,test.eclipselink.Persons.class);
    }catch(Exception e) {
      System.out.println("Fail");
    }
  }
  
  public static void loadAndPrint(File xml,Class<? extends Object> clz)throws Exception{
    Object persons = JAXB.unmarshal(xml, clz);
    List<Person> person = toPersonList(persons);
    person.forEach(System.out::println);
  }
  
  public static class Person {
    public String name,sex,id;
    public Long age;
    public Person(String name, Long age, Object sex, String id){
      this.name = name; this.age = age;this.id = id;
      this.sex = sex==null?null:sex.toString();
    }
    @Override public String toString(){
      return String.format("person:[id=%s],name=%s,age=%d,sex=%s",
          id,name,age,sex);
    }
  }
  
  @SuppressWarnings("unchecked")
  private static List<Person> toPersonList(Object persons) {
    return ((List<Object>)get(persons,"person",List.class)).stream()
        .map(p->new Person(
            get(p,"name",String.class),
            get(p,"age",Long.class),
            get(p,"sex",Object.class),
            get(p,"id",String.class)))
        .collect(Collectors.toList());
  }
  
  private static String firstUpperCase(String str) {
    if(str.length()==1)return str.toUpperCase();
    return new StringBuilder(str.length())
        .append(Character.toUpperCase(str.charAt(0)))
        .append(str,1,str.length())
        .toString();
  }
  
  private static <T> T get(Object element,String childName,Class<T> clz){
    try {
      Class<? extends Object> c = element.getClass();
      String mName = "get"+firstUpperCase(childName);
      Method m = c.getMethod(mName);
      m.setAccessible(true);
      Object o = m.invoke(element);
      return clz.cast(o);
    }catch(Exception e) {
      throw new RuntimeException(e);
    }
  }
}

 

 10行目で直接指定しているテスト用のXMLファイルは以下の様な内容です。

<persons>
  <person id="1">
    <name>nodamushi</name>
    <age>19</age>
    <sex>male</sex>
  </person>
  <person>
    <name>Aoki Kei</name>
    <age>30</age>
  </person>
  <person>
    <name>Koike Yuka</name>
    <sex>femail</sex><!-- typo-->
  </person>
</persons>

 で、ビルドして実行すると以下の様になりました。

-------XJC------------------
Fail
--------EclipseLink---------
person:[id=1],name=nodamushi,age=19,sex=MALE
person:[id=null],name=Aoki Kei,age=30,sex=null
person:[id=null],name=Koike Yuka,age=null,sex=null


 

 XJC側は失敗していますが、EclipseLink MOXyの方は正常に読み込めていますね。
(EclipseLinkだけ読み込めたのはtest.eclipselink.jaxb.propertiesがあるからです。typo処理の違いではありません。なお、Java8で実行すると、両方ちゃんと読み込めます。)

 

 

まとめ

 

  • EclipseLink Installerをダウンロードし、binディレクトリにパスを通す。(jaxb-compilerが使える様になる)
  • GradleやMavenの設定ファイルにjaxb-apiEclipseLink MOXyをdependencyに追加する


 というわけで、あまり苦労することなく、EclipseLink MOXyに移行出来そうです。

 が、依存含めて外部jarファイルが5Mbもあるのはいかがなもんでしょうかね………。