ゆくゆくは有へと

おかゆ/彼ノ∅有生 の雑記

おかゆ大反省会会場

www.adventar.org

はじめに

iuk.hateblo.jp

数か月くらい放置しきった「はじロジ」の続きを書いていきたい。「つよくてコンティニュー」

数か月くらい放置しきった「はじロジ」の続きを書いていきたい。「つよくてコンティニュー」

2016-01-04

はい(はいじゃないが)。

1年越しで未完(🍊)という mabla な事態になりそうですが、「まあそんなみんな期待してないっしょ(へらへら)」って内心思ってました。

ほんとうに申し訳ありませんでした fau'usai

というわけで、2016年におかゆのアホがやり残したことをここに書き連ね、もしおかゆがやり損なっても他の誰かが仕上げてくれることを期待して2017年へのおかゆに牽制しようと思います。

はじめてのロジバン第二版

はよ書けよ。

はじめてのロジバン 第2版 - はじめてのロジバン第2版

lojban wavelesson 日本語訳のレイアウト

参考:Google グループ

これは2016年になって尚やってないことですね。

2014年の終わりに、私のブログ「味噌煮込みロジバン」で日本語訳していた lojban wavelesson を le uitki に移行しました。

lojban wavelessons 日本語版 - La Lojban

とはいえまだレイアウトがぐちゃぐちゃのままで「いつかやんないとな~」って思いながら2年経ちましたね……。

確かこの翻訳の底本は2013年秋頃の英語版で、その頃からちょっと(割と?)内容が変更されてたりするので、その辺りも吸収しないといけない。

「まあ誰かやってくれるやろ」では誰もやらないということのいい事例ですね(戒め)

救世主

PJCG gimste Lv2

PJCG gimste lv2 プロジェクト - pre

iuk.hateblo.jp

lela.iúk.tánxe - PJCG gimste

PJCG(日本ロジバン若手の会)で初学者用の gismu リストを作ろうという企画の第二弾。

Evgeni シソーラスを少し改変したものを使って再編しようという流れで、そのシソーラスの改変の途中で止まってしまっている…。

そうこうしてる内に、新しいシソーラス(?)が誕生したようだ(おそらく glekiさんが手がけたもの):

semantic classes of verbs - La Lojban

新しい(とはいえ2013年ですが、Evgeniのよりは新しい)ということもあり、こっちを参考にしたほうがいいかもしれないと最近は思っている。 ひとまずは日本語訳からですね。

ロジバンアスペクト

iuk.hateblo.jp

前議論しかしてない…。

おかゆとロジバンのアスペクトの戦いはこれに始まるわけでなく、「味噌煮込みロジバン」でもかなりの記事がある:

味噌煮込みロジバン: アスペクト論

これらの雑多な考察をちゃんとまとめようかなとしていて、停滞中である。

zi'o と tag の話

iuk.hateblo.jp

iuk.hateblo.jp

iuk.hateblo.jp

zi'o と tag を対としてみてスッキリした見解を得たいという試みも未だ終わっていませんね。

ちなみにこの話は結構前からやっていたようです: 味噌煮込みロジバン: zi'o と sumtcita sumtiを対とみる

by standard まわりの修正

分かりにくいsumti位相談所

辞書定義における "by standard" などの日本語訳が結構ブレてて、それを統一したいなと思っていた事案。結局やってない。

ロジバンにおける化合物命名

lela.iúk.tánxe - lojbo xukmi bo cmene -ロジバンにおける化合物命名-

これも途中で止まってしまっている…。まあこれは優先度低いかな。


というわけで、結局やってない祭りでした。独自研究系はともかくとして、おかゆはとりあえず早くはじめてのロジバン第二版を仕上げないといけないですね。反省します。

「実践Python3」の Template method パターンが

なんかこれで確信したけど、散々過去に悩んだコレも「実践Python3」の捉え方が変なんだろうってことにしておく:

iuk.hateblo.jp

さておき

Template method パターンと Factory method パターンの語の(Pythonにおける)意味がやっと分かってきたからメモ。

この2つは名前の通り、そういうメソッドを作っておこう!っていうパターンで、それぞれのメソッドが「テンプレート的」か「ファクトリー的」かってことですね。

Template method は そのクラスがもつ他の抽象メソッド を使って書かれた、より大きな操作をするメソッド。これは ABCで実装されて、その具象クラスは抽象メソッドを実装すると同時に、この template method も使えるようになるって寸法よ!素敵! Template methodパターンは、ABCじゃなくて基底クラスを使ってもいい。その場合は、template method 内で使われる他のメソッドというのが抽象メソッドではなくてデフォルト実装されたメソッドになる。

