「ウクライナの大統領は、自国民の死者を増やさないために徹底抗戦を避けるべき」、という言説について。
「ウクライナの大統領は、自国民の死者を増やさないために徹底抗戦を避けるべき」、という言説について。
どうやらそのような言説をいう人がいるらしいので、すこし考えてみる。
ウクライナは、自国民を守るためにどこかで折り合いをつける必要はあるにせよ、「命より大切なものはない、自国民の死者が増えているのだから降伏すべき」というシンプルな論理で判断できるものではないと思う。
これほど明確に、主権国家を力でねじ伏せているのだから、ロシアの停戦条件を丸のみしてしまえば、この先のウクライナ国民にどのような人生が待っているのかわからない。
ロシアのウクライナ侵攻の大義名分の一つに「ウクライナ国内にいる親ロシアは住民を守る」というものがある。
いま、ロシアがしていることは「新ロシア派の住民を守る」為に、「その他大勢のウクライナ人を殺している」のである。
このような行動原理のロシアに降伏することは、目先の命は助かったとしても、その後元通りのウクライナが回復することが保証されるとは考えられない。
もちろん「全員が総玉砕という極論」と、「人命優先で今すぐ無条件降伏するという極論」の間のどこかに落としどころはあるのだろう。
しかしこれは、「今戦って死ぬこと」と「将来、理不尽な支配によって死ぬ」ことのバランスの問題であって「自国民の死者が増えている」という単純な理由で、「即座に徹底抗戦をやめる」と判断できるものではないと思う。
ウクライナの人々は、当然ウクライナの人命は大切だと思っている。
その大切な人命を失いながら、ウクライナが自分たちの国らしさを保ち、希望を持って住むに値する国であるために戦っているハズである。
なので、「ウクライナの大統領は、自国民の死者を増やさないために徹底抗戦を避けるべき」、という言説は私が思うに単純に過ぎる。ウクライナの大統領も、ウクライナ国民もその程度の理屈は承知の上でなお、戦うことを選んでいると思う。
「降伏すべきではない、というあなたは、ではウクライナの人に戦えというのか?」という言説について
これもまた単純な言説だと私は思う。
「降伏すべき」「戦うべき」は、どういう立場で論じているのか、という点で大きく異なる。
西側諸国に住む国民の打算を論じる立場としては、私は「戦うべき」であると思う。
ロシアの無法行為は許しがたいので、誰もこの戦争には参戦はしないけど、僕らの価値観を守るために「戦うべき」である。
これはかなり酷い言い方だけど、ゼレンスキー大統領自身もこのような構図は認識していて、アメリカ議会での演説でも「きょう、ウクライナ人はウクライナを守っているだけでなく、未来という名のもとに西欧の価値観と世界のために命をかけて戦っています。」と言っている。
(Today, the Ukrainian people are defending not only Ukraine, we are fighting for the values of Europe and the world, sacrificing our lives in the name of the future.)
ウクライナ人の境遇を思って声をかける一人の人間の立場としては、そもそも「降伏すべき」「戦うべき」のどちらも言うべきではない。
外国人の立場からはそれをいうことはできない
ウクライナ人が死ぬこともつらいし、無条件に降伏することもつらい。
この決断のバランスを考えられるのは、ウクライナの人たちであって、外国人が安全な場所から
ウクライナの人に向かって「戦うべきだ」ともいえないし、無責任に「降伏すべき」ともいえない。
上記2つの立場があると私は思うのだけど、
ウクライナの人を応援したいという気持ちの中には、一見後者の立場から出ているように思えても、いくらか前者の立場からの思いも含まれているのではないかと自覚している。
本当に「命より大切なものはない、自国民の死者が増えているのだから降伏すべき」のようなことを言っている人がいるのか
Twitterで検索してみると、いくらか見つかった。
・「時間の経過による状況の変化がある」ことを無視したもの
「単純な軍事力ではロシアに勝てるわけがないのだから、はやく降伏したほうがいい」というのは、これにあたると思う。
ウクライナは時間と人命を費やして、状況の変化を待っているのであって、単純な殴り合いで勝てるとは思っていない。
・「降伏後に、ロシアがウクライナ人の人権を回復する」と仮定したもの
「命より大切なものはない。子供の未来が大事。」「ひとまず逃げて、10年後に戻ってくればいい。」というのは、これにあたると思う。
こういう意見は、想像力が欠けていると思う。
親ロシア派の住民を守るという大義名分のもとに、これほどウクライナ人の命を軽視しているのだから、降伏したあとにどのように遇されるかを想像したほうがよい。
日本人にわかりやすい例えを考えてみる
北朝鮮が攻めてきたとしましょう。
戦わなければ、日本が北朝鮮の一部になり、将軍様に忠誠を誓わなければ生きていけない国になります。 となれば、さすがに戦わずに北朝鮮の一部になる選択をする人はいないのではないでしょうか?
北朝鮮が攻めてくることは実際考えづらいとは思いますが、あえて日本よりも軍事力が劣る国を選んでみました。 勝てそうな国ならば、失われる権利と勝算を比較して、戦う選択肢が浮かんだ人もいると思います。
これが北朝鮮ではなく、中国だったらどうでしょうか?
戦っても勝てないと思う人もいるでしょう。
誰しも、自身の権利や未来のために戦うことを選択する可能性・意思を秘めていると思います。
そして、失われる権利と勝算を比較する計算を働かせると思います。
ウクライナ侵攻における「政治とスポーツは別」という言説について
パラリンピックでのロシア・ベラルーシ選手の処遇について
ロシアのスポーツ選手がパラリンピックに出場できないなど、不利な処遇を受けている。これについて、「政治とスポーツは別なのだ。だから、スポーツ選手の扱いは平常時通りであるべき。」という意見がある。
果たして「政治とスポーツは別」なのか、少し考えてみたい。
五輪について
私はウクライナ侵攻においてはロシアのスポーツ選手に不利な措置が取ることは必要だと考える。
少なくとも2022北京五輪について、ロシアの選手が不利な扱いを受けることは当然と考える。
五輪休戦協定違反の常連国であるロシアには、少なくとも五輪に関しては「政治とスポーツは別」という論理を持ち出すのはおかしい。
五輪以外のスポーツについて
2022北京五輪以外のスポーツについては、議論の余地はあるかもしれないが、
やはり、私は五輪以外のスポーツにおいても、特に国際的な試合に関して、ロシアのスポーツ選手に不利な処遇を与えることは必要だと思う。
「人道はスポーツに優先する」
「政治とスポーツが別」ではあるが、「人道はスポーツに優先する」ともいえる。
今起きている戦争が、多少なりともお互いの主張のぶつかり合い、正義や憎悪のぶつかり合い、というある種の公平さがあるならば
戦時中でも「政治とスポーツは別」という論理もあるかもしれない。
しかし、今回の戦争は
・軍事大国であるロシアが、他の主権国家を侵略するという構図。
・かつその軍事大国は常任理事国である。
・その目的が「ウクライナの非武装・中立化」という支離滅裂なモノであること。(ウクライナ規模の国で非武装というのはありえない。)
・ウクライナ側は開戦の意思はなく、ロシアとの戦争を避けようとしていた。
というものである。
しかし、常任理事国が拒否権を行使して他国を侵略すれば、止められる方法はない。
このような状況で、西側諸国がロシアのウクライナ侵攻をとめるには、ロシアに対して考えうる様々な方法で
圧力をかけていくしか方法は残されていないように思われる。
この状況下でロシアのスポーツ選手が「政治とスポーツは別」という論理を持ち出すのは、バランスがおかしいと言わざるを得ない。
ロシア選手個人に「ウクライナ侵攻への反対」を求めるのは・・・
選手個人側には、ロシアの政権批判になるような事を口にしたくない、という事情があるとは思うので、選手個人に「ウクライナ侵攻への反対」や「プーチン批判」を求めるのは、安全な場にいる外国人がすべきことではないと思う。
ウクライナ侵攻
ウクライナ侵攻
ロシアが世界経済の真っ当な一員になったと自分に言い聞かせて、ビジネス優先でやってきたわけだろうけど
ここにきて、改めてロシアという国の指導者層がどのような人たちなのかを思い出すことになった。
NATOかアメリカの介入がなければ・・・・
ウクライナは頑張って抵抗しているが、NATOが介入しない限り、かならずどこかでロシアとの停戦に(ウクライナの不利な条件で)合意することになるだろう。
ウクライナは越境してくるロシア軍を叩いているだけで、ロシア本土を攻撃する能力はないのだから、長期化すればもはや戦争とも言えない一方的な虐殺になる可能性すらある。
そして停戦合意の条件は、ウクライナにとってかなり譲歩したものになるだろう。
(あくまでも、NATOの軍事介入がなければ、という話。)
ロシアの利益になる終わり方をさせたくない
一方、ロシアの利になるような停戦条件で、この侵攻が終結することは
ウクライナだけでなく、NATO諸国にとっても受容しがたいものだろう。
ウクライナ侵攻でロシアが利を得ることが許されるなら、次はジョージアやモルドバなどにも同じことをするだろうし
中国が周辺諸国の領土を侵食することのハードルもグッと下がるだろう。
国連での常任理事国の力関係というのは冷戦終結後、長らく西側が有利な状態が続いてきた、なんだかんだいって西側諸国の肩を持つ人の多い場である。そういう中で、ロシアや中国のような西側諸国とは大きく価値観の違う2大国が、ますます幅を利かせるような結果になってしまうのは、西側諸国には受け入れがたい。今回のウクライナ侵攻の収支がロシアにとってプラスになるような終わり方にはしたくないはずである。
ウクライナは耐えてチャンスを待っている
ウクライナにとっての頼みは、ロシアの攻撃に耐えて耐え抜いて、西側諸国の軍事介入を引き出す、というところにある。
ロシアとウクライナが戦い続ければ、少なくとも戦闘行為そのものでは最終的にはウクライナが負けるというのは、ウクライナ側もわかっていると思われる。
それが分かったうえで、ロシアの攻撃に耐えて耐え抜いているのは
政治的には、耐え続ければ、西側諸国が介入せざるを得ない事態に引き込めるハズ、という狙いがある。
国民心情的には、ロシアの政治体制を全く信頼しておらず、ロシアの属国として生きる人生に全く希望を見出せない、というところだろう。
西側諸国にとっては、心情的にはウクライナを救ってあげたいところだと思う。
しかし、打算的な視点でいうと、以下のような都合のいいことを理想としているだろう。
1.ウクライナが音を上げる寸前まで頑張らせて、ロシアを消耗させたい。
2.戦うのは嫌なので、ロシアが早く音を上げてくれると嬉しい
3.ロシアが音を上げたのを見ても直ぐに許さないよ。じっくり灸を据えるよ。
ルールを破る、ということにかけてはロシアや中国のような全体主義国家は強い。
さて西側諸国は、停戦後もロシアに厳しい制裁を続けるために結束しつづけられるだろうか。
ロシアにエネルギーを依存してきた西欧が、停戦後のウクライナにどのような政権が誕生しても
ロシアへの経済制裁を続けられるものだろうか。
ロシアは西側諸国の制裁によって国民が困窮しても意に介さないだろう。
一方、西側諸国は困窮することに慣れていないし、西側の為政者は政権を維持する支持率を得るために、あれこれ努力する必要がある。
ロシアのように国民に困窮を強いることはできない。
西側諸国市民が多少なりとも困窮を受け入れつつ、制裁を継続し続けられるならよいが
「あらゆる物価が高くなったが、安全保障と西側諸国の結束のために我慢しよう」
と、為政者はどれほど市民を納得させ続けられるだろうか。
この点は現時点では西側諸国市民は、この負担を受け入れようとする気運があるようだ。しかし、影響が長引けば、西側諸国の結束に将来ヒビを入れることになると思う。
今のところ、西側諸国の結束は思ったより強い
当初、ロシアが予想していたより西側諸国の結束はかたいように見える。
これはロシア指導者層の価値観が、西側諸国の価値観とかけ離れていることを思い知らされたことによるものと思う。
しかし、ウクライナに武器を融通しつつも、NATOは飛行禁止区域の設定はしない、という点でまだ腰が引けている。
これはNATOやアメリカは少し慎重に過ぎるように感じる。
現在も武器をウクライナに供給しまくっている時点で、すでにウクライナを代理人としたNATOとロシアの戦争である。
にもかかわらず、ロシアは西側諸国がおこなっている経済制裁を「宣戦布告に等しい」とはいうものの、「宣戦布告だ、NATOとの戦争だ」という言葉はいっていない。
ロシアはもともと安全保障を求めてNATOの不拡大を要求していたわけだけど、
ロシアがNATOに勝てるわけはなく、ロシアからNATOを攻撃する可能性は極めて低い。
ロシアがNATOに攻撃するとすれば、精神状態に問題があるのだろう。
だが、この言説すら「核を使うぞ」という脅しの強化のためにロシア側が流している可能性もあるのではないか。
ロシアはウクライナからは、ロシア国土への反撃はされない、と思っているから侵攻しているのであって、ロシアからNATOには侵攻しない。
その点を見越して、今後のロシアとウクライナの停戦交渉を有利にすすめる為にも
もう少しNATOとアメリカはウクライナに肩入れする姿を見せておいたほうがよいのではないかと思う。「人道支援だ」と強弁しながら、Mig29をウクライナに供与すればよいと思う。
NATOや米は武力介入することになるだろう
西側諸国の武力介入なしに、この戦争が中露の損になる終わり方をするとは思えない。
西側諸国が結束して経済制裁を加え続けられるか不明だし、制裁の効果が本当に永続するのかもわからない。中露やその他インドやUAEのような西側諸国とは言えない国々との新しい経済関係によって、ロシアが命脈を保つ可能性もあるだろう。
NATOや米国が、戦闘機のような攻撃的な兵器のウクライナへの供与を避けている。
西側諸国としては、「ロシアとの戦争もやむを得ない」といえるほどのロシアによる戦争犯罪の発生をまっているのではないだろうか。おそらく、ウクライナに戦争継続のための支援を続けていれば、どこかでNATOと一触即発の事態になるだろう。ロシアが十分に疲弊し、かつ、西側諸国が戦争に介入するに値する理由の発生をまっているのではないか。
ほかにも人道危機はあったのに・・・
それにしても、これまでアメリカもロシアもアチコチで侵略戦争をしてきたわけだし、深刻な人道危機も無数にあっただろうが、西側諸国各国から見れば対岸の火事だったと感じていたのかな、と
思う。つまり、ウクライナの侵略がここまで大問題になっているのは、
・ロシアにとってもNATOにとっても近い国だから
・西側諸国、特にヨーロッパ諸国にとっては人種的・宗教的にも近い人たちの国だから
だということかなと思う。
これはある意味、差別的なことではあるが、紛争のイメージのある地域・国での人道危機には、これまでこのようなヒステリックな反応はしてこなかったと思います。
Home Security Mod のインストールトラブル解決
Home Security Mod はここから入手 pcminecraft-mods.com
Home_Security_Mod_1.12.2.zip というファイルが手に入るはず。 modsフォルダに入れて起動すると、cdm 0.3.1 以上が必要と言われる。
これが何のことかわかりにくいのだけど、どうやら以下であることが知恵袋の書き込みから分かった。
https://www.curseforge.com/minecraft/mc-mods/mrcrayfishs-device-mod
(よくみたら、pcminecraft-mods.com にも説明があった )
ここで、device-mod-0.3.1-1.12.2.jar を入手。
だけど、今度は crash してしまう。
で、よく見ると Home_Security_Mod_1.12.2.zip の階層構造がおかしい。
//おかしい Home_Security_Mod_1.12.2.zip + Home_Security_Mod_1.12.2 + assets + com + META-INF + mcmod.info + park.mcmeta
一度、zipを展開して以下のような構造になるように再度zipする
//これが良い Home_Security_Mod_1.12.2.zip + assets + com + META-INF + mcmod.info + park.mcmeta
これで動くようになったよ。 配布サイトにもつたない英語で、コメントを残しておいた。
でも、modファイルの拡張子は普通は jar ですね。 再zip後、拡張子をjar にしておく方が気持ちがよいかもしれませんね。 ( jar ファイルはの実態は zipファイルであることは Java言語を知っている人には常識でしょうが)
C# と Python
これは何の話?
普段C#を書いている自分が、Pythonコードを書くためのメモ
クラス
class Hoge
{
}
class Hoge: pass
書くものがないときは、passを書く。
new する
var h = new Hoge();
h = Hoge()
インスタンスメソッド
public class Hoge{ public void Fuga(){ } }
class Hoge: def Fuga() -> None : pass
引数なしコンストラクタ
public class Hoge{ public Hoge(){ } }
class Hoge: def __init__(self): pass
コンストラクタに self が必要なところで驚く。 var h = Hoge() とかけば init が呼ばれてHogeインスタンスができる。 なので、実際のところ、呼び出し時に self を渡すコードを書くことはない。
引数ありコンストラクタ
public class Hoge{ public Hoge(string s){ } }
class Hoge: def __init__(self, s: str): pass
selfのあとに、引数を書く。 この例では引数は受け取っているけど、使っていない。
Pythonは型を明示しなくても動作するけど、C#に慣れた身としては明示しておいた方がしっくりくる。
mypyのようなコード検査ツールでは、型指定されていればそれに応じたチェックがなされる。
この例では、変数s
は str型
なので、 Hoge(123)
は、mypyではエラーが検出される。
実行時にはチェックされない。
インスタンスフィールド ( Python では 属性 と呼ぶ )
public class Hoge{ private string s; }
class Hoge: def __init__(self): self.s: str = ""
Pythonでは、この s のことを属性という。
単に宣言だけすることはできないので、何らかの初期値を与えておく。
staticフィールド ( Python では クラス変数 と呼ぶ )
public class Hoge{ public static string s; }
from typing import ClassVar #ClassVar型構築子を使う場合に必要 class Hoge: s1: str = "" s2: ClassVar[str] h = Hoge() Hoge.s1 = "123" #OK クラス名から辿れる h.s1 = "123" #OK インスタンスからも辿れる h.s1 = 123 #NG 型があっていない Hoge.s2 = "123" #OK クラス名から辿っているのでOK h.s2 = "123" #NG インスタンスから辿っているのでNG(これがClassVar型構築子の効果) Hoge.s2 = 123 #NG 型があっていない
C#er的には、インスタンスフィールドを書いたような気分になってしまうが これで、staticフィールドになる。インスタンス変数からアクセスできてしまったりするので、C#のstatic フィールドとは ちょっと違う。 ClassVar[型] のように型を指定すれば、クラス名でのアクセスを強制できる。
複数のコンストラクタ
public class Hoge{ private string s; private int i; public Hoge(string s){ this.s = s; } public Hoge(int i){ this.int = i; } }
class Hoge: def __init__(self): self.s :str= "" self.i :int= 0 @classmethod def CreateHoge1(cls, s : str): c = cls() c.s = s return c @classmethod def CreateHoge2(cls , i : int): c = cls() c.i = i return c h0 = Hoge() h1 = Hoge.CreateHoge1("abc") h2 = Hoge.CreateHoge2(123)
端的にいうと、複数のコンストラクタを作ることはできない。 init を複数個書くと実行時にエラーになる。 selfの属性として、インスタンスフィールドを表現する都合上、確かに複数の init の記述を認めると 読み手もどう解釈すればいいのかわからないのでこうなっている、と自分は受け止めている。
複数のコンストラクタを作りたい場合は、インスタンスを作るメソッドを作ることで代用する。 これには、 @classmethod デコレータをつけたメソッドを使うことが一般的なようだ。 これに相当するものは C# にはない。
第一引数を cls
とするのは慣例なので従うこと。
次の@staticmethodでも代用できそうにも思えるけど @classmethodにしかできないこともあり、後述する。
staticメソッド
public class Hoge{ public static void Piyo(string s){ Console.System.WriteLine(s); } }
class Hoge: @staticmethod def Piyo(s: str) -> None: print(s) h1 = Hoge() Hoge.Piyo("static") h1.Piyo("instance") # インスタンスから呼ぶこともできる
classmethod
classmethod は、C.f() とも呼び出せるし、 C().f() のように呼ぶこともできる。 このようなメソッドは、C#にはない。
複数のコンストラクタの代用品を作るときに classmethod を使用した。
staticmethodでも似たようなことができるが、cls()
のように、init の呼び出しを
抽象的に書くことはできない。
clsのように書くことで、継承のときに、cls()
がサブクラスの __init__()
に読み替えられる。
staticmethod で、コンストラクタ代わりのメソッドを書くと、継承時に困る。
先ほど、classmethodでコンストラクタ代わりのメソッドを書いたが 戻り値の型指定を省略していた。継承を考慮すると、戻り値の型指定を書くことはできない。
class Hoge: def __init__(self): self.s :str= "" self.i :int= 0 @classmethod def CreateHoge1(cls, s : str) -> cls : # このように書くことはできない c = cls() c.s = s return c @classmethod def CreateHoge2(cls , i : int) -> Hoge : # これもダメだった。意外。この時点ではクラスの定義が完了していない、ということのようだ。 c = cls() c.i = i return c
using
+-- Main.py | \---Fuga | Hoge.py | Piyo.py \ __init__.py
Hogeクラスが、Hoge.py に定義されている。 Piyoクラスが、Piyo.pyに定義されている。
from Fuga import Hoge,Piyo h = Hoge.Hoge() p = Piyo.Piyo() print(h) print(p)
from Fuga.Hoge import * from Fuga.Piyo import * h = Hoge() p = Piyo() print(h) print(p)
カラーボックスの扉にチャイルドロックをつけるDIY
2歳児は何でも触りたがるので、我が家では子供に触られたくないものがどんどん高所に避難しています。しかし避難場所も尽きてきたので、扉付きのカラーボックスにチャイルドロックをつけることにしました。
鍵付きのカラーボックスも市販されていますが、鍵が棚ごとに違ったりして不便だし、鍵無くすと開けられない恐れもあります。
磁気式のチャイルドロックならば、適当な磁石であけられますし、カラーボックスを2個以上つくるならば、たぶん割安です。
今回つかった製品。Amazonで上記2点を購入しました。
これでDIYします。
まずはCX-33Dを組み立て、完成させます。
その後、チャイルドロックをつけます。
木ねじと、木ねじをつけるための下穴をあけるドリルを用意します。
ロックを両面テープでつける。この時点ではそっとつけておきます。(グッと押し付けると、位置替えが面倒です。)
ロックには、取付位置用のクレイドルがついていますが、使いませんでした。我が家では、CX-33Dを6台組み立てましたが、ざっくり上記の位置につけて、あとで微調整でOKと思います。
開閉テストがうまくいったらロックと留め具を木ねじでとめていきます。
両面テープでは少し不安なので、2歳児の引っ張りに耐えられるように、木ねじで留めます。
木ねじだけで留め具を貫通させるのは、大変です。あらかじめ木ねじに3mm~4mm程度のドリルで穴をあけておきます。(留め具を貫通し、木くずが少しでるくらい。木の板を貫通しないように注意します。)
木ねじで留めると、留め具の高さが少し変わることがあるので、ここでもう一度開閉テストします。
テストがNGならば、ロックの位置を調整します。ロックの位置調整の代わりに、留め具を一度外して、予備の留め具テープを二重に貼って高さを調整してもよいでしょう。
↑予備のテープで高さを調整した例
ロックも2か所、木ねじで留めます。(写真は1か所しか映っていませんが、反対側も同様に留めます)
こちらもあらかじめドリルで下穴ををあけます。下穴が小さすぎたり、木ねじを締めすぎるとロックに亀裂が入ります。多少亀裂入っても多分だいじょうぶです。(両面テープでそこそこ固定されています。)
最後にもう一度開閉テストをして、ドリルくずの掃除をして完成です。
「(情緒的)再入可能性」入門
これは何の話?
初学者が次のようなメソッドを書くことがある。 戻り値にしたい値が二つあったのだけど、戻り値を二つ返せないので、インスタンスフィールドで結果を受け取る、というわけだ。
//戻り値ではなく、インスタンスフィールドで結果を受け取る List<string> filePathList = new List<string>(); List<int> fileSizeList = new List<int>(); public void DoSomething(){ //filePathListには girlsを含むファイルパスが格納される。 FindFileByName("girls"); //filePathListには、既にgirlsがあり、そこにboysが追加される。 //望んだ結果なの??? FindFileByName("boys"); } //keyword を含むファイルを見つける処理 private void FindFileByName(string keyword) { for( ... ) //何かのループ処理 { //ファイルシステムを検索し、 //keywordを含むファイルみつける処理 //見つかったファイルのpath と size を格納 filePathList.Add( ... ); fileSizeList.Add( ... ); } }
こういう作りのメソッドは、2回目に呼び出されたときに、1回目の結果がクリアされていないので、意図した結果にならないよね。
じゃ、戻り値を 「Tuple<string,int>で返そう」「戻り値用クラスを作ろう」というテクニックを教えると、こういうコードは無くなる・・・かというとそうでもない。 なぜか、「戻り値用クラスを作るのが面倒で・・・」とか何とかいって、またこういうコードを書いてしまう。
こういうコードは「●●●●性」が低いからよくないことが多いのよ、、、って言ってあげたいのだけど、ちょっといい言葉が思いつかない。 さて「●●●●性」ってなんだろう。
この文章が言いたいこと
長い文章なので、先に結論めいたことを言っておくと「(情緒的)再入可能性」の高いコードを書く方が良い場合が多いので、「(情緒的)再入可能性」を意識しよう、ということ。
情緒的に「再入可能性」を理解する
「●●●●性」は「(情緒的)再入可能性」ということにしよう。これは本来の「再入可能性」とはちょっと違うんだけど、初学者には不正確を承知で、「(情緒的)再入可能性」を以下のように覚えておくとよい。
- 再入可能性とは、同一スレッドで、ある関数を2回以上実行したときに、2回目以降も意図した結果になること。
これは、本来の定義とはまぁまぁ違うんだけど、これを満たすことは(真の)再入可能性の第一歩なので、ま、いいかなと。
以後、「再入可能性」というと、だいたいは「(情緒的)再入可能性」を指します。
「再入可能性」を2値ではなく、アナログ値で考える
「(真の)再入可能性」って「高いか/低いか」とアナログ値的に考えるものではなくて、「満たすか/満たさないか」の2値で考えるもの。
だけど、「(情緒的)再入可能性」では、アナログ値的に「高さ」を考えることもなかなか有用。そこで、「高い」「低い」を次のように考えることにしよう。
- 「再入可能性が高い」・・・メソッドに登場する変数のスコープは、ローカルなものが多い。
- 「再入可能性が低い」・・・メソッドに登場する変数のスコープは、ローカルなものが少ない。
「再入可能性」が「高い」方が、よい結果を招くことが多いので、できる範囲で「再入可能性を高く」しよう。
冒頭のメソッドは、インスタンスフィールドのクリアをすることでも、「(情緒的)再入可能」になる。だけど、結果を戻り値で返す方が「再入可能性が高い」と考えることができる。 (また、後述の「再入」を読めばわかるけど、インスタンスフィールドのクリアでは「(真の)再入可能」にはならない。)
「再入」ってなに?
たぶん、いろいろな記事を読み漁ってもピンと来ていない人も結構いると思いますが、「再入」とは、同一スレッド上で、関数Aの実行中にそれをとめて、関数Aを呼ぶこと、なんだよね。
ここで見落としてはいけないのは「同一スレッド上で」という点。
フツーにC#だけしか触ったことない人には、「ひとつのスレッドで関数Aを実行している最中に、そのスレッドで関数Aを呼ぶ」、ってことが理解できないよね。 でも、Linuxのシグナルハンドラは、スレッドXが関数Aを実行している最中に、シグナルを受け取ると、関数Aの実行は中断されて、スレッドXでシグナルハンドラが実行される。シグナルハンドラが関数Aを呼ぶと、つまりは関数Aは「再入」された、ということになる。この時、関数Aが意図通りの結果になるように実装されていれば、「(真の)再入可能性」を満たす、とよぶわけだ。
おれら「再入」とは無縁の世界の住人でしょ?
「再入」と無縁な人にとっては、「(真の)再入可能性」って過剰品質なんだよね。だから、「(真の)再入可能性」を理解する必要も、それを満たす必要もないと思う。
でも「再入」と縁がなくても、「(情緒的)再入可能性」として、「高さ」を意識することは結構有用だと思うんです。
例えば、冒頭のメソッドは「再入可能性の高さ」を意識していれば、「インスタンスフィールドのクリアをする/しない」という低レベルな問題につまずくことはないよね。
「参照透過性」と呼んじゃだめなの?
「参照透過性」について意識することでも、コードを書くときの美的センスについて、似たような洞察が得られるかもしれないけど、「参照透過性」って「再入可能性」より潔癖な感じだよね。
「参照透過性」って、おおざっぱに言えば
- 「引数以外、何も受け取らない」
- 「引数が同じなら、戻り値もいつも同じ結果になる」
ってことだよね。この話題は、「引数以外、何も受け取らない」というストイックな話ではないので「再入可能性」の方がしっくりくるかな、と思う。
スレッドセーフと「再入可能性」は違う
まぁ当然ちがうんだけど「再入可能性」が高いと、スレッドセーフに改造しやすいことが多い。なので、苦労のない範囲で「再入可能性」は高くなるように心がけた方がいい。
スレッドセーフと「(真の)再入可能性」は違う
非ローカルな変数Yを使っている関数は、「(真の)再入可能性」を満たさないけど、変数Yをスレッドローカル(TLS)な変数として宣言していれば、スレッドセーフにはなる。
別の説明もできそう。
「(真の)再入可能性」を満たすコードってヘンテコなコードだよね。スレッドセーフにするならば、tをTLSにすれば済むところ、同一スレッドで実行されるから「自前でTLS的な変数s」を用意している、と思うとわかりやすいような気もする。(逆に混乱させたらスマンけど)
_Thread_local int t; // tをスレッドローカルに宣言しているので、swapはスレッドセーフになる void swap(int *x, int *y) { int s; //再入にはTLSは効果ないので、自前で退避用のsを用意 s = t; // グローバル変数をセーブ t = *x; *x = *y; // ここでハード割り込みが起きて isr() が呼び出される可能性がある。 *y = t; t = s; // グローバル変数をリストア } //↓この関数がシグナルハンドラから呼ばれる void isr() { int x = 1, y = 2; swap(&x, &y); }
「再入」のコード
「再入」が同一スレッド上で実行されることを、次の汚いコードで確認できる。
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> #include <sys/types.h> #include <sys/syscall.h> #include <pthread.h> //gcc hoge.c -pthread でコンパイルできるハズ。 //思い出して書いたのでコンパイルできるか確認してません。全体的に適当なコードです。 //CentOSで動いた。 volatile sig_atomic_t exit_flag = 0; void sig_handler(int sig); void* secondthread(void* p); __thread int t; //tをスレッドローカルな変数にすることで、swap関数はスレッドセーフになる //int t; //swapは再入可能ではない void swap(int* x, int* y, int time) { t = *x; *x = *y; sleep(time); // ここでハード割り込みが起きて isr() が呼び出される可能性がある。 *y = t; } void isr() { int x = 1, y = 2; swap(&x, &y, 0); printf("isr-swap %d %d\n", x, y); } //スレッドID取得関数 pid_t gettid(void) { return syscall(SYS_gettid); } int main() { printf("main-tid %d\n", gettid()); if (signal(SIGINT, sig_handler) == SIG_ERR) { exit(1); } pthread_t pthread; pthread_create(&pthread, NULL, &secondthread, NULL); while (!exit_flag) { int x = 8, y = 9; swap(&x, &y, 1); printf("main-swap %d %d\n", x, y); } sleep(3); return 0; } void* secondthread(void* p) { printf("2nd-tid %d\n", gettid()); while (!exit_flag) { int x = 5, y = 6; swap(&x, &y, 1); printf("2nd--swap %d %d\n", x, y); } } void sig_handler(int sig) { printf("sig-tid %d\n", gettid()); isr(); exit_flag = 1; }