Clojureにおける同一性について

はじめに

Clojureの勉強のため、テスト駆動開発Clojureで写経しはじめました。
第1部は、他国通貨を扱った問題をTDDで順番に解決していくさまが紹介されます。

使用されている言語はjavaであり、オブジェクト指向でTDDが行われます。
一方Clojure関数型言語の仲間であり、オブジェクト指向でいう継承・メソッドといったものがないため、 実現しかたに考えることがあります。

テストをしていくにあたってClojureの面白いなおもったことがあったので残しておきます。

Javaの場合

まずjavaのテストコードの場合は以下の通り。
ドルクラスを作って、そこから5ドルのインスタンスを生成し、timesメソッドを使って倍化できることを確認するテストである。

public class MoneyTest {
    @Test
    public void testMultiplication() {
        Dollar five = new Dollar(5);
        five.times(2);
        assertEqual(10, five.amount);
        five.times(3);
        assertEqual(15, five.amount);
    }
}

注目すべきは、assertionのとき10や15という数値とfiveの属性であるamount(数値)を比較しており、 オブジェクト同士を比較しているわけではないということである。

Clojureの場合

もちろんClojureの場合も同じようにかける

(deftest multiple-test
  (testing "5ドルを2,3倍すれば10,15ドルになる"
    (let [five (d/->Dollar 5)]
      (is (= 10 (:amount (d/times five 2))))
      (is (= 15 (:amount (d/times five 3)))))))

しかし、数値比較と同じようにオブジェクト同士を比較してもClojureではテストが通る。

(deftest multiple-test
  (testing "5ドルを2,3倍すれば10,15ドルになる"
    (let [five (d/->Dollar 5)]
      (is (= (d/->Dollar 10) (d/times five 2)))
      (is (= (d/->Dollar 15) (d/times five 3))))))

javajavascriptの場合、同じ属性で同一値を持っているオブジェクトであっても 参照値が異なるため単純な比較はできず、deepEqualなどが必要になる。

それに対してClojureはオブジェクト同士を比較できる。 この理由は、Clojureはデータをデフォルトでイミュータブルで扱うことだろうと考えられる。

ある数値に別の数値を掛けたとしても、副作用で互いの数値が別の値に変わることがないように、 それがオブジェクトであってもClojureでは変わらない。だから、=で比較できる。

ではClojureでミュータブルなデータを使用した場合、どうなるのだろうか?

(def zero (atom 0))
(def Zero (atom 0))

(= zero Zero)
;=> false

@ をつけずに比較すると、一致しないことがわかる。

(def five (atom d/->Dollar 5))
(def ten (atom d/->Dollar 10))

(= (d/times @five 2) @ten)
;=> true

@ を使えば比較できる。 結局atom@ で逆参照したとき取得できた値はイミュータブルだからであろう。

デフォルトイミュータブルな世界が為せる面白い挙動なんだろうと感じた。

ClojureDocs=の記述を読めば、 そのとおりだとわかる。

Equality. Returns true if x equals y, false if not. Same as Java x.equals(y) except it also works for nil, and compares numbers and collections in a type-independent manner. Clojure's immutable data structures define equals() (and thus =) as a value, not an identity, comparison.

イミュータブルなデータ構造は、参照比較ではなく値比較してるというわけだ。 なるほど。

テスト駆動開発

テスト駆動開発

awkでvlookupみたいな表結合させる

はじめに

最近、bash, awk, jqにお世話になっております。

特にawkは使い始めたばかりなので、awkを使ってワンラインでexcelのvlookup的なことをやりたいときにどうすればよいのかわからなくて、excelで頑張ってたりしてました。

でもエンジニアならば、excelなんて使わなくてもコマンドラインでぱぱっと操作できたほうが格好いいじゃない?と思ったので挑戦してみます。

目標

以下の2つの表を想定する。

ユーザ一覧を格納した表(users.csv)

id name
-- ----
1  一郎
2  二郎
3  三郎
4  四郎

何かしらの条件をみたしたIDの表(condition.csv)

id
--
2
4

この2つの表から以下を得たい。