一方、Factory method は、ABC では抽象メソッドとして定義されるメソッド。で、具象クラスにおいてファクトリーとして働く、つまり、特定のインターフェースを備えたオブジェクトを吐き出すメソッドとして実装される。

「なんで特定のインターフェイスを備えたやつじゃないといかんのか?」ということについては、他のところでそういうインターフェース持ってる前提で色々書いちゃうからでしょうな。

ということを考えると、Template method で使う 抽象メソッド の中には大雑把には Factory method も含まれる。ただし厳密にいえば、それは抽象メソッドではなくて抽象オブジェクト(具体的にそれがどんなものか決まってない(インターフェースしか分からん)という意味での抽象)なので、ちゃんと区別するに値はするのかな?という感じ。

生成されるオブジェクトのインターフェースに従ってあーだこーだするという点で template method パターンよりも手の込んだ感じしますよね。そういう感じよ(?)

使用する抽象的なのが自身のクラス内のメソッドで完結するのが template method パターン、インターフェースの要請だけしておいて、外部からオブジェクトもらってこようとするのが Factory method パターンかなと理解した。

ただ、この書き方だと、「Template method パターンにさらに Factory method パターンを適用したもの」という側面でしか Factory method パターンを描写できてない気がする。

でも、「Template Methodパターンをオブジェクト生成の場面に適応させたデザインパターンと言えます」*1 らしいので、これでいいのかもしれない。

いずれにせよ、Template/Factory method パターンどちらも、「上位クラスが下位クラスに部品(メソッド)の実装丸投げするパターン」ってことだろう。

そういう意味で、このパターンはクラスとクラスのパターン(クラスパターン)らしい*2

他のパターンとのつながり

ちょっと変えたら別のパターンになるってのが結構あるっぽい。

Strategy パターン

一定の処理とか関数をアウトソーシング化してしまおうぜっていうパターン。Template/Factory method パターンでは、サブクラスの定義で自由度がなくなってしまう(メソッドとかが一意に定まる)けど、Strategy パターンは処理・関数を引数化してしまうことで自由度をあげられる。

あと、クラスとクラスのパターンから、オブジェクトとオブジェクトのパターン(だよね?)に持ち込めるので、ABCとか使わなくていいという意味では楽。

Factory method パターンにおけるサブクラスの実装バリエーションが増えてきたら Strategy パターンに移行すればいいんじゃないかな。たとえば Factory method を3種定義するABCを作るとして、それぞれのメソッドの実装(どのクラスのインスタンスをつくるか)の通りが5つあるとしたら、完全に網羅するには 53 = 125 ものサブクラスが必要になるわけで、そうなると早々に引数化してしまったほうがいい。

もちろん引数化するというのは無用な柔軟さを与えてしまうことにもなるので、そこのところはトレードオフでしょうね。

Bridge パターン

UML図的には Strategy パターンと同じらしい*3

Strategy パターンの UML クラス図は Bridge パターンのものと同じである。しかし、これら二つのデザインパターンはその意図が同じではない。Strategy パターンは 振る舞い に対するパターンであるが、Bridge パターンは 構造 に対するパターンである。

こういう、絵面的には同じだけど意図が違うってパターン割とあるっぽいよね。

Strategy パターンは「振る舞いのアウトソーシング」で、Bridge パターンは割と単純に「モノの分離」。

抽象メソッドの実装という形ではなくて、欲しいモノは引数としてもらおうというパターン。

「xを食べる男」抽象クラスを具象化して「リンゴを食べる男」「バナナを食べる男」クラスを作るんじゃなくて、「xを食べる男」クラスを作っておいて、そのインスタンスの引数に食べ物をとって「リンゴを食べる男」「バナナを食べる男」インスタンス作ろうって話(たぶん)。

いざ「xを食べる男」クラスのサブクラスを作ろうとしたときに対応しやすいのが動機。

Abstract Factory パターン

Factory Method と Abstract Factory の違いを順に理解する | Futurismo より:

Abstract Factory は Factory Method のカプセル化に過ぎないことを示す.

構造的な側面だけに着目すれば、たしかにカプセル化にすぎないかなと思うけど、デザインパターンは構造的な側面だけに注目して理解するものでもなさそうだしね(Strategy/Bridgeパターン然り)。

