平田智剛のブログ

立法、行政、司法、報道、そして科学

F.ism.does_harm_to(!F);

シグモイド関数yが(0, 0.5)に対して点対称であることを証明してみた

シグモイド関数yが(0, 0.5)に対して点対称であることを証明せよ

y=\frac{1}{1+\exp(-ax)}が点(0, 0.5)に対して対称であることは
y=\frac{1}{1+\exp(-ax)}-0.5が原点対称であることに同値。
また、y=f(x)が原点対称というのは、
f(-x,-f(x))を通ることに同値である」ことに同値である。
またf(-x,f(-x))も通るので、-f(x)=f(-x)に同値といってもよい。
 
すなわち、y=\frac{1}{1+exp(-ax)}-0.5が原点対称であることは
-(\frac{1}{1+\exp(-ax)}-0.5)=\frac{1}{1+\exp(ax)}-0.5であることに同値だ。
-\frac{1}{1+\exp(-ax)}=\frac{1}{1+\exp(ax)}-1
-1=\frac{1+\exp(-ax)}{1+\exp(ax)}-(1+\exp(-ax))
0=\frac{1+\exp(-ax)}{1+\exp(ax)}-\exp(-ax)
\exp(-ax)=\frac{1+\exp(-ax)}{1+\exp(ax)}
(1+\exp(ax))\exp(-ax)=1+\exp(-ax)
\exp(-ax)+1=1+\exp(-ax)
QED

多重継承を許す場合の循環継承の見つけ方

(同じ記事をqiitaにも上げています)

qiita.com

0.目的

クラスの多重継承を許す場合、継承の循環の起こり方が複雑になる。
そのような場合に循環継承を効率的に見つけるアルゴリズムを考えてみる。

1.循環継承とは

そもそも循環継承とは何か。
視覚的には図1.1のように、「継承の単方向輪」ができることである。

image.png

図1.1 単一継承の場合の循環継承の例
 
これを言葉で説明すると、「自分が自分自身の先祖や子孫となる」関係である。図1.1の例では、AはA自身の曾祖父母あるいは曽孫であるといえる。

多重継承の場合、直系が複数通りになるため、「どの直系で循環継承が起きているか」を考える必要が出てきてしまう。

2.考案するアルゴリズム

すべての「子クラスを持たないクラスc0」に対して次の方法を適用することで、系内の循環継承を見つけることができる。但し、cic0直系尊属i親等な任意のクラスである。

  1. i=0とする。
  2. ciの子孫集合D_1を考える
  3. ciの親の配列Ci+1を考える。
  4. Ci+1の各元ci+1の子孫集合D_2を考える。
  5. ci+1の循環参照がないことの真偽はD_1 \cap D_2 = \phiと同値である
  6. iをインクリメントする。

なお、このアルゴリズムには次の2つの定理を採用している。

  • すべての「子を持たないクラス」の先祖を辿れば、系内全てのクラスにアクセス可能である (2-1節で詳解)
  • 子孫にdを持つクラスcの任意の親pが子孫にdを持つことと、pが循環継承していることは同値である (2-2節で詳解)

2-1.系内全てのクラスに最低1度は確実にアクセスする方法

すべての「子を持たないクラス」の先祖を辿れば、系内全てのクラスにアクセス可能である

全てのクラスは「子を持たないクラス」と「子を持つクラス」に分けられる。
「子を持つクラス」は何かしらのクラスから見た親クラスである。
いま、クラスについて①②に場合分けして考えよう。
「子を持つクラス」の子が「子を持たないクラス」である場合、子の先祖を辿ることでアクセス可能だ。・・・①
「子を持つクラス」の子が「子を持つクラス」である場合、子についてもう一度場合分けして考える。・・・②
②に分類される限り永遠に場合分けが続き、停止するのは①に分類される場合のみ。
循環継承がない限り、卑属の世代数は有限であるため、すべてのクラスは必ずいつか①に分類される。
循環継承がある場合、循環が起きているということは、すでに循環継承に関わるクラスにはすべてアクセス済みであるということである。

2-2.循環参照がそこにあるかの判定法