ゴール

id name condition
-- ---- ----
1  一郎 F
2  二郎 T
3  三郎 F
4  四郎 T

conditionにデータがあれば、usersの3列名にTを立て、なければFを立てたいわけです。

sqlでいえば、

SELECT
  id
  , name
  , CASE WHEN condition.id IS NULL THEN 'F' ELSE 'T' END
FROM users
LEFT JOIN CONDITION
ON users.id = CONDITION.id

みたいな操作を実行したいのだ。

回答

awk 'FNR==NR{a[$1]++; next} {print $0, (a[$1]) ? "T" : "F"}' condition.csv users.csv

ね、簡単だね!

説明

なぜ上記コマンドになるのかを説明しようと思う。

FNR==NR{a[$1]; next}

FNR==NRは条件を表現しており、{}内はその条件がマッチしたときの処理を意味する。

よって、FNR==NR, {}内に分けて説明する。

1. FNR==NRとについて

2つのファイルを読み込んで、それぞれNR,FNRと両者の行を出力すると、以下となる。

awk '{print NR, FNR, $0}' condition.csv users.csv
NR FNR $0
-- --- --
1   1   2
2   2   4
3   1   1 一郎
4   2   2 二郎
5   3   3 三郎
6   4   4 四郎

NRは2つのファイルをまとめて何行目を処理しているかを FNRは各ファイルの何行目を処理しているかを表している。

よって、はじめのファイル(condition.csv)が処理中のときは、FNR==NRが真となる。

2. {a[$1]++; next;}

突然登場した a は変数である。var, constなど使わずともいきなり変数を定義できるのである。さらに[$1]とつづくので、この変数は$1をkey名にもつ連想配列になる。
++は、1値を追加せよ(初期値0)という意味になる。

したがって、aという連想配列を宣言して、$1をkeyとして1増加せよという意味である。

next は、以降つづく処理をストップして次の行に進みなさいという意味である。{a[$1]++; next;} {…1} {…2} {…n}と続いたときに{…1}から{…n}の操作は実行されない。

3. {print $0, (a[$1]) ? "T" : "F"}' condition.csv users.csv

今までのを整理すると、 FNR==NR{a[$1]++; next} {...} は、

  1. はじめに読み込んだファイルに対してのみ真となり、
  2. 読み込んだ1列目をkeyとして1追加した連想配列を生成し、
  3. 次に続く処理{…}を実行しない

という意味になる。

残った {print $0, ....} は、FN==FNRが未達成ときに実行される。つまり、NR >= 3のとき、常に処理される。

$0で、users.csvの処理中の行を表示し、 a[$1]は、users.csvの1行目の値を、連想配列aのkeyに指定したときに真となるかどうかを確認して、TまたはFを出力している。

注意

users.csvとcondition.csvの順番を間違えると意味がなくなるので、注意が必要である。

awk 'FNR==NR{a[$1]++; next} {print $0, (a[$1]) ? "T" : "F"}' users.csv condition.csv

結果は、こちらの通り。理由はもうわかりますよね。

id Flag
-- --
2  T
4  T

このやり方を使うときは、必ずSQLでいうRihgt joinになってしまうわけである。users.csvが4行あるのに対して、conditionは2行しかないのでおかしな結果になってしまう。

もっとつぎへ

先程の使用したconditionで該当するユーザが、2,4だけであることが記載されていた。 しかし、そうでないときもあると思います。

たとえば、bmi.csvが、全ユーザのBMI情報を保存しており、25以上ユーザの名前とBMI情報が知りたいということもあります。

全ユーザとBMI情報が記載された表(bmi.csv)id = 2, 4は

id BMI
-- --
1  22
2  40
3  21
4  30
awk 'FNR==NR{a[$1]=$2; next} {if (a[$1] > 25) print $0, a[$1]}' bmi.csv users.csv

今回のように、bmi.csv, users.csvの行数が同じならば、LEFT JOIN的に記述することも可能となります。ちょっと冗長かな?