Factory method パターンでは、その抽象なファクトリーメソッドを使って、より大きい処理をしようとするのはそのクラス自身(つまりそういうメソッドをもつ)。一方、Abstract Factory パターンでは、そういう大きい処理をしようとするのはクラスではなくて外部の誰か。そういう意味で、Abstract Factoryパターンはオブジェクトパターン(らしい)。

ファクトリー関数の利点は、共通(固定した)名前で(所定の)インスタンス作れることなわけだから、誰かに使ってもらわないと(しかも、どのインスタンスがきてもいいようなある程度の普遍性をもった処理の中で)あまり生かされているとは言えない。

Factory method パターンは最終的に自分の中に書いた処理のためのパターンで、Abstract Factory パターンは外部の処理のためのパターンだね(繰り返し)。

とはいえ、その意図を除けば、一連の整合性のあるファクトリー関数(メソッド)群をまとめてしまおうというのが Abstract Factory パターン。

結局、ふたをあければファクトリー関数がそこにあるわけで、共通のインターフェースで異なるファクトリー群が使えるというのが Abstract Factory パターンの美味しささね。

とはいえ、「実践Python3」で Template method パターン実演なのに、template method もってない抽象クラスもってきたりするのは草生えたけど、まあ確かに「何もしない」ことも、共通処理の1つに入ると言われれば…首肯せざるをえない。

そういう意味では、抽象クラスにfactory method が使われている共通処理のないようなものもまた factory method パターンに則ってると言える。そう考えると、factory method をカプセル化したものが Abstract factory だ、というのはあながち間違いでもないのかもしれない。

コーラブルオブジェクトをイテレータ化する

「実践Python3」の一部事項のメモ

__call__ メソッドを実装したクラスのインスタンスなり、関数なり、バウンドメソッドなり。コーラブルオブジェクトは呼び出し(とそのときに渡される引数)に応じて何か値を返したり、どっかの内部状態(IOとか自身の属性とか)を変えたりしてくれる。

普通はコーラブルオブジェクトをイテレータ化しようとは思わないというか、イテレータ化の目処が立たないですが、呼び出しによって自身の内部状態を変えながら値を返してくれるマン(カウンターのような)なら少しその気がありますね。

# coding=utf-8

class C:
    def __init__(self):
        self.i = 0

    def count(self):
        print("exec.", end=" ")
        self.i += 1
        return self.i

この c.countc.i が9になるまで回したいとき、まず思いつくのは for 文です:

c = C()

for _ in range(9):
    print(c.count())

ただ、これは「値を 9 まで回す」、ではなくて、「c.count を 9回実行する」なので、少し不安定です。

なら、whileを使えばいいような?

c = C()

while c.i != 9:
    print(c.count())

いい感じですが、i がプライベート属性だった場合はこの方法は使えません。ちょっと回りくどいですが、

c = C()
i = 0
while i != 9:
    i = c.count()
    print(i)

としてやれば確かに機能します。

結局、for では単純に回数しか、while では別変数や属性を用いて値を監視しながら回すことになります。

もっとこう、c.count の吐き出した値だけを見てよしなに回せたりしないかなあ?

c = C()
for elem in iter(c.count, 10):
    print(elem)

iter関数は引数を2つ取った場合、コーラブルイテレータをつくりだすそう。x1にコーラブルオブジェクトを、x2にセンチネル値(その値が返されたらイテレータを閉めるような値)を入れます。

type を見てみると、<class 'callable_iterator'> でした。

上の実行結果です:

exec. 1
exec. 2
exec. 3
exec. 4
exec. 5
exec. 6
exec. 7
exec. 8
exec. 9
exec.

コーラブルイテレータは、next で次の要素を吐き出す際に、まず第一引数に取ったコーラブルオブジェクトを呼び出す。

今回は c.count が呼び出され、print("exec.", end=" ")が実行されたあと、値が返される。

コーラブルオブジェクトが返した値をセンチネル値と比較し、等しくなければnextの値として採用して吐き出す。

もしリターン値がセンチネル値に一致した場合、nextStopIterationを raise する。

上の結果では、10回目のnextで呼び出されたコーラブルオブジェクトのprint文は実行されていますが、その返り値(10)はセンチネル値と等しいためにelemに渡されずイテレータが止まります。

2. 組み込み関数 — Python 3.5.2 ドキュメント より:

第二引数 sentinel が与えられているなら、 object は呼び出し可能オブジェクトでなければなりません。この場合に生成されるイテレータは、 __next__() を呼ぶ毎に object を引数無しで呼び出します。返された値が sentinel と等しければ、 StopIteration が送出され、そうでなければ、戻り値がそのまま返されます。

コーラブルオブジェクトはnextの度に引数無しで呼び出されるので、呼び出す際に何か値を渡したいというときにはもう少し工夫しないといけない。

イテレート中、同じ引数を与えたい場合

lambda: callable_obj(a, b, c) みたくすればOK。たとえばさっきのクラスに

def count_add(self, j):
    self.i += 1
    return self.i + j

を追加して、j=5イテレータを回したいとしたら、

for elem in iter(lambda: C().count_add(5), 9):
    ...

とすればいい。

イテレータ中、異なる引数を与えたい場合

いやそれもうその引数をリストにして for 文回したほうがよくない?

c = C()
for j in [1, 3, 2, 5, 2]:
    tmp = c.count_add(j)
    if tmp == 9:
        break
    else:
        print(tmp)
else:
    raise ValueError("not become 9.")

forelse は完全に回りきっちゃったときにだけ実行される部分ですな


こう、なんか使えそうで使えなさそうでちょっと使えそうな感じですね。

センチネル値にちょうど一致しない限り止まってくれないので、もしその値を飛び越えちゃったら無限ループします。

でも、コーラブルオブジェクトがStopIterationを上げてくれたときは、それをnextが受け継いで吐いてくれるので止まります。

ただ、コーラブルオブジェクト内にそういう制御を書くなら(i.e. 元々イテレータ用にコーラブルオブジェクトを書くくらいなら)、ジェネレータ使ったほうが…って気もする…。

「いや、lambda: next(generator) とセンチネル値で、途中で止まるようなイテレータを作れるじゃん!」

itertools.takewhile でよくない?」

10.1. itertools — 効率的なループ実行のためのイテレータ生成関数 — Python 3.5.2 ドキュメント

ジェネレータ使ってるなら、素直にifでセンチネル値一致の分岐させたほうがいいと思うけど…。

あっ、これあれか、組み込みのイテレータプロトコルを使わない場合のイテレータの作り方に相当するのか?(とすれば、組み込みでイテレータプロトコルのある(さらにいえばもっと簡単にジェネレータまである)Pythonにはあんまり必要ないのかもしれない(もっと内部のところで使ってたりするのかな?))

括弧を憎みすぎた人間の末路(Pythonで関数合成)

本実装はともかく、ちょっと試しにprintしてみたいときあるじゃないですか。そういうときに、

print(f(g(h(1, 2))))

などと、括弧が多すぎて死にそうになることがある。

まあこれくらい我慢しろっても思うんだけど、ちょっと機嫌が悪くてブチ切れた

魔法少女になって括弧を消し去りたいッ!

せめて関数合成を

functoolsモジュールに関数合成のためのツールあるかなと思ったらなかった…作るしかない…。

というわけで作ったのがこれ。Composableクラス。

class Composable:
    def __init__(self, callable_obj):
        self.__callable = callable_obj

    def __call__(self, *args, **kwds):
        return self.__callable(*args, **kwds)

    def __matmul__(self, other):
        @Composable
        def composite_function(*args, **kwds):
            return self(other(*args, **kwds))
        return composite_function

    def __imatmul__(self, other):
        return self @ other
        
    def __getattr__(self, name):
        return getattr(self.__callable, name)

Composable を関数デコレータとして使うことで@演算子を2関数に使うことができるようになる。

@Composable
def add_10(n):
    return 10 + n

@Composable
def product_3(n):
    return 3 * n

print((add_10 @ product_3)(10)) # => 40

これで入れ子の括弧はかなり無くなる!嬉しさがある。

def chain(*func):
    composite = Composable(lambda x: x)
    for func in func_rest:
        composite @= Composable(func)
    return composite

print(chain(add_10, add_10, product_3)(3)) # => 29

ついでにこういうのも作っておいたので、これでなんとかしてもいい。

殲滅させたい

でも括弧を憎みすぎて、もっと括弧を減らしたかった。というわけで魔改造したのがこれ。