各クラスの親の1次元配列を考えよう。
このとき、各クラスの親もクラスであるため、各々の親の1次元配列を持っている。
そのため、各クラスは祖父母の2次元配列を持っているといえる。
同様に考えることで、一般に各クラスは直系尊属n親等のn次元配列を持っているということができる。
ここであるクラスをcと呼ぶこととする。cの先祖の任意の多次元配列にcが含まれることは、
cが循環継承しているということと同じである。
つまり命題「cの先祖配列 \ni c ⇔ cは循環継承している」は真である。
この命題は次のように書き換えられる。
cの先祖配列 \ni c' かつc'=c ⇔ c'は循環継承している」
ここで「cの先祖配列 \ni c' 」とは、cc'の子孫といえることと同じである。
c'からみてcの先祖配列とは、自身を含む先祖配列であり、その配列の少なくとも1つの元について、子孫にcを持つものである。
したがって、任意の「cを含む先祖配列」において、その元の少なくとも1つが子孫にc'(=c)を持つことと、c'が循環継承していることは同値だ。
ここで、「子孫にdを持つ任意のクラスc」の親クラスの先祖配列P_1(c)を考えよう。
cの任意の親をp_1(c)と呼ぶとき、P_1(c)は「p_1(c)を含む先祖配列」ということができる。
P_1(c)の元の少なくとも1つが子孫にp_1(c)を持つことと、p_1(c)が循環継承していることは同値である。
但し、子孫にp_1(c)を持つとき、必ず子孫に「p_1(c)の子孫」をも併せ持つ。つまり子孫として必ずcdを持つというわけだ。
したがって、次のように言うことができる。

「子孫にdを持つクラスcの任意の親pが子孫にdを持つことと、pが循環継承していることは同値である」by 17ec084

努力しても数学ができるようにならない人の中にはこんな奴もいる

お久しぶりです。

今日はオブジェクト指向のクラス図を生成するアルゴリズムについて考えていました。

「子クラスを持たないクラス」すべての先祖クラスを辿れば、完全なクラス図(クラスAの先祖BがAの子孫でもあるといったおかしな「循環継承」がない限り)が生成できることに気がつきました。

(数学的帰納法による証明:
すべてのクラスは「子クラスを持つクラス」と「子クラスを持たないクラス」に分けられる。
任意の「子クラスを持つクラス」P1は何かしらのクラスC1の親クラスである。
またP1の先祖は必ずC1の先祖だ。ゆえにC1の先祖が完全に分かるとき、P1の先祖も完全に分かる。
C1が子クラスを持つクラスの場合、P2=C1として、P2の任意の子クラスC2を考える。これを繰り返すと、循環継承がないという仮定より、いつか必ずCiが子クラスを持たないクラスになる。Ci=Pi+1の先祖が分かっていれば、Piの先祖が分かる。これを帰納的に繰り返せば、P1の先祖もわかる。逆順にたどれば当然P1の子孫クラスもわかる)

 いやぁ自分で証明を書いててびっくりしました。
こんなに煩雑なこと考えた覚えないんです。

結局ひらめきというか、無根拠に「これできそう」と考え、後付けで論理的に確認するだけなんですよね。

 

僕は高校時代、いくら努力しても一向に数学ができるようにならなかったんですが、
教科書や演習教材の問題を解いて、解説を読み、苦労して理解するだけだったんです。
次似たような問題解いても一切解けない。教科書レベルならともかく、入試対策とかは本当にそうでした。当時は「2つの問題が似ている」ことにすら気が付けなかったです。

 

数学の問題解くのって、無根拠ひらめきに対する証明を書くのと逆だなって思っていました。論理展開して、一つの答えを導く必要があるから、まず先に論理展開する必要があるわけです。

「この論理で答えへたどり着けそう」という無根拠ひらめきが浮かべば、数学の問題は解けることでしょう。ここにまで根拠を求めてしまうのが、「努力しても数学ができない」人の一部です。

解説書に「余弦定理より~」とか書いてあって、数学のできるほとんどの人は「そうか。こういう時は余弦定理を使うのか」と納得し、覚えます。教師が「数学は暗記じゃない」といっていようと、結局は覚えているはずです。
それこそ論理的に考えてください。どうして昨日の彼が解けなかった問題が、今日の彼は解けるのでしょうか。それはきっと解き方が頭に入っているからです。頭の中をプログラミングに例えるなら、「変数」のような形では覚えていないでしょう。それは確かに丸暗記であり、応用が利かないと思います。でも解き方というプログラムを暗記しているはずなんです。チューリングマシンで考えれば、変数もプログラムもメモリに保存される「覚えておくもの」です。コンピュータだろうと脳だろうと、チューリングマシンでモデル化して説明する限り、次の結論を得ます。
昨日できなかったことが今日できるのは、昨日から今日の間に、何かがメモリに書き込まれたからにほかなりません。(厳密にはメモリというより「テープ」ですがね笑)

つまり、論理展開の根拠は暗記した内容でいいはずなんです。

 

一方、「どうしてそこで余弦定理が出てくるのか」を、さらに基本的な原理に立ち返って考えてしまう人は悲惨です。

 

「論理展開の根拠」を求めてしまうと、ドツボにはまります。

