プログラムdeタマゴ

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

C#ぼっち勉強会 配列

 はい、随分期間が空きましたが、別に勉強サボってた訳じゃ無くて、単にブログ書くのが面倒くさかっただけです。

 チマチマとC#で作る様になってきてJavaと比較して使いにくい点もままあるけど(特にInterfaceがデフォルト実装持てないのと、String.Spritが使いにくい、インデント幅4は長すぎる)、まぁ、良い言語かなと思います。

 

 文法は、JavaとC++をやっていれば、微妙な違いはありますが、だいたいどちらかの言語にある概念を持ってくれば理解できます。
 というわけで、文法はすっ飛ばして、データ型とその処理について書いていこうと思う。言語を使いこなせるかどうかって、結局、基本データ型(コレクション、ファイル、時間、Thread)の操作を使いこなせるかどうかが結構大きいからね。

 最初は配列からだ。

配列


 配列の型宣言は型[]だけが許されるっぽい。参照型で全てArrayクラスの継承クラス。各要素の初期値は0かnullである。

int[] array = {1,2,3,4};
// int array[] = {1,2,3,4}はだめ


 配列はおおよそ以下の種類がある。

  • 1次元配列
  • 多次元配列
  • ジャグ配列
  • スタック上の配列(Span)

 多次元配列はJavaにはない配列で、中身は1次元配列だけど、添え字で多次元的にアクセス出来る配列。
 以下の例の様に、array2dの添え字 [y,x]は、arrayの [x + y*X] と等価だ。(Xは1次元の長さ) 次元はRankに格納されていて、各次元の長さはGetLength(n)から取得出来る。

int[] array = {1,2,3 ,  4,5,6};
int[,] array2d = {{1,2,3}, {4,5,6}};

Console.Write(
    "[1,2] => array[1*3+2]({0}),  array2d[1,2]=({1})",
   array[2 + 1*3 ], array2d[1,2]);

 なお、多次元配列はforeachの前には無意味だ。

//どちらも同じ。
foreach( var i : array) Console.WriteLine(i);
foreach( var i : array2d)   Console.WriteLine(i);

 
 次に、ジャグ配列はJavaの配列と同じで、配列の要素に別の配列を入れるタイプの配列だ。各要素ごとに長さが違う配列を入れることが出来る。

int[][] array2dJ = new int[2][];
array2dJ[0] = new int[5];
array2dJ[1] = new int[3];

 
 最後に、なんとC#はnewせずに直接スタック上に配列が確保出来る。スゲぇ。Javaにもくれ。

// newではなく、スタック上に確保(解放コスト無し)
Span<int> array = stackalloc int[10];
array[0] = 100;

 なお、C#7.2以上じゃないと駄目なので、program.csprojのPropertyGroup.LangVersionをlatestとかにしておくこと。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <DebugType>Full</DebugType>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>
</Project>

配列の各要素を結合した文字列を作成

 String.Joinで各要素を結合した文字列を作ることが出来る。

int[] array = { 1, 2, 3, 4, 5, 6 };
Console.WriteLine("array={0}",String.Join(",",array));

 ただし、多次元配列だと駄目。面倒くさい。

部分配列(Span)

 Spanを使うことで部分配列を取り出すことが出来る。
 ただし、やっぱり多次元配列だと駄目。(DangerousCreateで出来る?)
 多次元配列いらなくない?[x + y * width]って書くのJavaで慣れてしまったし、正直、デメリットの方が大きいわ。
 Cの多次元配列と同じ構造なら、多次元から1次元にポインタ変換出来そうな気がするんだけど、手段無いのかな?

int[] array = { 1, 2, 3, 4, 5, 6 };
var span = array.AsSpan(2,3);// new Span<int>(array,2,3);
span[0] = 100;
span[1] = 101;
span[2] = 102;
//span[3] = 103; これはエラー
// {1,2,100,101,102,6}
Console.WriteLine("array={0}",String.Join(",",array));


特定のデータで埋める(JavaのArrays.fill)

 JavaのArrays.fillはArray.Clear,Array.Fillそれに当たる。0やnull初期化したいならArray.Clearが引数が無くて楽かも知れない。

int[] array = { 1, 2, 3, 4, 5, 6 };

// {1,0,0,0,5,6}
Array.Clear(array,1,3);
// {1,0,0,100,100,6}
Array.Fill(array,100,3,2);

検索や反転、コピー

 バイナリサーチ、反転、コピーなどのJavaではArraysクラスにstaticメソッドで提供されている関数は、C#ではArrayに一通り揃っている。


 ちなみに、int[]の配列では無く、Spanにしておくと、メソッドとして使えるっぽい。Spanで扱えるところは基本的にSpanで扱った方が楽そうだ。