awk 'FNR==NR{a[$1]=$2; next} {if ($2 > 25) print $1, a[$1],  $2}' users.csv bmi.csv

まとめ

awkをつかって、特定の列をキーに2つの表を結合する方法を整理してみました。

ファイル内容によっては、ファイルの指定順序に気をつけて使ってみてください。

とはいうものの、csvが用意されていたらつかえるわけで、 結局excelスプレッドシートで渡されるようであれば使えないわけですね。

そのあたりはなんとかできないんかな汗

typescript導入したprivateなnpmパッケージの作り方

はじめに

開発の規模を大きくなってくると、共通化したコンポーネントを利用したいこともあると思います。
git submoduleをつかって共通部分を切り出すことも可能ですが、branchの変更忘れてしまうと反映されないので、個人的には好みではないです。 一方privateなnpmパッケージで実現することも可能だと思います。

npm private registoryを利用することも可能ですが、こちらの場合 US $7/月という月額料金がかかってしまいます。 一方、github privateは無料になったので、githubをつかってprivate npmパッケージを作ってみたいと思います。

また共通利用するなら型情報があったほうがありがたいので、typescriptを導入してみたいと思います。

目標

  1. github privateリポジトリの作成
  2. 他のリポジトリからimport確認
  3. npm version を利用してバージョン管理する

1. my private npm moduleの作成

npm moduleの作成

共通ライブラリのnpmパッケージを作っていきます。

  1. npm moduleの作成
mkdir myNpmModule
cd myNpmModule
git init
npm init -y

.gitignoreの設定は割愛します。

  1. typescript設定
npm i -D typescript
npx tsc --init

npx tsc --init で自動生成された.tsconfig.jsonに以下追加してコンパイル設定を修正する。

.tsconfig.json

 {
 ...
 "declaration": true,
 "sourceMap": true,
 "outDir": "./build",
 "rootDir": "./src",
 ...
}

rootDir でbuild対象のファイルを指定し、outDir でbuild後のファイル保存先を指定する。

declaration でビルド時にxxx.d.tsとして型定義ファイルを出力させる。
sourceMap でtsとjsの対応関係をつくっておく。

  1. package.json修正

共通ライブラリの設定を追加します。

package.json

{
  ...
  "main": "build/index.js",
  "files": [
    "build"
  ],
  "scripts": {
    "build": "tsc",
    "prepare": "npm run build"
  },
  ...
}

files ではパッケージ利用側にどのディレクトリ・ファイルをダウンロードさせるかを指定できる。
ただし、package.jsonとREADME.mdは指定しなくてもダウンロードされる。

main でパッケージ利用側がimportコマンドを使用したときにどのファイルをロードするのかを指定する。

scripts では、typescriptのbuildコマンドに加えて、 prepare を設定する。

prepare はパッケージ利用側が、パッケージをインストールする直前に実行させる処理を定義できる。
srcからビルドされたファイルを作成してくれるため、パッケージ管理側はbuildファイルをgit管理しなくてよい。

つまり、利用側で勝手にsrcからbuildディレクトリを生成してくれるため、.gitignoreにbuildディレクトリを指定しなくてよいわけとなります。

  1. 公開モジュール作成
mkdir src
touch src/Person.ts

src/index.ts

class Person {
  private name: string;
  private age: number;

  public constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  public call(): string {
      return this.name;
  }
}

export { Person };
git add .
git commit -m 'my first commit';

github private repoとして登録

githubページより、新しいリポジトリ作成から、privateにチェックをつけて作成する.

f:id:poppon555:20200405122203p:plain

git remote add origin git@github.com:karuta0825/myNpmModule.git
git push -u origin master

f:id:poppon555:20200405122217p:plain

buildディレクトリ管理しなくてよしです。

2. 他のリポジトリからimport確認

package.json

{
  "dependencies": {
   ...
   "myNpmModule": "git+ssh://git@github.com:karuta0825/myNpmModule.git",
   ...
  },
}

利用側のpackage.jsonのdepenenciesで、使用したいprivate moduleを指定する。git+ssh//に続けて、githubのclone or dowloaddに表示されているパスをコピーすればよき。