論理展開の論理展開というか。メタ論理に苦しみます。

せっかく定理という「クラス」にまとめられているのだから、定理の内容と使い道を覚え、「呼び出す」べきです。

オブジェクト指向でメインメソッドの可読性が増すように、定理を積極的に使えば、論理展開が簡単になるはずなんです。

それをわざわざ手続き型言語に書き換えようとしているのが、「論理展開の根拠」を求めるということです。

確かに、機械語に近いコードを確かめるかの如く、「問題の本質を理解したい」のであれば、自主的な勉強としてこれをやるのはありでしょう。

しかし、毎回毎回論理展開の根拠を追い、定理とその使い方の定着そのものを疎かにしてしまうようでは本末転倒です。

 

いくつかの役立つクラスが定義済みで、オブジェクト指向
「この機能を10行以内のコードで30分以内に実装しなさい」
と問題が出ているところを、クラスを無視して機械語でプログラミングして解答しているようなものです。

 

これでは、勉強時間当たりの演習量そのものが激減してしまいます。


高校生の時は、「他の奴らよりもずっと本質的な勉強を、他の奴らよりも長い時間やってるはずなのに、どうして他の奴らよりも自分は数学が苦手なんだろう」と思っていましたが、今考えれば至極当然のことでした。

 

「他の奴らよりもずっと機械の本質に迫った機械語プログラムを、他の奴らよりも長時間書いているはずなのに、javaでコーディングしてる奴らよりも自分はプログラムがたくさん書けないのだろう」といっているようなものです。

どうしてこう解けるの?はもうやめましょう。怒られようと、理解の上で暗記しましょう。

 

オブジェクト指向を「英文」とみなそうとするともやもやが残る理由

Qiitaにも同じ記事を投稿しました。

qiita.com

0. 結論

他動詞と自動詞を区別しよう。ただそれだけ。

1. 導入

 オブジェクト指向による適度に可読化されたコードが、英文とみなせるということを聞いたことのある方は多いだろう。具体的にコードを英文として読むためには、次のように読む。

主語.述語();//第1文型
主語.述語(補語);//第2文型
主語.述語(目的語);//第3文型
主語.述語(目的語, 目的語);//第4文型
主語.述語(目的語, 補語);//第5文型

2. 問題

2-1. 分かりやすい問題

 しかし、述語(動詞)の種類によっては、「主語」が本当に「主語」なのか疑わしくなってしまう時がある。 例えば、javaList(知らない方向けに説明すると、配列のようなもの)では list.add(obj); のような書き方ができる。 このコードではListインスタンスlistobjを追加するということである。 決してlist「が」(どこかへ)objを追加するわけではないのだ。 そのため英文では「JVM adds list obj.」となってしまい、listは主語でなくなってしまう。 英文的な正しさを追求するなら「JVM adds list obj.」も誤り。addは第4文型をとらないので、素直に「JVM adds obj to list.」と、修飾語付きの第3文型で書くべきである。但し、これは自然言語的な問題であり、人工言語への統一的な変換規則を考える上では相応しくない。そのため今後もすべての他動詞において、第4文型や第5文型に対応するものとみなす。

2-2. 実際上の大問題

 2-1節の問題をなあなあにすると、アクセッサが180度入れ替わってしまう大問題に発展する。すなわちgetterはsetterに、setterはgetterになってしまう。 例えば フィールドram およびそのアクセッサ getRam(int address), setRam(int address, byte value) を持つクラス OS を用意し、そのインスタンス computer を作ったとしよう。

public class OS
{
    private byte[] ram;
    public byte getRam(int address){return this.ram[address];}
    public void setRam(int address, byte value){this.ram[address]=value;}
    public OS(int ram_size){this.ram = new byte[ram_size];}
}

class Main{ public static void main(String...args)
{
    OS computer = new OS(64);//64byteのRAMを持つコンピュータの作成
    int i=0;
    for(char each_char : "hello world".toCharArray())
        computer.setRam(i++, (byte)each_char);
        //0~10番地に「hello world」を書き込む

    for(int j=0; j<11; j++)
        System.out.print((char)computer.getRam(j));
        //0~10番地を(charとして)表示
        //→コンソールに「hello world」と表示される。
}}

このようにして、(何の役にも立たない)自作OSを定義し、そのOSの入った(何の役にも立たたない)仮想computerを作り、hello worldと表示させることに成功する。

ここで、

    int i=0;
    for(char each_char : "hello world".toCharArray())
        computer.setRam(i++, (byte)each_char);