int[] array={1,2,3,4,5,6};
Span<int> span = array;
Array.Reverse(array);
span.Reverse();

map,filter

 JavaでのmapやfilterはC#ではLINQで実現される。Javaと違って、Streamにしなくてもそのまま使える。

 だが、これがぶっちゃけ言って全然慣れない!!
 なんで、mapがSelectなん?SQLぽさなんていらんわ。Selectって響き的にfilterっぽいじゃん。で、filterがなんでWhereなん?英語的には条件式ってIf,Whenもありうるじゃん。Aggregate?英語音痴の私、この単語知らない。(集計)
 正直この名前付けには不満しかないわ。flattenないし。何でmap→flattenを一つにしてSelectManyにしたわけ?

 逆に、JavaのStreamでは削除されたzipがあるのは素晴らしい。

Java C#
map Select
filter Where
reduce Aggregate
sort OrderBy
sum,max,min,distinct Sum,Max,Min,Distinct
first FirstOrDefault
なし Zip,Concat,Join
flatten SelectMany(_=>_)
toArray ToArray
collect(toList()) ToList
groupingBy GroupBy
parallel AsParallel
IntStream.range Enumerable.Range


 Javaに無いJoinは、最初よく分からんかったけど、こういうことらしい。SQLの内部結合と同じ物を表現する為にあるらしい。SQLとか触ったことしかないからなぁ。

array.Join(array2, func1 , func2 , func3);
// ↓
array.Zip(array2,(x,y) => (x,y))
     .When(x => func1(x.x) == func2(x.y))
     .Select(x => func3(x.x,x.y))


 以上、次はコレクションかな。

C++でタスクトレイに格納するアプリケーションを作る

やりたいこと

 別に立派なGUIが作りたいんじゃない。作りたいのは、コンソールアプリで十分だ。

 しかし、そのコンソールアプリが常に何かを出力し続けていたら?ちょっとモード変更したいとか、そのための入力処理を作るのも億劫だし、そもそも流れ続けるコンソールに入力したくもない。だが、コンソールアプリで十分なのに、画面なんて作りたくない。

 これって、タスクトレイにボタン一つ置ければ、話は簡単じゃないか。

 しかし、そんなことをC++でやろうとすると、途端に非常に面倒くさいことが要求される。Windowsならメッセージを受け取る為の見えないウィンドウを作り、Shell_NotifyIconでNOTIFYICONDATAを登録し…。

 違う。違うんだ、私がやりたいのはそんなことじゃないんだ。ボタン押したら対応するイベントハンドラが発火して、後はチェックボックスぐらいがあれば、それで良いんだ。

 

zserge/tray


 WINAPIを叩いたとしても、まぁ、数百行あればかけるっちゃかけるだろう。
 でも私はWin APIを叩きたくないのだ。何故なら私はGUIを作りたい訳じゃないからだ。そこは本質ではないのだ。情けないことを言えば、ぶっちゃけWinAPIは得意ではないのだ。

 かといって、コンソールアプリを作ってるのに、Qtの出る幕は無い。


 というわけで、今回紹介するzserge/trayを使います。

github.com

 C++でタスクトレイに格納したアプリケーションを、ヘッダファイル1つをインクルードするだけで簡単に作れてしまうライブラリ。リンクは不要。しかもクラスプラットフォームだ。
 私はC++で使ったが、Cでも使える。(というか、C用だから、C++用も欲しいな)

 

サンプルコード

 永遠と「Hoge Hoge」と言い続けるが、タスクトレイから「Piyo Piyo」モードに変更も出来て、終了も出来るプログラムの例を示す。これだけで、もう何となく使うことが出来ると思う。

f:id:nodamushi:20180917020301p:plain

//↓ OS設定。LinuxならTRAY_APPINDICATOR、MacならTRAY_APPKIT。Makefileに書いとくといい。
#define TRAY_WINAPI 1
#include "tray.h"
#include <iostream>
#include <thread>
#include <chrono>

using namespace std;
using namespace std::chrono;

volatile bool piyoMode;    //!< Piyo Piyoとコンソールに出力するモード
volatile bool processDone; //!< 処理完了フラグ
void piyo(tray_menu *);    //!< Piyoモードのトグル
void finish(tray_menu *);  //!< 終了処理
void _main();              //!< Hoge Hogeと出力するメイン処理
tray tray ={               //!< タスクトレイの設定
  (char*)"icon.png", // タスクトレイのアイコン画像。別途用意しておく。
  (tray_menu[]){
    {(char*)"piyoMode",0,0,piyo}, //サブメニューを指定すれば、階層化も可能
    {(char*)"-"},                 // セパレータ
    {(char*)"exit",0,0,finish},   //終了
    {nullptr}                     //最後の要素。必須。
  }
};


