30日OS自作本12日目
12日目はタイマーです。
タイマーとは?その必要性は?
- タイマーがないと定期的に or 何秒後にXXするという処理を作りにくい
- タイマーがなくても一応実現できる。
- ただしCPUのクロックに依存したコードを書く必要がある
- 各命令や関数が何クロックで終わるのか注意する必要がある
- HLT命令のような終了するクロック数が分からない命令がそもそも使えなくなる
- HLT命令はCPUをHALT状態にして割り込みなどが発生するまで休ませる命令。
PIT
- PCでタイマーを使うにはPIT(Programmable Interval Timer)というハードを使う
- PITはIRQの0番に繋がっている
- PIT自身もクロックを持っている1,193,182Hz
- この本の内容だと周期は2Byte(65536)までしか設定できない。
- 正確に1秒を測れない
- 本の例では100Hzにするための周期として11932(0x2E9C)を設定:1193182 / 11932=99.9984914516≒100HZ
- EAX(32bit)とかを使えばできるのかな?
- Linuxのリポジトリを検索してみたところ1193182という値は使われている
- やはり本書に書かれていない何らかの方法で正確な1秒を測れる?
- この本の内容だと周期は2Byte(65536)までしか設定できない。
- あとはIDTやタイマーをシートのように資源として管理する構造体を作るだけなので省略
Beepを鳴らすプログラム(失敗)
このページ
http://oswiki.osask.jp/?(PIT)8254
を参考にビープを鳴らすプログラムを書いてみましたがうんともすんとも言わず。 前回(BIOSからメモリ容量を取得するアセンブリ)と違ってOSをぶっ壊すような自体にはならなかったが、音は鳴らない。QEMU側やCPUに何か追加の設定がいるのか、プログラムが間違っているかのどちらかだと思う
void beep(void) { io_out8(PIT_CTRL, 0x43); /*0x0a98=2712の周期を設定する(440Hz)*/ io_out8(PIT_CNT0, 0x98); io_out8(PIT_CNT0, 0x0a); beep_on(); return; }
;BeepをONにする。: IN(AL, 0x61); AL |= 0x03; AL &= 0x0f; OUT(0x61, AL);のつもり。 _beep_on: IN AL,0x61 OR AL,0x03 AND AL,0x0f OUT 0x61,AL
感想
本書のタイマーのプログラムを動かしていみましたが、1分で1秒以上ずれました。 原因は今の私にはわかりません。qemuがリアルタイムではないかもしれない、と思っていますが 「qemu realtime」などでググってもめぼしい情報はなし。
13日目もタイマーをやります
30日OS自作本11日目
11日目はついにWindowを作ります。
ウインドウ
ウインドウの作り方です。簡単にコメントで補足してみました。
struct SHEET *sht_win; unsigned char *buf_win; /*リソース確保*/ sht_win = sheet_alloc(shtctl); //シート buf_win = (unsigned char *) memman_alloc_4k(memman, 160 * 68); //メモリ確保 sheet_setbuf(sht_win, buf_win, 160, 68, -1); /* 透明色なし */ /*グラフィカルな部分を作るヘルパー関数*/ make_window8(buf_win, 160, 68, "window"); /*Windowに各文字の設定*/ putfonts8_asc(buf_win, 160, 24, 28, COL8_000000, "Welcome to"); putfonts8_asc(buf_win, 160, 24, 44, COL8_000000, " Haribote-OS!"); /*位置と高さを設定する*/ sheet_slide(sht_win, 80, 72);//位置 sheet_updown(sht_win, 2);//高さ
描画(refresh)の改善
ウインドウの描画時に意図しないチラツキが発生するので改善します。
- 全シートの書き換え
- 改善1:指定したシート以上を書き換え
- 改善2:指定したシート以上で重ならない部分のみを書き換え
GUIの描画について考えるときは書き換えを行なった高さのシート以外の扱いを注意する必要があります。書き換え対象より下の層は描画する必要はありません。 描画してしまうと「全シート書き換え」の時のように一番下のレイヤである背景がvram上に書かれてしまいます。 画面refresh時にすぐ上のウインドウに上書きされますが、一瞬だけ描画される時間ができるので、チラツキとして画面に現れてしまいます。
改善1で書き換え対象(今回はウインドウ)より下のレイヤは描画しなくなりましたが、上のレイヤ(今回はマウス)に重なったレイヤは描画する必要はありません(チラツキの原因)
改善2のためにmapという仕組みを導入しています。
Mapとは
Mapを使ってvramの各ピクセルはどのシートが写っているかを管理します。 vramと同等のサイズ(unsigned int(1B)で320 * 200)の配列で管理します
struct SHTCTL { unsigned char *vram, *map; int xsize, ysize, top; struct SHEET *sheets[MAX_SHEETS]; struct SHEET sheets0[MAX_SHEETS]; };
mapを導入することで
- チラツキの解消
- 描画範囲の更なる限定
が実現できましたが、メモリーがMAPのサイズ分((unsigned int(1B)で320 * 200)=64000B=64KB
)減っています。
画面に表示されているメモリ残量が64KB減っています。
free: 29216KB → 29152KB
感想
ウインドウが出てきたことによりOSっぽくなってきました。 しばらくGUIはお別れになるようです。day12ではタイマーを扱います。
GUIとオブジェクト指向
という訳で、最初にオブジェクト指向的なものに出会ったのが自宅で作ってたiOSのGUI(ボタン、ラベル)でした。 GUIはオブジェクト指向っぽくあって欲しい、という個人的な思いがあるので、この辺はOOPができる言語(Rust,C++)で進めている人たちは データモデリングが楽しいだろうなと思います。
- 作者: 川合秀実
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2006/03/01
- メディア: 単行本
- 購入: 36人 クリック: 735回
- この商品を含むブログ (299件) を見る
30日OS自作本10日目
10日目はGUIの改善を行いました。これにより、下のバーをマウスが破壊する現象がなくなりました。
day9までのGUIの課題
- マウスを描画する範囲に正方形の影が出来てダサい
- 再描画を明示しない部分はマウスカーソルが通ったあと描画内容が消える
などの課題がありましが、10日目の内容で以下の機能を付け足します。
- 透明
- 画面要素のレイヤー(高さ)
この2つを組み合わせたシートという概念で実現します。
struct SHEET { unsigned char *buf; int bxsize, bysize, vx0, vy0, col_inv, height, flags; };
- bxsize,bysize = シートのサイズ
- vx0,vy0 = シートの座標
- col_inv = 透明色番号
- flags = 制御用の情報
という感じです。ちょっと変数名がわかりにくい気がしますがこの辺は慣れでしょう。
次にシートを管理する構造体です
#define MAX_SHEETS 256 struct SHTCTL { unsigned char *vram; int xsize, ysize, top; struct SHEET *sheets[MAX_SHEETS]; struct SHEET sheets0[MAX_SHEETS]; };
- vram = vramのアドレス
- xsize,ysize = vramの画面サイズ
こちらはBOOTINFOに入っているものをそのまま保持して使います。
気になるのがSHEETの
- ポインタの配列(sheets)
- 構造体の配列(sheets0)
の2種があることだと思いますが、 これはsheetsは高さでソートしたシートのオブジェクトの参照を持たせ、sheets0は特に高さの順番を意識せずに使うようです。
画面再描画の方法
マウスの情報を表示している画面2行目を書き換える処理です。
//sの内容は`[lcr 0000 0000]`こんな感じ。 sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y); -(略)- //sを背景(buf_backに書き込む) putfonts8_asc(buf_back, binfo->scrnx, 32, 16, COL8_FFFFFF, s); //sを書き込んだ後の背景(buf_back)を再描画 sheet_refresh(shtctl, sht_back, 32, 16, 32 + 15 * 8, 32);
[lcr %4d %4d]
%4dは幅を揃える記述になるので最終的に以下のような形式になります[lcr 0000 0000]
前日のやった通り、文字は16×8ピクセルで表現しているため、再描画が必要となる範囲は縦16ピクセル,横120ピクセル(8ピクセル×15文字)となります。
結果的に上記のコードは15文字 × 8ピクセル分の横幅、16文字文の縦幅を書き換えるようなコードになります。
画面描画の改善
思いつきで作った最初のバージョン(harib07b)はマウスを動かすたびに画面全体が再描画しているので、この部分の処理を段階的に改善します またまた動画を撮ってみました。
- 1.画面全体の再描画(harib07b)
- 2.シートの指定範囲のみ書き換え(harib07c)
- 3.描画時のfor文が指定範囲内のみになるよう改善(harib07d)
2.ではシートの再描画位置をせっかく指定しているのに、描画時にシートの全範囲に、描画必要の有無の条件判定を行なっているので無駄。 ということで3で描画時の範囲指定を指定範囲のみになるようリファクタリングしたという改善を行なっている。
相対位置と絶対位置の変換などをなんどもやっているため ここのコードが少し読みにくく、ちょっと混乱しました。
- bx,byの先頭のbは多分bufferを表していて、シート上の座標になります
- vx,vyの先頭のvはvramを表していて、VRAM上の座標になります
- bxn,bynはシート状のどの地点書き換えるかを外部から与える変数になります。(起点はvramではなくシートです)
vxが絶対座標、bxが相対座標のようになっている、と気をつけて読めばそこまで難しいソースではなかったのかもしれません。
ちなみに動画をご覧いただければわかりますが、パフォーマンスの改善前後の画面描画がそこまで変わらないので、いまいちこの恩恵を感じられません。 本書発売時のハードウェアスペックだと改善の実感を得られたのでしょうか。ハードウェアの進化ってすげーってことなんでしょうか(笑)
感想
そういえば、大学時代iOSのアプリを作っていたのですが*1、iOSでは画面の原点が左上にあることを思い出しました。これが何かと食い違ってて、凄く違和感を持ってやってた思い出があります。hariboteOSも原点左上でしたね。こういうのって標準的な考え方とかあるのかな。
https://qiita.com/wheel/items/a26f7cb1464aff60940a
*1:リリースはしてない
「世界一シンプルで科学的に証明された究極の食事」を読んだ
面白かったので買って1日で全部読めた。ホットなうちに感想を書きました。 念のためですが私は医療や食の専門家ではないので書いてることは鵜呑みにしないでください。
本書の特徴
色々な健康法へのアンチテーゼ
間違った健康情報がはびこる理由
医学部は食事や栄養に関することをあまり習わないため食事に関する知識を持っている医者は少ない。逆に食事・栄養の知識を持つ栄養士は統計学や疫学の知識を持っている人が少ない。と言うのが著者の主張。「健康的な食事」という情報を発信するスキルセットを持っている人材は少ないらしい。
そのような情報が、各団体のロビー活動やマーケティング、マスメディアによるアクセス稼ぎのための過剰な表現などでさらに歪められた形で発散される。
書き方が誠実
各食品に関して「体に悪い」や「健康によい」で済ませている箇所が少ない。
- XXガンのリスクがXX%上昇する。
- XXの病気のリスクは下がるがガンへの影響は少ない。
など病気やガンへのリスクを細かく説明している。 ガン予防に効果的だけど、普通の病気の改善には使えなかったり、 健常者にはリスク増加の影響があるが、病人には症状を緩和するなど。
銀の弾丸的な食品紹介がなく、非常に誠実的な印象を受けた。
新しく知ったこと
乳製品の取りすぎは前立腺癌リスクを上昇させる
牛乳1~2杯、ヨーグルト170g~450gが上限とのこと。 日本では様々な政治的圧力により、「できるだけ摂取しないように」という主張が難しいらしく「1日XXくらい摂取しましょう」という表記になってしまうとのこと。乳製品の健康リスクは今まで乳糖不耐症がらみのことしか聞いたことがなかった。
背が高くなるために牛乳をたくさん飲みましょう、みたいなのはよく聞きますが有害情報ですね。*1
野菜ジュースと健康効果
- フルーツ:体に良い
- フルーツジュース:体に悪い
- 野菜:体に良い
- 野菜ジュース:体に良いか不明
フルーツが体に良いことに反して、フルーツジュースが体に悪いことはすでに研究されているが、 野菜が体に良いことに対して野菜ジュースが体にどのように影響を与えるかはまだ信頼できるエビデンスがないとのこと。
卵一日に何個も食べても健康被害はない説は間違い
卵のコレステロールは消費しても血中コレステロールにそれほど影響しない、という話が一般的に広まり、 卵は1日1個までから何個でも食べてもOKという風潮があるが、本書によると
血液検査のスコアに影響はないが、糖尿病リスクが上昇するとのこと。根拠とされている論文の概要を読んだが。 卵消費は心臓病のリスクとは関連していないが糖尿病発症リスクの増加とは関係しているとのこと。 卵は安くてタンパク質などの栄養が豊富で調理も簡単なので重宝していましたが、控える必要がありそうです。
- 根拠とされている論文:https://www.ncbi.nlm.nih.gov/pubmed/23676423
著者の結絵検査の結果よりも病気のリスクを気にしろという主張も納得が行きます。 ちなみに卵は一週間に6個までが良いとのこと。
疑問点
ビタミンサプリが体に悪い理由がよくわからない
カロリーメイトやビタミン野菜みたいな、1日の推奨ビタミンに合うように栄養を添加している食品*2が好きなのでこの情報はちょっとショックでした。 ですが、この節は体に悪い理由がサプリによる摂取(質的な問題)が原因なのか、耐容上限Overによるもの(量的な問題)なのかが不明でした。
例えばβカロチンのサプリの件は、そもそもビタミンAの類は耐容上限が定められているのでサプリで取らなくても人参を食べまくれば体に悪いと思います。
根拠としている論文を見に行ったところ、
https://academic.oup.com/jnci/article/96/23/1743/2521077
毎日βカロチンを30 mgを摂取で調査したらしいがこれは耐用上限に対してどれくらいの量かと言うと
30mg = 30000 μg 30000 /12 = 2500㎍RAE
参考:https://www.tyojyu.or.jp/net/kenkou-tyoju/eiyouso/vitamin-a.html
となり、実験で摂取したβカロチンだけで成人のレチノール活性当量の耐容上限(2,700㎍RAE)にかなり近い数値になっている。*3 βカロチンをサプリで取ることが危険、ではなくβカロチンをサプリで耐容上限近く摂取すると危険ということしか言えないと思うのだが。
このトピックはもう少し論文を読むなどして勉強する必要がありそうです。
サプリが良くない根拠として挙げられている他の論文
- ★6ガン予防効果なし&膀胱癌のリスク増加:https://www.ncbi.nlm.nih.gov/pubmed/21981610
- ★7肺がん胃がんリスクが増える:https://www.ncbi.nlm.nih.gov/pubmed/19876916
- ★8死亡率が7%上がる:https://www.ncbi.nlm.nih.gov/pubmed/12814711
- ★9飲酒と合わせると脳出血リスク増:https://www.ncbi.nlm.nih.gov/pubmed/11030804
グルテンフリー
セリアック病でない健常者が摂取することによる効果を裏付けた研究がないことから、 批判的なスタンスを取っているが、
世の中には、グルテンを摂取することで頭がぼーっとする、もしくはお腹がやたらと春などの理由でグルテンを控える人もおり、それが目的であればグルテンを減らすのも一理あるかもしれない
との一文が入っているが、著者的には健康的=病気のリスクが減るで、日々の快調とかはまた別の話なのでしょうか? いずれにせよグルテンフリーで快調になるなら、そっち方面の研究も気になります。(まだ存在しない?)
バターコーヒーなども信頼できる研究がないことを理由にバッサリ切り捨てていますが、いずれ研究する人が出て何らかの結論が出るのでしょう。
成分より食品
食品を成分の集合体としてのみ捉える考え方を栄養至上主義(ニュートリショニズム)と呼ぶ
私もスペック厨的なところがあったのですが、本書では明確に批判するスタンスを取っている。 割と世間一般的に誤った考え方のようですが、なぜ成分信仰がダメのかよくわかりませんでした。
いかのリンクの
ニュートリショニズムは、個々の栄養素について言及されることが多いが、様々な栄養素の相互作用が及ぼす人体への影響についての考察に欠けていることが多い
http://ebis.nutritio.net/hn/modules/pico/index.php?content_id=487ebis.nutritio.net
この辺が理由なのでしょうか。
含有成分的に、最強の野菜はモロヘイヤ*4だと思っているのですが、これはビタミンやらミネラルの成分だけを見て到達した結論なので、モロヘイヤは最強ではないのかもしれません。
- 作者: 津川友介
- 出版社/メーカー: 東洋経済新報社
- 発売日: 2018/04/13
- メディア: 単行本
- この商品を含むブログ (2件) を見る
総合的にオススメです
30日OS自作本9日目
30日OS自作本9日目
9日はメモリ管理です。
BIOSからメモリの情報を取得する方法(失敗)
- https://stackoverflow.com/questions/39024754/how-to-get-ram-size-bootloader
- https://wiki.osdev.org/Detecting_Memory_(x86)#Manual_Probing
- http://softwaretechnique.jp/OS_Development/Tips/Bios_Services/General_Services/bigmemory_services_E820.html
- http://softwaretechnique.jp/OS_Development/kernel_development05.html
この辺を参考にINT 0x15命令を適切なパラメータで呼べばBIOSからCX,DXレジスタにメモリの情報っぽいものを得れることがわかりました。
Typical Output: AX = CX = extended memory between 1M and 16M, in K (max 3C00h = 15MB) BX = DX = extended memory above 16M, in 64K blocks
naskだと動かない命令がいくつかあったので書き直してみました。
_memoryfromBIOS: ;EDXとECXを退避 PUSH EDX PUSH ECX ;ECXとEDXを初期化 MOV ECX, 0x0000 MOV EDX, 0x0000 ;パラメータをAXに設定してINT 0x15命令を呼ぶ MOV AX, 0xDA88 INT 0x15 ;メモリの情報をAX,BXに入れ戻り値とする? MOV AX, CX MOV BX, DX ;退避したEDXとECXを元に戻す POP ECX POP EDX RET
あれ?AXに設定すれば戻り値になるのはわかるけどBXに設定したのはなんでだろう?
まぁとりあえず外部から呼べるように.nasにGLOBAL修飾子を足します
GLOBAL _memoryfromBIOS
bootpack.hに関数の定義を書いて*1
void memoryfromBIOS(void);
これをmain関数から呼びます
多分動くと思ったのですが、INT 0x15を呼び出すとそのあとのプログラムの動きが止まってしまうようです。*2 これは多分、
- INT 0x15を呼び出すための準備(設定のようなもの)がきちんとできていない
- INT 0x15を読んだ後にすべき後処理がきちんとできていない
が原因だと思うのですが、調べてもよくわからないのでこの辺で打ち切ります。(あまりこの辺に時間をかけても美味しくない)
BIOSの機能を使わずOS側のPGで使用可能メモリを調査する
BIOSは使わずにプログラムからメモリの書き込み可否を操作していきます。*3
- 1.固定データ書き込み
- 2.書き込みデータを反転
- 3.再度読み込み
で1で書き込んだデータが3で読み込んだデータの反転した値になっているか、をチェックします。*4
unsigned int i, *p, old, pat0 = 0xaa55aa55, pat1 = 0x55aa55aa; for (i = start; i <= end; i += 0x1000) { p = (unsigned int *) (i + 0xffc); old = *p; /* いじる前の値を覚えておく */ *p = pat0; /* ためしに書いてみる */ *p ^= 0xffffffff; /* そしてそれを反転してみる */ if (*p != pat1) { /* 反転結果になったか? */ not_memory: *p = old; break; } *p ^= 0xffffffff; /* もう一度反転してみる */ if (*p != pat0) { /* 元に戻ったか? */ goto not_memory; } *p = old; /* いじった値を元に戻す */ }
結局上記Cのコードはコンパイル時の最適の影響でうまく動かなくなるためアセンブリで同じ内容のコードを書くことになりました。
キャッシュ禁止の設定方法
上記のようなメモリを触りに行くことが目的のコードでキャッシュを使われると困るので、キャッシュを禁止するように設定しました。
CR0レジスタに0x60000000
を書き込みます
これは多分、先頭2bit,3bit目を1にすると設定できる、とうい風なことだと思います(0x6
=0b0110
)
486
この章で486の話題が出てきましたが、以前からこの本を読もうと思っています。
32ビットコンピュータをやさしく語る はじめて読む486 (アスキー書籍)
- 作者: 蒲地輝尚
- 出版社/メーカー: KADOKAWA / アスキー・メディアワークス
- 発売日: 2014/10/21
- メディア: Kindle版
- この商品を含むブログを見る
電子版があったり、ちょっと前にruiさんがtwitterで進めていたりしたので、ちょくちょく立ち読みしていました。
#tcfm 7みたいにメモリとかカーネルモードとかよくわからないという人には「はじめて読む486」が結構おすすめかも。相当古い本だけどここらへんはほとんど変わっていないし、読みやすくて今でも入門にとてもよい本だと思うなぁ。 https://t.co/N9gQoKJjXw
— Rui Ueyama (@rui314) 2018年2月28日
マウス(PS2)の割り込みについて
8章終了時にマウスが動いている仕組みがイメージできなかったので 5〜8章で割り込みついて書いてるところを自分なりにまとめました。
- PICの設定の初期化
- IDTの設定
- IDTに割り込みハンドラを登録する
- 割り込みの発生、割り込みハンドラの呼び出し
- 割り込みハンドラ(アセンブリ)内でC言語の関数を呼ぶ
- C言語から再びアセンブリ経由でPS2のデータポートを参照する
PICの設定の初期化
int.cのinit_pic
io_out8(PIC1_ICW2, 0x28 ); /* IRQ8-15は、INT28-2fで受ける */
- IRQ0~15をINT0x20~2fで受け取るように指定。
- 0x00~0x0fはcpuが予約しているセキュリティ的な割込がすでにあるためここを回避
IDTの設定
- 初期化、マウス0x2c、キーボード0x21の登録を行なっています。
- 0x27の登録は起動バグ対策のようです。(p126)*1
struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) ADR_IDT;//補足:ADR_IDT=0x0026f800 /* IDTの初期化 */ for (i = 0; i <= LIMIT_IDT / 8; i++) { set_gatedesc(idt + i, 0, 0, 0); } load_idtr(LIMIT_IDT, ADR_IDT); /* IDTの設定 */ set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);//キーボード set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);//バグ対策 set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);//マスク
IDTに割り込みハンドラを登録する
set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
割り込みの発生、割り込みハンドラの呼び出し
割り込みが発生する。 INT XXの命令が発生しIDTに登録している該当の割り込みハンドラが呼ばれる
- PIC 12(マウス)の割り込みはもちろん設定した通りINT 0x2Cで受け取る
- 登録した以下の割り込みハンドラが呼ばれる
_asm_inthandler2c:
PUSH ES
PUSH DS
PUSHAD
MOV EAX,ESP
PUSH EAX
MOV AX,SS
MOV DS,AX
MOV ES,AX
CALL _inthandler2c
POP EAX
POPAD
POP DS
POP ES
IRETD
割り込みハンドラ(アセンブリ)内でC言語の関数を呼ぶ
CALL _inthandler2c
によりC言語で用意したinthandler2c
が呼ばれます。
void inthandler2c(int *esp) /* PS/2マウスからの割り込み */ { unsigned char data; io_out8(PIC1_OCW2, 0x64); /* IRQ-12受付完了をPIC1に通知 */ io_out8(PIC0_OCW2, 0x62); /* IRQ-02受付完了をPIC0に通知 */ data = io_in8(PORT_KEYDAT);//補足:PORT_KEYDAT=0x0060 fifo8_put(&mousefifo, data); return; }
C言語から再びアセンブリ経由でPS2のデータポートを参照する
io_in8(PORT_KEYDAT)
で60番ポートに届いたデータを取りに行きます
_io_in8: ; int io_in8(int port); MOV EDX,[ESP+4] ; port MOV EAX,0 IN AL,DX RET
こちらの0x60がPS2のデータポートと書かれています https://wiki.osdev.org/%228042%22_PS/2_Controller
The Data Port (IO Port 0x60) is used for reading data that was received from a PS/2 device or from the PS/2 controller itself, and writing data to a PS/2 device or to the PS/2 controller itself.
データポートが使えるようになれば、こちらに0xfa
が届きます。
そちらが確認できれば0xfa
は読み捨てて以後マウスからのデータを受信します。
マウスからのデータは3バイト1セット(1:クリック、2:横移動のデータ,3:縦移動のデータ)だと思うのですが、 マウスを動かしている際になぜか1バイト目が反応しているのが気になりました。
*1:これを登録しないと起動に失敗するらしい。
Java読書会BOF「Kotlinイン・アクション」を読む会 第5回に参加
7,8,9章あたりを読みました。 ジェネリクス、ラムダなどJavaだとちょっと不便なところをKotlinがどう解決しているか?というのが見所だと思います。
- コード
- 分割宣言
- 移譲の例
- withIndexを使ったfor文
- デザインパターンと関数型プログラミング
- インライン展開
- withLock関数でtry-finally節を省略できる
- 拡張関数はどこに?
- 型パラメータに対する複数の制約の指定
- 実行時に消去されない方引数(reified)
- 9.3.2 クラス、型、サブタイプ
コード
自分が朗読するパート以外は今回からコードを動かしながら拝聴しました。 https://github.com/Kotlin/kotlin-in-action
分割宣言
複数の戻り値を持つ関数
分割代入を利用する
class Sums(val sum: Int, val count: Int){ //componentNで operator fun component1() = sum operator fun component2() =count } data class Sums2(val sum: Int, val count:Int){ } /** * 合計と要素数を返す関数 */ fun multiRet(arg:List<Int>):Sums{ return Sums(arg.sum(),arg.count()); //return Sums2(arg.sum(),arg.count()); } fun main(args: Array<String>) { val numbers = listOf(1,2,3,4) val (a,b)=multiRet(numbers) //上記の一文は以下のように解釈される //val a = multiRet(numbers).component1() //val b = multiRet(numbers).component2() println(a) println(b) }
- 宣言時に複数のcomponentN()で実装されたメソッドの戻り値を受け取れる
- データクラスでは自動でcomponentN関数が実装される
- 上記コードはコメントアウトされているデータクラスのsumに書き換えても動く
- Qiitaに昔に書いた逆コンパイルしたデータクラスにもcomponentN関数が入っていることが確認できる
public final String component1() { return name; } public final int component2() { return age; }
これを書いていた当時はcomponent1ってなんだ?と思っていたが腑に落ちた。
- connecting dotsじゃないけど学んでることが繋がると嬉しい。
分割宣言とループとの組み合わせ
コレクションの各クラスには独自にcomponentN関数が実装されており、その恩恵を受けるような書き方ができる。
- Mapのentryにはcomponent1,2がそれぞれkey,valueに対応付けられている。
fun printEntries(map:Map<String,String>){ for((key,value) in map){ println("$key -> $value") } /* //mapのentryのにはcomponentNがkey,valueに実装されており、 //上記のfor文の生身はこんな感じに解釈される for (entry in map.entries){ val key = entry.key val value = entry.value println("$key -> $value") } */ } fun main(args: Array<String>) { val map = mapOf("Oracle" to "Java", "JB" to "Kotlin") printEntries(map) /* 結果 Oracle -> Java JB -> Kotlin */ }
移譲の例
ORマッパーのようなものを移譲を利用して簡単に作成できる
- オブザーバーパターンを活用
- 監視した値が変わったら更新
- これはKotlinでなくてもORマッパーはObserverパターンを利用しているのかな?
withIndexを使ったfor文
分割代入を利用して拡張for文内でindexを使えるようにする
Javaだとfor-each文のなかでindexを扱えないので、 for外のスコープに定義した変数をループの中でインクリメントしてごまかしたりしますが、 Kotlinだと次のように書くと拡張for文内でもindexを使えます
val numbers = listOf(21,19,18) for((index,value) in numbers.withIndex()){ println("$index : $value") }
出力
0 : 21 1 : 19 2 : 18
withIndex()がDataクラスであるIndexedValue(index: Int, value: T)
のイテレータを返しているようです。こちらはデータクラスなので分割代入に対応しています。
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/with-index.html
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-indexed-value/index.html
デザインパターンと関数型プログラミング
276pより
- ストラテジーパターンは高階関数で実現できる。
- 振る舞い(ストラテジー)を外部からラムダとして渡す
design patternでstack overflowを検索すると 最上位に関数型プログラミングはデザインパターンを置き換える?と行った内容の議論が出てくることを思い出しました。
https://stackoverflow.com/questions/327955/does-functional-programming-replace-gof-design-patterns
インライン展開
p279あたり
https://ja.wikipedia.org/wiki/インライン展開
- intelliJはよく、インライン化しろ!と命令してくるらしい。(経験者談)
- インラインはテンプレートではなくマクロっぽい。
withLock関数でtry-finally節を省略できる
import java.util.concurrent.locks.Lock
をかなりシンプルに扱う機能のです
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
これ相当のプログラムがKotlinでは以下のようにかけます
val l:lock = ... l.withLock{ //ロックで保護したリソースにアクセス }
かなり強力ではないでしょうか?
javaのtry-with-resources文
- Kotlinにない。 = 代替としてuse関数を使う
import java.io.BufferedReader import java.io.FileReader import java.io.File fun readFirstLineFromFile(path: String): String { BufferedReader(FileReader(path)).use { br -> return br.readLine() } }
Closableインターフケースが実装されている必要があります。try-with-resourcesのAutoCloseable相当なのでしょうか?
拡張関数はどこに?
探すの大変そう。
- C#でも同じ苦労がある
- 知ってたら使えるが、知らなかったら永久に存在がわからない。
- ラッパークラスを作るより、拡張関数の方が良いという意見もある。
型パラメータに対する複数の制約の指定
<>の中で一つだけ上限境界(upper bound)*1を指定できます。 複数の型パラメータをもたせたい場合はwhereを使います JavaでおんなじことやろうとするinstanceOfで型検査する必要があります(多分この方法しかないと思う((単数だとsuer ,extendsなどでできた気がするが、複数はできなかったと思う)))。
fun <T> ensureTrailingPeriod(seq: T) where T : CharSequence, T : Appendable { if (!seq.endsWith('.')) { seq.append('.') } } fun main(args: Array<String>) { val helloWorld = StringBuilder("Hello World") ensureTrailingPeriod(helloWorld) println(helloWorld) }
実行時に消去されない方引数(reified)
p308ページあたり
9.3.2 クラス、型、サブタイプ
これはEffective Javaにも出てくる話ですね。ほったらかしにしている3rd(英語版)があるので、後日復習して書こうと思います。
- 共変
- 不変
- 反変
- 作者: Dmitry Jemerov,Svetlana Isakova
- 出版社/メーカー: マイナビ出版
- 発売日: 2017/10/31
- メディア: Kindle版
- この商品を含むブログを見る