により、RAMに"hello world"を書き込んでいる。 setRamの主語は誰だろう。 つまり誰がRAMを書き込んで(setして)いるのだろうか。 ここでコードは大ウソをついている。 コードはこの疑問に、あろうことかcomputerと答えている。 computerは書き込まれる目的語じゃんか。。。

setRamの主語を本当にcomputerであるとするなら、 computerがRAMを外の世界に書き込んで(setして)いることになってしまう。 そしてそれは、getRamの機能「RAMの内容を呼び出し元に返却する」に他ならないではないか。

つまりコードを素直に解釈すると、setterはgetterになってしまうのだ。 この主張を理解できない上級者の方は、初心に返って考えてほしい。 「『computer』が『ram』を(『呼び出し元』に)『書き込みset』する」といったとき、 ramはcomputerに書き込まれるの?それとも「呼び出し元」に書き込まれるの?と考えてみよう。 そこで気がついてほしい。自分は今までとんでもない世界に慣れてしまい、感覚がマヒしていたのだと。

同様に、getterもsetterになってしまうcomputer.getRAM(~);を素直に解釈すると、 「『computer』が『ram』を『取得get』する」のだからこれはsetterの定義に他ならない。

3. 問題の発生要因

 この問題が生まれたのは、他動詞と自動詞の区別ができる(重んじる)技術者があまりにも少ないためである。 少なくともOracleは他動詞と自動詞を混同させてオブジェクト指向を説明してしまっている。もはやオブジェクト指向を理解できない方が正しいと言わざるを得ない段階まで悲惨だ。 以下、https://docs.oracle.com/javase/tutorial/java/concepts/object.html から引用および和訳する。

(引用)

Real-world objects share two characteristics: They all have state and behavior. Dogs have state (name, color, breed, hungry) and behavior (barking, fetching, wagging tail). Bicycles also have state (current gear, current pedal cadence, current speed) and behavior (changing gear, changing pedal cadence, applying brakes).

(和訳)

実世界のオブジェクトには2つの特性がある。「状態」と「行動」だ。 犬は状態として「名前」、「色」、「犬種」、「空腹さ」を持ち、 行動として「吠える」、「くわえる」、「尻尾を振る」を持つ。 自転車であれば状態として「現在のギア」、「現在のペダルを漕ぐ速さ」、「現在の速さ」を持ち、 行動として「ギアチェンジ」、「ペダルの漕ぐ速さの変更」、「ブレーキ作動」などがある。

この文章の中で、犬の行動と自転車の行動で決定的(いや、致命的)な違いがある。 犬の行動は犬が「自分でとる行動」であるのに対し、自転車の行動は人という「自分以外のものが取る行動」だ。 犬の行動「吠える」、「くわえる」、「尻尾を振る」はいずれも犬が主語となる自動詞であるが、 自転車の行動「ギアチェンジ」も「ペダルを漕ぐ速さを変える」のも「ブレーキを作動させる」のも、自転車が主語にならない他動詞である。

自転車の行動としてあげられている例は、本当は「自転車に対して適用可能な操作」である。

犬の「行動」と自転車の「操作」を混同して、「これがオブジェクト指向の例だよ」といわれても、初学者の素直な感覚では理解不能であろう。 「行動」と「操作」の違いを頑張って忘れ、getとsetの方向を間違えるようになって、初めてオブジェクト指向は理解できる のが現状だ。 これでは「理解していない方が正しい」といわざるを得ないし、 (主語.述語(目的語)が嘘と分からないままで)オブジェクト指向を理解することは、一種の洗脳にかかることとすら言えるであろう。

4.解決

4-1. 理論

 他動詞メソッドを受動態に変えるか、インスタンスを第1引数に移動することで、オブジェクト指向のコードは正しく英文として読めるようになる。4章ではこのことを解説していく。

 3章に示した通り、他動詞と自動詞を区別しない文化が、主語と目的語を混同する現状を招いている。掘り下げていえば、オブジェクト指向では他動詞の目的語を主語とみなす文化がある。・・・① 本来、自動詞の主語は「能動的な行動者」(例:吠える犬)であり、 他動詞の主語は「操作者」(例:自転車のギアを変える人)であって、 他動詞の目的語が「受動的な行動者」(例:ギアを変えられる自転車)になる。・・・② ①②をまとめていうなら「オブジェクト指向では、他動詞が出てきたとき、受け身の名詞と主語の名詞を逆にしてしまう問題を抱えている」のである。

 逆に言えば、他動詞が出てきたとき、受動態に書き換えることで、もう一度主語と目的語を反転させることができるから、正しい英文が完成する。 例えば bicycle.change_gear_to(5);bicycle.is_changed_gear_to(5); あるいは bicycle.gear_is_changed_to(5); とすれば、「自転車は、ギアを5に変更される」と和訳できるような英文として読めるため、意味の上でも正しくなる。・・・受動態法

 あるいはコード上でもインスタンスを引数の位置に持ってきてしまう。 change_gear(of bicycle,to 5); これなら、主語(javaならJVM)の欠けた英文、すなわち命令文として正しく読むことができる。・・・目的語法