int main(int argc, char *argv[]){
  piyoMode=processDone=false;
  thread t(_main);// 表示処理は別スレッド化
  tray_init(&tray); //タスクトレイに登録
  while(tray_loop(1) == 0); //タスクトレイが表示されている間ループ
  t.join();
  return 0;
}

void piyo(tray_menu * item){
  bool b =!item->checked;
  piyoMode = b;
  item->checked = b; //チェックボックスの表示非表示変更
  tray_update(&tray); //更新
}

void finish(tray_menu *){
  processDone=true;
  tray_exit();  // タスクトレイから削除
}

void _main(){
  while(!processDone){
    cout << (piyoMode? "Piyo Piyo" : "Hoge Hoge") << endl; // piyoかhogeを出力
    this_thread::sleep_for(seconds(2)); 
  }
  cout << "Done"<<endl;
}

tray構造体

 タスクトレイの情報。メニューとアイコンを設定する。Windowsでもアイコンってpngでいけるんやね。

static tray tray ={
  (char*)"icon.png",
  (tray_menu[]){
    {nullptr}      //最後の要素。必須。
  }
};

tray_menu構造体

 タスクトレイのメニュー。入れ子構造に出来たり、Check、Disable/Enableも設定出来る。textが"-"だとセパレータになる。

要素 説明
char* text メニュー名。"-"だとセパレータ
int disabled 有効無効の設定
int checked チェックの設定
void (*cb)(struct tray_menu*) コールバック関数。
void *context お好きな様にお使い下さい。
tray_menu* submenu サブメニュー

関数

  • tray_init(tray*): 初期化する。1が返ると失敗
  • tray_update(tray*): disabledとか、checkedとかを変更した時に呼び出して、画面を更新
  • tray_loop(int) : UIのループ。引数はブロッキングするかどうか。tray_exitが呼ばれると1が返る。
  • tray_exit() : UIループを終了する

Utubntu 18.04にTestLinkを入れてみた

 外のサービスはあまり使わせてくれないのに、仮想サーバーはポンポンくれるので、サーバーにTestLinkを入れてみた。お金ないの。

 で、自宅でもTestLink試してみるって事でVirtual Boxに入れてみた。その手順。

Virtual BoxにUbuntu 18.04をインストール

 いつものことなのでチャッチャと。まぁ、初Ubuntu Server 18.04なんだけど。
 もう面倒くさいので全部デフォルトだ。

f:id:nodamushi:20180910231609p:plain:w320

f:id:nodamushi:20180910231628p:plain:w320

f:id:nodamushi:20180910231647p:plain:w320

f:id:nodamushi:20180910231706p:plain:w320

f:id:nodamushi:20180910231725p:plain:w320

f:id:nodamushi:20180910231743p:plain:w320

f:id:nodamushi:20180910231825p:plain:w320

f:id:nodamushi:20180910231843p:plain:w320

f:id:nodamushi:20180910231859p:plain:w320

f:id:nodamushi:20180910231915p:plain:w320

f:id:nodamushi:20180910231947p:plain:w320

 全部「testlink」。セキュリティ?( ゚д゚)

f:id:nodamushi:20180910232033p:plain:w320
f:id:nodamushi:20180910232115p:plain:w320
 どうやらDockerを始めから入れておけるっぽいので、入れておいた。Enterを押すと「*」とチェックが入るので、その後一番下のclose


f:id:nodamushi:20180910232209p:plain:w320
 絶賛放置ング。終わったらリブート。


f:id:nodamushi:20180910232425p:plain:w320
 Enter

f:id:nodamushi:20180910232721p:plain:w320
 リブート終わって、とりあえずログインしたら、もうVirtual Boxは放置。

 Tera Termでログイン。なお、色々ぶつかるので、、仮想サーバーのポートフォワーディングは適当に設定してある。
f:id:nodamushi:20180910233117p:plain:w320
f:id:nodamushi:20180910233054p:plain:w320



Docker-Composeを入れる

 Docker自動で入れてくれるなら、Docker-Composeもデフォルトで入れておいて欲しいな!
 TestLinkのallin-oneをbitnami様々が公開してくれているのですが、docker-composeが必要なので入れます。

 インストールはここを参照して下さい。私は以下(version 1.22.0)で入れました。

sudo curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x  /usr/local/bin/docker-compose

 で、bitnamiのtestlinkを入れる。

mkdir testlink
cd testlink
curl -sSL https://raw.githubusercontent.com/bitnami/bitnami-docker-testlink/master/docker-compose.yml > docker-compose.yml


 docker-compose.ymlの設定を適当にします。私はとりあえず、TESTLINK_USERNAME,TESTLINK_PASSWORD,TESTLINK_LANGUAGEを追加で設定しておいたぐらいです。

 設定したら起動。