f:id:poppon555:20200405122242p:plain

npm i を実行すると、node_modulesに作成したprivate packageが作成されてます。

.
├── node_modules
│   ├── @types
│   ├── myNpmModule <-- 追加されてる
│   └── typescript
├── package-lock.json
├── package.json
├── sample.ts
└── tsconfig.json

node_modulesのmyNpmModuleのディレクトリ構成も設定したとおり。

myNpmModule
├── build
│   ├── index.d.ts
│   ├── index.js
│   └── index.js.map
└── package.json

package.jsonのfilesに指定したbuildディレクトリのみがあり、そこには、型定義ファイルのindex.d.tsとコンパイルされたjsとsourceMapの.mapファイルがある。

では、実際にインストールした共通パッケージを利用できるか確認してみます。

f:id:poppon555:20200405123146p:plain importでき、候補も表示されています。

3. my npm moduleをバージョン管理する

共通パッケージを使用していくならば、バージョン管理をしておくも考えられると思います。
ということで、npmのバージョン管理設定も説明しておきます。
とっても簡単。commit後に npm version を使用するだけです。

import dayjs from "dayjs";

class Person {
  private name: string;
  private age: number;
  public constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  public call(): string {
    return this.name;
  }

  // 追加
  public tellBirthYear(): string {
    return dayjs().subtract(this.age, "year").format("YYYY");
  }
}

git commitする

git add .
git commit -m 'add a method to Person class.'

npm versionのあとに、major, minor, patchのいずれかを指定できます。
majorだと、1.0.0 => 2.0.0
minorだと、1.0.0 => 1.1.0
patchだと、1.0.0 => 1.0.1
にバージョンが変わる。

package.jsonのversionや、git logが変更されていること確認できます。

npm version minor

git log --oneline
// 6a0bb79 1.1.0
// 3d505ff add a method to Person class.
// 351675f my first commit

package.json

{
  ...
  "version": "1.1.0",
  ...
}

git pushして、利用側でnpm updateをして確認すると、

npm update

+ myNpmModule@1.1.0
added 1 package from 1 contributor, updated 3 packages and audited 5 packages in 19.281s
found 0 vulnerabilities

1.1.0のmyNpmModuleが利用できるようになりました。

実際下図の通り、追加したメソッドが候補に上がるようになります。

f:id:poppon555:20200405122235p:plain

まとめ

github privateを利用して、typescript導入npmパッケージを作成しました。
また npm version を利用してバージョン管理する方法をかんたんに説明しました。

ライブラリとして使用されるならば、以下のことも今後対応方法見つけられたらと思ってます。

  • パッケージのmin化 現状だとsrcのディレクトリ構成のままbuildディレクトができるので、一本にまとめたほうがよい。
  • material-uiやlodashのようにmyNpmModue/Personみたいな感じでロードファイルを指定できる

参考にしたサイト

https://dev.classmethod.jp/articles/private-npm-modules-to-package-json/https://medium.com/cameron-nokes/the-30-second-guide-to-publishing-a-typescript-package-to-npm-89d93ff7bccd

goで継承をつかう

はじめに

goを勉強始めるとjavaなどで学習してきたオブジェクト指向の考え方を違う場面に出くわす。
たとえば、goでは継承できないことである。
しかし委譲を使えば、同じことを実現できるのでその整理をしようと思う。

仕様整理

下図のような関係を考える。 Personクラスはスーパークラスで、Manクラスをそのサブクラスという関係である。
Personクラスは、名前と年齢をメンバ変数にもち、getName()で名前を取得でき、oldAge()でひとつ年を増やせるメソッドをもつ。
一方Manクラスは、性別("M", "S")をもち、男ならばcall()メソッドで"Mr. 名前"と返す。

f:id:poppon555:20191222141722p:plain

goで実装

goではクラス/メソッドではなく、struct/receiverと呼ぶが、クラスを使う。

package main

// スーパークラス
type Person struct {
  name string
  age  int
}

// コンストラクタ
func newPerson(name string, age int) *Person {
  return &Person{
    name,
    age,
  }
}