4-2. 受動態法の実践 

OSクラスとcomputerインスタンスの例では、次のようになる。

public class OS
{
    private byte[] ram;
    public byte is_got_ram(int address){return this.ram[address];}
    public void is_set_ram(int address, byte value){this.ram[address]=value;}

    public OS(int ram_size){this.ram = new byte[ram_size];}
}

class Main{ public static void main(String...args)
{
    OS computer = new OS(64);//64byteのRAMを持つコンピュータの作成
    int i=0;
    for(char each_char : "hello world".toCharArray())
        computer.is_set_ram(i++, (byte)each_char);//by jvm
        //0~10番地に「hello world」を書き込む

    for(int j=0; j<11; j++)
        System.out.print((char)computer.is_got_ram(j));//by jvm
        //0~10番地を(charとして)表示
        //→コンソールに「hello world」と表示される。
}}

確かに、英文として正しく読めるようにはなったものの 今度はメソッド名に_が多くなり、わかりにくくなってしまった。 (とくに、isから始まるメソッドはboolean型を返却するという掟と競合してしまうのは問題)

目的語法(4-3節)ではこの問題が発生しない。

4-3. 目的語法の実践 

OSクラスとcomputerインスタンスの例では、次のようになる。

String code = "

public class OS
{
    private byte[] ram;
    public byte getRam(int address){return this.ram[address];}
    public void setRam(int address, byte value){this.ram[address]=value;}

    public OS(int ram_size){this.ram = new byte[ram_size];}
}

class Main{ public static void main(String...args)
{
    OS computer = new OS(64);//64byteのRAMを持つコンピュータの作成
    int i=0;
    for(char each_char : "hello world".toCharArray())
        SetRam(of computer,i++, (byte)each_char);
        //0~10番地に「hello world」を書き込む

    for(int j=0; j<11; j++)
        System.out.print((char)GetRam(of computer,j));
        //0~10番地を(charとして)表示
        //→コンソールに「hello world」と表示される。
}}

"; code=new P().parse(code);

これなら読みやすいが、 例えば SetRam(of computer, i++, (byte)each_char)computer.setRam(i++, (byte)each_char) に置き換えるようなパーサPを構築する必要がある。 (正規表現では、括弧言語を受理しないため、力不足)

動的SQLをpsqlだけで動かす

1. 静的SQLの限界

第1章の結論:
表名や列名などを動的に指定することは、静的SQLでは無理
(この意味が分かるのであれば、第1章を詳しく読む必要はない。
「表あ」と「表あ転置」に目を通し、第2章に進んでよい。)
 

表あは主キー「id」列及び列ア~ウから成っていて、idが1となるレコードを持っているものとする。
このレコードを90度回転させ、列方向に並べて表示させるSQLを考えてみよう。

DROP TABLE IF EXISTS 表あの列名を並べた複数行1列の表;
DROP TABLE IF EXISTS 表あ転置;
CREATE TABLE 表あの列名を並べた複数行1列の表(col text);
INSERT INTO 表あの列名を並べた複数行1列の表 VALUES ('列ア'),('列イ'),('列ウ');
CREATE TABLE 表あ転置 AS SELECT * FROM 表あの列名を並べた複数行1列の表;

-- 表あの列名を並べた複数行1列の表
--┌────┐
--│ col│
--┝━━━━┥
--│列ア│
--├────┤
--│列イ│
--├────┤
--│列ウ│
--└────┘

FOR 表あの各列名 IN SELECT col FROM 表あの列名を並べた複数行1列の表 LOOP
    UPDATE 表あ転置 
    SET col=
    (
        SELECT 表あの各列名 --※
        FROM 表あ 
        WHERE id=1
    ) 
    WHERE col=表あの各列名; --※
END LOOP;   

SELECT * FROM 表あ転置;

表あ (データ型はid以外すべてtext)

id (主キー integer) 列ア 列イ 列ウ
1 神奈川 藤沢 江の島
2 東京 足立 千住

表あ転置(想定)

col (text)
神奈川
藤沢
江の島

(「表あ転置」は、テーブルの未定義してあり、レコードは0件))

しかし、このSQL文は残念ながら思惑通りに動作しない。
FOR文内で、 表あの各列名 はループごとに 列ア列イ列ウ と変化して解釈されるが、
※を付した行では 表あの各列名 はそもそも解釈されない
SELECT 表あの各列名 FROM 表あ は、表あから「表あの各列名」という名前の列を探してこようとしてしまい、
「『表あの各列名』という名の列は、表あには存在しない」という旨のエラーを返してしまう。