sudo docker-compose up -d

 で、ホストOS(Windows)からlocalhostにアクセスしてみる。(仮想OSの設定でポートフォワーディングしてあるので、localhost:2080)

f:id:nodamushi:20180911003953p:plain

 わーい、クッソ楽( ゚д゚)

 ちなみに、私が与えられた仮想サーバーはCPUが64bitなのに、OSがUbuntu14 32bitというイジメを受けた為、Dockerが使えず、手動で全部入れて、結構面倒だった。
 特に、MySQL5.7に手間取ったぜ…(TestLink 1.9.17ってMySQL5.6以上って書いてあるのに、動かなかったんだけどーーー!?)

AWK超簡単再入門

「この合成ログファイルから使用セルの数とサイズ求めるプログラム作れ」
「んー、各行にインスタンスパスとセル名とサイズとかがあるんすか。こんなん、プログラムじゃなくって、awkでいいじゃん」
「何でも良いから」
「あいよ」

 と、おもむろに端末を立ち上げて、awkと打ち込み数秒後

やっべ AWK忘れた


 たぶん、2年ぶりぐらいにawkの文字を打ち込んだんじゃないかな。クソニートだし、もうそろそろ痴呆が始まったかな。時間の流れは残酷だね

AWKとは

 一行一行を抜き出して、更にその行をセパレータ(デフォルトは空白)で分割して処理すると言うことを簡単に記述する処理系。
 CSVとか、何かの文字で区切られているテキストファイルを処理するのに向いている。

 処理系は幾つかあって、この記事ではgawk基準。

 大規模なプログラムも作れるらしいが、この記事はワンライナーで使うのに十分な程度しか解説しません。

自動定義される変数

 一行一行読み出した値や、分割されたデータは$0、$1といった変数に格納されます。

f:id:nodamushi:20180826151828p:plain

 

printとprintf

 出力する時はprintかprintf。print文は","(カンマ)区切りで変数を並べるか、" "(半角スペース)区切りで変数を並べる。カンマ区切りの場合は、出力結果にスペースが入る。
 ファイルに出力も出来る。

print "a" "b" "c"  # abcと出力
print "a","b","c"  # a b cと出力
print "hoge" > "hoge.txt" # hoge.txtにhogeを出力
print "moge" >> "hoge.txt" # hoge.txtに追加出力

printf "%05d, %f, %s\n",10,1.2,"hoge"  # printと同様にファイルにも書き出せる。


 

AWKスクリプトの構造

BEGIN            { AWKの初期化処理 }
END              { AWKの終了処理 }

                 {各行の処理}

/正規表現/       {正規表現にマッチした行の処理}
!/正規表現/      {正規表現にマッチしなかった行の処理}
$n ~ /正規表現/  {$nが正規表現にマッチした行の処理}
$n !~ /正規表現/ {$nが正規表現にマッチしなかった行の処理}

match($0,/正規表現/,m) { 正規表現にマッチした行の処理。m[i]でグループを取得出来る }

変数宣言と型の種類

 変数宣言はない。初めて使ったところから存在し始める。

 文字列と数値と連想配列がある。文字列と数値にほぼ区別はない。
 未定義の変数は0か空文字と見なされる
 

AWKスクリプトの例:出現回数を求めて、値の和を求める

 例として今回の最初の課題、数数えてサイズ求めろ、というのを書いてみましょう。

 セル名が最初に、合計を取りたいサイズが最後に書いてあるログフォーマット(,区切り)だと仮定します。

BEGIN{FS=","}
{count[$1]++; size[$1]+=$NF;}
END{ for(n in count) printf "%s:count = %d, size = %f\n",n,count[n],size[n] }

 うーん、簡単。BEGIN{FS=","}は -F ","というコマンド引数でも可


 今時AWKで巨大スクリプトを書くなんてことはないでしょうが、ワンライナーとしては今でも十分強力ですね。
 昔はsortに渡す為に使ったりしたっけなぁ。

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)とか読みにくい。内包表記なんてもっと読みにくい。Pandasが必要ない様な場面でも常にPandasを使えとでも言うのだろうか?
 言語的に糞とよく言われる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とか。そして、Type Hintを使っても、あるクラスのメソッド定義に、自分のクラスを使うことは出来ない。あほちゃうか?

class A:
    def hoge(self,a:A): だめ

 むろん、Pythonで大型のプログラムを書く人は、オブジェクト指向の型以外のメリットを十分に享受しているのだろう。
 だが残念だが、私はスクリプト的にしか使っていない。

 なのに、クラスのメソッドと、関数を使い分けさせられる。ついでにwithやらinやら?もう、勘弁してくれ。全部関数にしてくれ。あぁ、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の入力をすべて奪ってみる。(なお、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は今、神となったのだ(`・д´・ )