func (p *Person) getName() string {
  return p.name
}

func (p *Person) oldAge() {
  p.age = p.age + 1
}

// サブクラス
type Man struct {
  // (1) Personポインタ 
  *Person
  sex string
}

// コンストラクタ
func NewMan(name string, age int) *Man {
  return &Man{
    // (2)  ポインタ変数にインスタンスを格納
    Person: newPerson(name, age),
    sex:    "M",
  }
}

func (m *Man) call() string {
  return "Mr." + m.name
}

(1) Personポインタを渡すことでManインスタンスから直にPersonインスタンスにアクセスできる 。
man.Person.getName() ではなく、man.getName() が可能となる。
Personの部分をショートカットしてgetName()が呼べるので、あたかも継承してるかのように使える。
もし、person Person とメンバ変数名を明記すると、man.Person.getName()でアクセスしないといけないので継承ぽさは消える。

(2) サブクラスでスーパクラスのインスタンスを作成するとき、*を外したPersonにインスタンスを格納する。

testコードでうまくいくことを確認できる

package main

import "testing"

func TestNewMan(t *testing.T) {

  t.Run("スーパクラスのメソッドgetName()が呼べる", func(t *testing.T) {
    man := NewMan("adam", 10)
    if man.getName() != "adam" {
      t.Fatal("err")
    }
  })

  t.Run("スーパクラスのメソッドoldAge()が呼べる", func(t *testing.T) {
    man := NewMan("adam", 10)
    man.oldAge()
    if man.age != 11 {
      t.Fatal("err")
    }
  })

  t.Run("サブクラスからスーパークラスのメンバ変数nameにアクセスできる", func(t *testing.T) {
    man := NewMan("adam", 10)
    if man.name != "adam" {
      t.Fatal("err")
    }
  })

  t.Run("サブクラスの定義メソッドcall()が呼べてMr.adamと返す", func(t *testign.T) {
    man := NewMan("adam", 10)
    if man.call() != "Mr.adam" {
      t.Fatal("err")
    }
  })
}
PASS
ok      github.com/karuta0825/study 0.262s

最後に

オブジェクト指向脳の人が、goで継承ぽいものを使う方法を説明しました。
goはコンパイル言語なのに、スクリプト言語のように開発すすめるのはいい言語だなと思います。
javaのライブラリ管理とか面倒だもの。
もっともgenericsが使えないのは、悔しいのですが。。。
そんな感じでgoらしさというのがまだまだわからずなのですが、来年はgoへの理解を深めていきたい!

述語論理をつかったSQL

はじめに

僕は、SQLが苦手である。 普段使い慣れている言語とは別の考えがSQLには必要なのであろうと痛感している。

ということで、達人に学ぶ SQL徹底指南書を使って最近SQLの考え方を学習中。
今回は、SQLで数列を扱うの備忘録を残す。

達人に学ぶ SQL徹底指南書 (CodeZine BOOKS)

達人に学ぶ SQL徹底指南書 (CodeZine BOOKS)

問題

以下の座席表から空席が 3 つ連続している部分を求めろ たとえば、(3,4,5), (7,8,9)となる。

id status
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

データの準備

CREATE TABLE seats
(
    seat   INT NOT NULL,
    status VARCHAR(10),
    PRIMARY KEY (seat)
);

INSERT INTO seats
VALUES (1, ""),
       (2, ""),
       (3, ""),
       (4, ""),
       (5, ""),
       (6, ""),
       (7, ""),
       (8, ""),
       (9, ""),
       (10, ""),
       (11, ""),
       (12, ""),
       (13, ""),
       (14, ""),
       (15, "");

回答

SELECT
  s1.seat as '始点',
  s2.seat as '終点'
FROM seats s1,
     seats s2
WHERE s1.seat + 2 = s2.seat
  AND NOT exists(
        SELECT * FROM seats s3 WHERE (s3.seat BETWEEN s1.seat AND s2.seat) AND (s3.status <> '')
    );
始点 終点
3 5
7 9
8 10
9 11

考え方