このような問題は一般的に 表名や列名(や、データベース名もかな?)に変数を与えたときに発生する
ここでいう「変数を与える」というのを正確な表現で言うと、 動的に指定するということである。
表名や列名などを動的に指定することは、普通のSQL(静的SQL)では無理 なのである。

2. 動的SQLの導入

2-1. 動的SQLは埋め込みSQL

表名や列名を動的に指定する場合、「動的SQL」という方法をとる必要がある。
動的SQLは「SQLを実行した結果として別のSQL文を完成させ、その文を実行する」ということをしたいときに用いるワザである。
postgreSQL 11.5のドキュメンテーション には、次のように記述されている。

36.5. 動的SQL
多くの場合、アプリケーションが実行しなければならないSQL文は、アプリケーションを作成する段階で決まります。 しかし、中には、SQL文が実行時に構成されることや外部ソースで提供されることがあります。 このような場合、SQL文を直接Cソースコードに埋め込むことはできません。 しかし、文字列変数として提供される任意のSQL文を呼び出すことができる機能が存在します。

この引用文の主張はこうだ。SELECT 変数 のようにすることはできないが、str = 'SELECT' + 変数; のように 文字列としてのSQL文を生成 してからkaishaku(str);のように 文字列をSQL文として解釈する機能を利用すれば、同じことができる。
(kaishakuという関数は勝手につけた名前。実際には EXECUTE というのを使う)

ところで、この引用文の載っていた36章は「埋め込みSQL」について紹介する章である。
埋め込みSQLとは、「C言語など他の言語内に書かれたSQL」のことである。

少なくともpostgreSQLの場合、動的SQLを利用する場合は必ず埋め込みSQLを使うことになる。
(そもそも、kaishaku の機能を持つSQL文が存在しないからである)

2-2. psqlだけで動的SQLを実現させる

動的SQLは埋め込みSQLで扱わなければならず、埋め込みSQLは他の言語内に書かれたSQLを意味するのであれば、
三段論法より、 動的SQLは何らかのSQL以外の言語の中に書かなければならない ということになる。
このことから、一見すると、psqlだけでは動的SQLを扱うことができないように思われるだろう。
しかし、実際は psqlだけで動的SQLを扱うワザが存在する のである。
 
埋め込みSQLをほかの言語Lへ埋め込むとき、Lを埋め込み先言語と呼ぶことにしよう。
psqlへ埋め込み可能な(psqlから呼び出し可能な)埋め込み先言語」へ埋め込みSQLを埋め込めば、psqlだけで動的SQLの扱いが完結するではないか。(図2.2.1)
f:id:ec084:20200120065618p:plain 図2.2.1 埋め込み先言語(赤)を介してpsqlへ二重に埋め込まれる動的SQL
 
実は、psqlでは「SQL手続き言語」と呼ばれる言語を呼び出すことができる のである。
本来は、SQL手続き言語を利用するためにはデータベースにその言語をインストールする必要があるが、
PL/pgSQL」という言語はデフォルトですべてのデータベースにインストールされている ので、手動でのインストールが不要である。
(ドキュメンテーション42.1節より)
 
psqlからPL/pgSQLを呼び出すには、次のようにする。

DO 
$$
[<<ラベル名>>]
[DECLARE (PL/pgSQL変数名 型[ {:=|=} 値];)*]
BEGIN
    処理内容
END [ラベル名];
$$;

2-2-1. DO

DO $$hoge$$は、PL/pgSQLで書かれたhogeを実行するためのコマンドである。

ドキュメンテーション(SQLコマンドDOについて)には次のような記述がある。

DO
DO — 無名コードブロックを実行します。
 
概要
DO [ LANGUAGE lang_name ] code
説明
DOは無名コードブロック、言い換えると、手続き言語内の一時的な無名関数を実行します。
 
コードブロックはあたかもパラメータを取らずにvoidを返す関数の本体かのように扱われます。 これは解析され、一回実行されます。
 
LANGUAGE句をコードブロックの前または後ろにつけることができます。
 
パラメータ
code
実行される手続き言語のコードです。 これは、CREATE FUNCTIONの場合と同様、文字列リテラルとして指定しなければなりません。 ドル記号による引用符付けの使用を勧めます。
 
lang_name
コードの作成に使用する手続き言語の名前です。 省略時のデフォルトはplpgsqlです。
 
注釈
使用される手続き言語は、CREATE EXTENSIONを使用して現在のデータベースにインストール済みでなければなりません。 plpgsqlはデフォルトでインストールされますが、他の言語はインストールされません。