class Composable:
    def __init__(self, callable_obj):
        self.__callable = callable_obj

    def __call__(self, *args, **kwds):
        return self.__callable(*args, **kwds)

    def __matmul__(self, other):
        @Composable
        def composite_function(*args, **kwds):
            return self(other(*args, **kwds))
        return composite_function

    def __imatmul__(self, other):
        return self @ other

    def __rshift__(self, other):
        return other(self)

    def __rrshift__(self, other):
        return self(other)

    def __mod__(self, other):
        return self.apply(other)

    def apply(self, *applied_args, **applied_kwds):
        @Composable
        def applied_function(*args, **kwds):
            return self(*applied_args, *args, **applied_kwds, **kwds)
        return applied_function

    def __getattr__(self, name):
        return getattr(self.__callable, name)
@Composable
def xruti(func):
    return func()

@Composable
def add_10(n):
    return 10 + n

@Composable
def product_3(n):
    return 3 * n

@Composable
def add(n, m):
    return n + m

@Composable
def sub(n, m):
    return n - m

@Composable
def product(n, m):
    return n * m


print((sub % 200 @ add % 50 @ product % 4 @ add % 5)(10)) #(1)
print(10 >> add % 5 >> product % 4 >> add % 50 >> sub % 200) #(2)
print(sub % 5 % 1 >> xruti) #(3)

多分3日後の自分は理解できない

まず (1) はさっきと同じで関数合成してるけど、部分適用の % をぶち作った。

(2)では、>> を導入。

こういうときって、「引数にほにゃして、もにゃして、ぽちゃして、どみゃして」って思考なのに関数で書くと逆になるのがイヤ!

ならメソッドチェーンで書けばいいんだけど、Python は絶妙にメソッドチェーンできなくて辛いし…。

なので、>>に左の値を右の関数にぶち込んで値を返すということをしてもらって、括弧をなくしました。

(3) は % がちょっとポンコツで、全部値が埋まってても値を返さずになお関数オブジェクトを返してくるので、xruti関数(ロジバンで "return")を使って値を返すように改造してある。

「キーワード引数」?知らん!!!!!!!!!

お気を確かに

確かに関数で包みまくってると嫌になりますが、まあ流石に上はやり過ぎ感がある。

もう少し穏当にするならまあこんな感じだろうか。

from functools import reduce
def compose(callables):
    callables.reverse()
    def apply(*args, **kwds):
        accum = callables.pop(0)(*args, **kwds)
        return reduce((lambda x, f: f(x)), callables, accum)
    return apply
print(compose([product_3, add_10, product_3, add_10])(10)) #=> 210

引数はアンパックしてもいいかもね。まあこれくらいが平和かも

「実践Python3」の Factory method がもやもやする

題のごとし。

ググって理解したところによれば、factory method って「具象クラスごとに、同種手続き(たいてい抽象クラスで定義されてるメソッド)時に呼び出されるクラス可変的なコンストラクタをメソッド化したもの(で、具象クラス実装時に中身が書かれるもの)」っぽい。

で、「同種手続き」というのを抽象クラスのレベルで書いちゃうというのがどうやら template method パティーンらしい。ググった感じだと、factory method パティーンは template method パティーンに更に組み込んで使うっぽい。

んだけど、「実践Python3」の場合、この「同種手続き」というのに共通性がほとんどなくて、「えぇ…」ってなっちゃった。

たぶん、Board の上に駒を置いていく操作、populate_board が同種手続き(factory method パティーンでいうanOperation?)に相当しそうなんだけど、そもそも anOperation は具象クラスでオーバーライドしない(したら意味ないし)から、「???」って感じだ。

でも、create_piece は「ファクトリー関数」らしくて、これのおかげで、populate_board は「「ファクトリーメソッド」と呼ぶことができる」らしい。そうすると、同書においては、__init__ が anOperation に相当して、populate_board がファクトリーメソッドということになるのだろうか…。

Factory Method パターン - Wikipedia 見てたら、

factoryMethodは、[...] パラメータを取り、それによって生成するクラスを変えることもある。

ともあるので、それならむしろ create_piecefactoryMethod では……?

そう考えると、AbstractBoard はこうしたくなる(ちなみに個人的な趣味で ABC にしておいた)

from abc import ABCMeta, abstractmethod

class AbstractBoard(metaclass=ABCMeta):

    def __init__(self, rows, columns):
        self.rows = rows
        self.columns = columns
        self.board = [[None for _ in range(columns)] for _ in range(rows)]
        self.populate_board()

    def populate_board(self):
        for row in range(self.rows):
            for column in range(self.columns):
                self.board[row][column] = self.create_piece(row, column)

    @abstractmethod
    def create_piece(row, column):
        pass

    def __str__(self):
        ...