始点、終点を s1, s2 の自己結合によって得、その間に s3 のすべての status が空を満たすというクエリをつくる方針をとる。

すべての〇〇が XX という条件を満たすというクエリを記述するのにAll演算子を使おうと考えるが、MySQLにはAll演算子は用意されていない。

なので、EXSITS演算子を利用して All と同じ意味をもつ式を記述することになる。
いきなりだが、以下は述語論理から成立する式である。

1. ∀xF(x) => ¬∃x¬F(x)
2. ∃xF(x) => ¬∀x¬F(x)

∀ は All を意味するので、今回は 1 のパターンである。
この考え方を日本語で整理すると、
始点・終点間のすべての座席が空である

始点・終点間に空ではない(a)座席は存在しない(b)
と表現可能となる。

(a), (b)をクエリで記述すると

  1. SELECT * FROM seats s3 WHERE (s3.seat BETWEEN s1.seat AND s2.seat) AND (s3.status <> '空')
  2. NOT exists(1.)

となる。

よって、上記回答の通りになる。

おわりに

にしてもSQLはクエリに悩むよりも、データの準備が大変だ。。。 もっと楽にデータ準備できないものかと感じる

DataGripでクエリの大文字化設定

SQLのエディタ?は、Datagripを使うようになった(勉強中)

その理由は、 1. キーバインドがカスタマイズできる 2. 補完が最強 3. フォーマット機能が優秀 にある。

さらに、クエリを自動で大文字化してくれることがわかったので 載せておこう。

Datagrip > Preferences > Editor > Code Style > SQL > データベース にあるcaseタブから設定できる

f:id:poppon555:20191014181802p:plain

これが、自動フォーマット(Cmd + option + L)させると f:id:poppon555:20191014182332p:plain

こうなる f:id:poppon555:20191014182427p:plain

ありがたや。 DataGripもっと使いこなせるようになりたい。。。

SQLの集合演算整理

はじめに

SQL は集合的思考が必要ということだが、和集合や差集合など求める集合演算の仕方は複数存在するようなので、一度整理しようと思う。

  1. 集合演算子形式
  2. exist を使った述語論理形式
  3. (外部・内部)結合形式
    3パターンで考えてみた。

データの準備

まずデータベースにデータを準備する。 今回は、英語クラスと数学クラスを受講している生徒の情報を記録したテーブルを用意して考える。

-- 英語クラステーブル作成
CREATE TABLE `schemaName`.`english` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NULL,
  PRIMARY KEY (`id`));

-- データ挿入
insert into english value(1, '一浪'), (2, '二浪'), (3, '三浪');

-- 数学クラステーブル作成
CREATE TABLE `schemaName`.`math` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NULL,
PRIMARY KEY (`id`));

-- データ挿入
insert into math value(1, '三浪'), (2, '四浪'), (3, '五浪');

和集合

英語あるいは数学の少なくとも一方を受けている生徒をみつける

  1. 集合演算子(MySQL は不可能)
  select name from english union select name from math;
  1. 述語論理
不可能...
  1. 外部結合
不可能...

差集合

英語を受講しているが、数学は受講していない生徒をみつける

  1. 集合演算子(MySQL は不可能)
select * from english except math;
  1. 述語論理
select * from english e where not exists(select * from math m where e.name = m.name);
  1. 結合
select * from english e left join math m on e.name = m.name where m.name is null;

積集合

英語と数学両方を受講している生徒をみつける

  1. 集合演算子(MySQL は不可能)
select * from english intersect math
  1. 述語論理
select * from english e where exists(select * from math m where e.name = m.name);
  1. 結合
select e.name from english e inner join math m on e.name = m.name;

まとめ

MySQL は、和集合除いて集合演算子は使えないみたいですね。
その代わりに結合形式や述語論理形式をつかった表現力が必要になったのだろうと思う。

なぜ述語論理形式で集合演算が可能なのかなと考えてみると、existsが 引数に集合をとれることと、さらに相関サブクエリによって2つの集合の要素(行)同士を比較することができるからなのであろうということはちょっとした気付きになりました。