DO $$hoge$$$$は、文字列の開始及び終了を意味するものである。
クオーテーションでなくドルを用いることで、文字列中にクオーテーションが出てくる際にエスケープする必要がなくなる。

2-2-2. DECLARE/BEGIN/END

2-2-1節で述べた通り、DO直後の$$と$$に囲まれたDECLARE、BEGIN、ENDは、PL/pgSQL言語で書かれたものとみなされる。
したがって、カーソルを定義するSQLコマンドDECLARE、トランザクションを囲むSQLコマンドBEGINやENDとは一切関係ない ことに注意。
DECLARE の直後に 変数名 型; あるいは変数名 型 := 値; を書くことで変数を宣言できる。
また、BEGINENDの間に、処理内容をPL/pgSQL言語のルールに則って記述する。

2-2-3. EXECUTEでSQLを利用する(まずは基本)

2-2-3-1. INTOもUSINGも使わない例

PL/pgSQLSQLを扱うには、EXECUTE '文字列としてのSQL文'を利用する。

まずは静的SQLでもできるような簡単な内容を試してみよう。
psqlに以下の内容を打ち込むと、データベースに「テスト」表がつくられる。

DO
$$
BEGIN
    EXECUTE 'DROP TABLE IF EXISTS テスト';
    EXECUTE 'CREATE TABLE テスト(col text)';
    EXECUTE 
    $inner$ 
        INSERT INTO テスト 
        VALUES('表「テスト」は、SQL中に埋め込んだPL/pgSQL言語の中にさらにSQLを埋め込んで作成した表である。')
    $inner$;
END;
$$;

SELECT * FROM テスト;

2-2-3-2. USINGを使う例(厄介)

EXECUTE '変数$1, $2, ...を使ったSQL文' USING 変数1, 変数2, ... を利用すると、実行するSQL文に変数を持たせることができることがある
2-2-3-1節と同じことをする例を次に示す。

DO
$$
DECLARE
    mytext text :='表「テスト」は、SQL中に埋め込んだPL/pgSQL言語の中にさらにSQLを埋め込んで作成した表である。';
BEGIN
    EXECUTE 'DROP TABLE IF EXISTS テスト';
    EXECUTE 'CREATE TABLE テスト(col text)';
    EXECUTE 'INSERT INTO テスト VALUES($1)' USING mytext;
END;
$$;

SELECT * FROM テスト;

但し、USINGにより変数を後からはめ込むこの方法は、使える場所が限られている。
(他の場所でやると、そもそも解釈されず、「$1」などが残ってしまう。)
したがって、次のように文字列連結||を使う方が無難であると思う。

DO
$$
DECLARE
    mytext text :='表「テスト」は、SQL中に埋め込んだPL/pgSQL言語の中にさらにSQLを埋め込んで作成した表である。';
BEGIN
    EXECUTE 'DROP TABLE IF EXISTS テスト';
    EXECUTE 'CREATE TABLE テスト(col text)';
    EXECUTE 'INSERT INTO テスト VALUES($$||mytext||$$)';
END;
$$;

SELECT * FROM テスト;

2-2-3-3. INTOを使う例

EXECUTE 'SQL文' INTO 結果を受け取る変数 を利用すると、SQL文により取得した結果の 先頭1行 を「結果を受け取る変数」に渡すことができる。
USINGと併用してもよい。
2-2-3-1節と同じことをする例を次に示す。

DO
$$
DECLARE
    mytext text :='表「テスト」は、SQL中に埋め込んだPL/pgSQL言語の中にさらにSQLを埋め込んで作成した表である。';
    myresult text;
BEGIN
    EXECUTE 'DROP TABLE IF EXISTS テスト';
    EXECUTE 'DROP TABLE IF EXISTS テスト2';
    EXECUTE 'CREATE TABLE テスト(col text)';
    EXECUTE 'CREATE TABLE テスト2(col text)';
    EXECUTE 'INSERT INTO テスト2 VALUES($1)' USING mytext;
    EXECUTE 'SELECT * FROM テスト2' INTO myresult;
    EXECUTE 'INSERT INTO テスト VALUES($1)' USING myresult ;
END;
$$;

SELECT * FROM テスト;

2-2-4. EXECUTEでSQLを利用する(psqlだけで行う動的SQLの実現)

では、第1章に示した表あから、表あ転置を生成する動的SQLを設計しよう。
第1章に示したコードと比較しながら読んでほしい。

(蛇足: FOR文も、実はPL/pgSQL言語のものであるため、第1章に示したコードは、--※を付した箇所の問題を訂正しただけでは、本当は動かない。)