populate_board は各マスに対して、サブクラスで実装されたcreate_piece(row, column) を呼び出して相応しいインスタンスをもらえる。populate_boardanOperation に相当するはずで、個別の具象ボードにおいて共通のプロセス。

ほんで、ChessBoard 具象クラスでは

class ChessBoard(AbstractBoard):

    def __init__(self):
        super().__init__(8, 8)

    def create_piece(self, row, column):
        piece_dict = self._init_piece_map()
        name_dict = {PAWN: "ChessPawn", ROOK: "ChessRook",
                     KNIGHT: "ChessKnight", BISHOP: "ChessBishop",
                     KING: "ChessKing", QUEEN: "ChessQueen"}
        if (row, column) in piece_dict:
            kind, color = piece_dict[(row, column)]
            name = name_dict[kind]
            return globals()[color + name]()
        else:
            return None

    def _init_piece_map(self):
        colors = (((0, 1), BLACK), ((6, 7), WHITE))
        kinds = (((0, 7), ROOK), ((1, 6), KNIGHT), ((2, 5), BISHOP),
                 ((3,), QUEEN), ((4,), KING), ((0, 1, 2, 3, 4, 5, 6, 7), PAWN))
        piece_dict = {(row, column): (kind, color)
                      for row, color in colors for column, kind in kinds}
        return piece_dict

ヘルパーメソッドとして _init_piece_map を用意した。_init_piece_map で初期状態における各コマの座標の辞書を返してもらって、今の位置がその辞書にあれば相応のインスタンスを渡してあげるという形。

んん…でもこれならもういっそこうした方が…

from abc import ABCMeta, abstractmethod

class AbstractBoard(metaclass=ABCMeta):

    def __init__(self, rows, columns):
        self.rows = rows
        self.columns = columns
        self.board = [[None for _ in range(columns)] for _ in range(rows)]
        self.populate_board()

    def populate_board(self):
        for row in range(self.rows):
            for column in range(self.columns):
                self.board[row][column] = self.create_piece(row, column)

    def create_piece(self, row, column):
        piece_dict = self.initial_piece_map()
        name_dict = self.get_name_dict()
        if (row, column) in piece_dict:
            kind, color = piece_dict[(row, column)]
            name = name_dict[kind]
            return globals()[color + name]()
        else:
            return None

    @abstractmethod
    @staticmethod
    def initial_piece_map():
        pass

    @abstractmethod
    @staticmethod
    def get_name_dict():
        pass

    def __str__(self):
        ...


class ChessBoard(AbstractBoard):

    def __init__(self):
        super().__init__(8, 8)

    @staticmethod
    def get_name_dict():
        name_dict = {PAWN: "ChessPawn", ROOK: "ChessRook",
                     KNIGHT: "ChessKnight", BISHOP: "ChessBishop",
                     KING: "ChessKing", QUEEN: "ChessQueen"}
        return name_dict

    @staticmethod
    def initial_piece_map():
        colors = (((0, 1), BLACK), ((6, 7), WHITE))
        kinds = (((0, 7), ROOK), ((1, 6), KNIGHT), ((2, 5), BISHOP),
                 ((3,), QUEEN), ((4,), KING), ((0, 1, 2, 3, 4, 5, 6, 7), PAWN))
        piece_dict = {(row, column): (kind, color)
                      for row, color in colors for column, kind in kinds}
        return piece_dict

今回のテーマだと本質的に差異が生じるところって「どこにどんな駒を置くのか」なので、それを具象クラスで決めるという風にしたほうが簡潔そう…。特に、駒の位置情報を司るinitial_piece_map が分離するおかげで、外部ファイルからの読み込みに変更するのも簡単。

ただ、これが factory method パティーンなのかと言われると……。そもそもこのテーマ設定がファクトリーメソッドパティーンに向いてないのかしら。

しかし考えてみれば、population の仕方を抽象メソッドで固定してしまうというのはハードコーディングな気もする。population の仕方は同書のやり方もあるし、おかゆのやり方もあるわけで(たとえばおかゆのやり方は、列真ん中付近の無駄なところまで走査してるので、パフォーマンスが悪い)。そうなると、やはり @abstractmethod にするのは populate_board にしておくべきなのだろうか…。

いや、でも、仮にそうだったとしても、populate_board が factory method とは思えない。こいつは別に anOperation (この状況でいえば__init__) に対してクラス差異的にオブジェクトを生成してるわけではなくて、個別クラス特有的にインスタンスの内部状態を変更してるだけ。単なる素朴なオーバーライドにしか見えない。

もやもやする

追記

2016-11-29-15:20

えーっと。factory パターンというのがあるのを知った。理解によれば、「factoryクラスに指示(引数)を与えて、それに見合ったオブジェクトを持ってきてくれるというクールなやつをつくる」パティーンのことらしい。言い方としては「指示に見合ったクラスのインスタンスを作り出す」といったほうがいいか(関数でよくない…?シンプルなファクトリなら関数でもできる(create_pieceがまさにそう)けど、ファクトリの内部状態によって指示への従い方を変えていく…みたいなことをするなら確かにクラスである必要がありそう(でもファクトリってそんな複雑なことする(していい)のかな))。

個人的には router みも感じる。

これを踏まえると、なるほど、create_piece は確かに「ファクトリー関数」ではあっても factory method ではない気がしてきた。

じゃあ、問題はどれがanOperation で、factory method なのか。

サルでもわかる 逆引きデザインパターン 第2章 逆引きカタログ ロジック編 Factory/Factory Method(ファクトリ/ファクトリメソッド) をみる。

それでは、ファクトリパターンとファクトリメソッドパターンの違いは何でしょうか?

ファクトリパターンは、オブジェクトの生成処理だけでなく、どのオブジェクトを生成するかの判断もオブジェクトェクトの使用者から隠してくれるパターンです。 ファクトリパターンでは、生成するオブジェクトの種類の変更をファクトリの処理の中で動的に行います。

しかし、ファクトリパターンでは、生成するオブジェクトの種類が増えたり、生成処理手順が複雑化した場合、ファクトリ内の処理が冗長で複雑になってしまいます。 そこで登場するのがファクトリメソッドパターンです。

せやな。続き:

ファクトリメソッドパターンでは、生成するオブジェクトごとにファクトリを用意し、ファクトリに対して共通のスーパークラスを設けることで、オブジェクトの生成処理を柔軟に行います。 スーパークラスでは、オブジェクト生成に共通な処理の実装と、オブジェクトのnewを行うメソッドやオブジェクト固有の生成手順を抽象メソッドとして定義します。 サブクラスでは、抽象メソッドの実装(オブジェクトのnew)を行うだけです。 オブジェクトの生成はサブクラスで行い、サブクラスでは生成処理の差分のみを実装すればよいので、生成処理を簡略化できます(注4)。

ファクトリメソッドパターンは、1つのファクトリは1つのオブジェクトの生成のみを行うため、生成するオブジェクトの種類の変更を行う場合、ファクトリクラスを切り替える必要があります。 ファクトリメソッドパターンでは多くの場合、オブジェクトの使用者はファクトリのスーパークラスを使用します。 そしてファクトリ指定は、オブジェクト使用者の生成時にコンストラクタで渡したり、ファクトリ設定用メソッドを設けるなどの手段が必要になります。

やっぱりとにかくファクトリメソッドパターンというのは

  • 共通処理は抽象クラスで
  • 差分処理(たいていコンストラクタの指定)は具象クラスで

だと思うんだよな。で、ファクトリである以上、多分、クライアントに渡されるのはプロダクト(を加工したもの)であるべきで、さらにそのプロダクトというのはファクトリーメソッドによって可変的に持ってきたもの。

と考えたときに、やっぱり「実践Python3」のゲームボードの例は気持ちが悪い…。

なんか、もしかして「ファクトリ関数を中に含んだメソッド」をファクトリメソッドと呼んでるんじゃないかコイツ?という感がしてきた。

あ~ もやもやする

追記

16:50

これを Factory method パターンと理解しようとするのを諦めた。

で、こういうの見つけた

doloopwhile.hatenablog.com

あまりにも自由度が高くなってきたらクラスを引数で渡してやれっていう…えぇ…いやそりゃそうするけど……

なんか基本的に批判的な目で読んだほうがいいんかも分からんね(まあ元々がC++デザインパターンだし仕方ないけども)

python でリストのパターンマッチっぽい何か

「実践Python3」のコラム見て、ハッと閃いたのでメモ。(Python3.5)

シーケンスのアンパックは引数定義のところでよく使われますね。

def func(*args, **keywords):
  pass

みたいな感じで。アンパックはまだ使いみちがあって、入れ子になったシーケンスの内側にあるシーケンスをアンパックして平たくできます。