DROP TABLE IF EXISTS 表あの列名を並べた複数行1列の表;
DROP TABLE IF EXISTS 表あ転置;
CREATE TABLE 表あの列名を並べた複数行1列の表(col text);
INSERT INTO 表あの列名を並べた複数行1列の表 VALUES ('列ア'),('列イ'),('列ウ');
CREATE TABLE 表あ転置 AS SELECT * FROM 表あの列名を並べた複数行1列の表;

-- 表あの列名を並べた複数行1列の表
--┌────┐
--│ col│
--┝━━━━┥
--│列ア│
--├────┤
--│列イ│
--├────┤
--│列ウ│
--└────┘

DO
$outer$
DECLARE
    表あの各列名 text;
BEGIN
    FOR 表あの各列名 IN SELECT col FROM 表あの列名を並べた複数行1列の表 
    LOOP
        EXECUTE 
        $$
            UPDATE 表あ転置 
            SET col=
            (
                SELECT $$||表あの各列名||$$ --※
                FROM 表あ 
                WHERE id=1
            ) 
            WHERE col='$$||表あの各列名||$$' --※
        $$ USING 表あの各列名;
    END LOOP;   
END;
$outer$;

SELECT * FROM 表あ転置;

このコードを動かすと、第1章に示した想定通りの「表あ転置」が得られる。

f:id:ec084:20200119205212p:plain
図2.2.4.1 動的SQLの比較図

3. 参考サイト:

https://improve-future.com/postgresql-execute-plpgsql-on-the-spot.html および、postgreSQLドキュメンテーション

学部時代の研究方針どうしようかな

・マスコミ論に精通する

↑これは前から決めてたやつ。マスコミを批判するならまずマスコミを知って、どの学者からも反論できないようにしないとならないから。

・決定木分析と説明可能AIを両方やってみて、両者の違いや長所短所を明らかにする

↑説明可能AIの方が、複雑な条件などに対応してくれやすいだろう。付随して、論理ゲートネットワークでもAIが作れると良いかも(説明がそのまま可能なので)

・(日本語における)基底ベクトルとなる単語を知る。

↑報道を統計学的に評価する際のパラメータに使いたい。

ネットの国語辞典へ無作為にアクセスして、ある単語が説明されているページを開き、その後説明文を各単語に分け、それぞれが説明されたページを開く。その説明文もまた各単語に分ける。これを繰り返した時、最終的に停止する(あるいは循環する)単語が、基底ベクトルとなるはずだ。

多分停止は名詞で起こる。これを基底名詞と呼ぼう。名詞はベクトルで表され、動詞はベクトル関数で表現できるかもしれない。

餌用ハムスターは愛されるべきか

おはようございます。

僕はハムスター大好きなのですが、

蛇にハムスターを生餌として与える動画を見てしまいました。

動機は単純。「ハムスターの生餌にされるミルワームも、ハムスターと全く同じ命である」ことを頭でしか理解できていない自分が許せなかったからです。

そのハムスターは人間にとても懐いていて、蛇に出される最後の最後まで可愛がられていたことが、その動画からよくわかりました。

愛情を注いでおいて、最後裏切るなんて、人間ってどこまで残酷なんだろうと思いつつ、「餌だから愛情を注がない」場合についても、考察してしまいました。

実はそれもまた人間のエゴなのかなと。

餌にされようがペットにされようが、ハムちゃんも、ミルワームも、蛇も、人間が死刑執行の判を押すその時まで、一つの幸せになるべき命として生きています。

「餌にも命がある。だったら、餌の人生の最後の一瞬だけ裏切ってしまうけど、それまでの間なるべくずぅっと幸せでいてもらいたい」

この考えが、本当に残酷なのだろうか。

生まれたときから「お前は餌。愛など必要ない」と割り切るほうが、餌ではあるものの一つの命である動物の権利を踏みにじっているような気もします。
だって、それって、裏切るときに心が苦しくなるのを少しでも緩和しようという、人間の勝手な都合なだけですから。
生後1年の餌用ハムスターには、(1440×365)-10分間の短い命の期間を、どうして幸せに過ごす権利がないのでしょうか。苦しむのは最期の10分だけにしてあげてはダメなのでしょうか?

もちろん、最終的な判断は飼い主さんたちに任せます。

「ペットやその餌の権利など飼い主個人の裁量の範囲内」という考えも尊重します。

家畜を育てている農家さんが、「家畜は道具」と割り切るのも否定しません。

ただ、もしも迷いを持ってしまう心優しい飼い主さんの方が、僕の記事を読んで、さらに考えを深めるきっかけになったらな、という思いで書いてます。