[1, 2, 3, *[4, 5, 6]]
# >> [1, 2, 3, 4, 5, 6]

ところで、Pythonではアンパック代入というのができて、・・・要はタプルの内部に変数を置けます(パターンマッチ的な代入?なんて言うのか分からない)

tpl = (1, 2, 3)
a, b, c = tpl # (a, b, c) = tpl に同じ
assert a == 1 and b == 2 and c == 3

で、このアンパック代入、対タプルならイミュータブルなので割と使えますが、ミュータブルなシーケンス(リストとか)だとふつうの使い方では使い勝手が悪い。

ここで*の出番です。

seq = [1, 2, 3, 4, 5]
x, *xs = seq
assert x == 1
assert xs == [2, 3, 4, 5]

アンパック代入時、最後に*を付した変数を入れておけば、余った部分は全部そこに収容されるので、多い日もあんしん!((少ないときはifして))

で、これ使うと Haskell みたいなリストの取り扱い方(x:xsみたいなの)ができそうだと思って、「すごいH本」4章の実装練習を Python でやってみました。

from typing import List, TypeVar, Tuple, Sequence, Any
from abc import ABCMeta, abstractmethod

T = TypeVar('T')
S = TypeVar('S')
CT = TypeVar('CT', bound=Comparable)

class Comparable(metaclass=ABCMeta):
    @abstractmethod
    def __lt__(self, other: Any) -> bool: pass
    @abstractmethod
    def __gt__(self, other: Any) -> bool: pass
    def __le__(self, other: Any) -> bool:
        return not self > other
    def __ge__(self, other: Any) -> bool:
        return not self < other

def sum_(xs: Sequence[float]) -> float:
    if xs:
        x, *xs = xs
        return x + sum(xs)
    else:
        return 0

def maximum_(xs: Sequence[T]) -> T:
    if xs:
        x, *xs = xs
        if xs:
            return max(x, maximum_(xs))
        else:
            return x
    else:
        raise ValueError("maximum of empty list!")

def take_(n: int, xs: Sequence[T]) -> List[T]:
    if n >= 0 and xs:
        x, *xs = xs
        return [x] + take_(n-1, xs)
    else:
        return []

def reverse_(xs: Sequence[T]) -> List[T]:
    if xs:
        x, *xs = xs
        return reverse_(xs) + [x]
    else:
        return []

def zip_(xs: Sequence[S], ys: Sequence[T]) -> List[Tuple[S, T]]:
    if xs and ys:
        x, *xs = xs
        y, *ys = ys
        return [(x, y)] + zip_(xs, ys)
    else:
        return []

def elem_(a: T, xs: List[T]) -> bool:
    if xs:
        x, *xs = xs
        if a == x:
            return True
        else:
            return elem_(a, xs)
    else:
        return False

def quicksort_(xs: List[CT]) -> List[CT]:
    if xs:
        x, *xs = xs
        smaller_or_equal = [a for a in xs if a <= x]
        larger = [a for a in xs if a > x]
        return quicksort_(smaller_or_equal) + [x] + quicksort_(larger)
    else:
        return []

という感じで。え? pop使えって?うるせーニシキヘビぶつけんぞ。とはいえ確かにジェネレータとかに対しては不向き(特に無限イテレータ)ですね。

とはいえ、ジェネレータで実装する場合はそもそも上のような書き方をしないのでは、とも思うので、もっぱらリスト向きの(トイな)書き方ではある。

ジェネレータで再帰するならyield fromを使うとよさそう

from typing import Iterable, Iterator

def reverse_2(xs: Iterable[T]) -> Iterator[T]:
    xs = iter(xs)
    x = next(xs)
    yield from reverse_2(xs)
    yield x

assert list(reverse_2(range(5))) == [4, 3, 2, 1, 0]

一周してジェネレータを積極的に使いたみというオチになってしまった。ちなみに、遅延評価の Haskell なら簡単にかけるrepeatPython ではジェネレータで書けます。

def repeat(x: T) -> Iterator[T]:
    yield x
    yield from repeat(x)

ジェネレータ便利やね。(全く同じ形式で、yield from repeat(x+1) とすればカウンターになるのも面白い)

ジェネレータ、ループにはwhile使うのが常套手段っぽいけど、再帰のほうがなんかかっこよくていいですよね(?)

P.S.

ところで、Comparable の抽象基底クラスってなんでないんやろ