Pythonわかる? からのおはなし

23
Dan Kogai (小飼 弾) @dankogai

いくら御託を並べられてもこれに関しては🐍は💩。Python 2時代のUnicode文字列の扱いほどFatalではないけど twitter.com/tanakahisateru…

2020-07-25 18:36:26
Dan Kogai (小飼 弾) @dankogai

o.lengthとかo.countとかはArrayやDictionaryのようにO(1)で要素数がわかるものには生えているべきだし、無限リスト他イテレーター一般には生えていないのが自然な姿

2020-07-25 18:48:00
田中ひさてる @tanakahisateru

@dankogai メソッドがあるかないかより、インターフェースを実装するかの方が意味が明示的になってよいので、さらにそれを、型クラスを満たすかどうかに置き換えることで、オブジェクトメッセージングしばりでないレベルでの一般化ができてより良くなる、という考えかたでの自然さもあると思いますがいかがですか

2020-07-25 19:55:30
田中ひさてる @tanakahisateru

@dankogai どちらが正しいかの話ではなく、2つの違う視点で見るのが面白いだけで

2020-07-25 19:58:09
非実在naka aki @naka_aki_spl

@tanakahisateru @dankogai rubyのばあい、メソッドが後付けできるので、「それすらもメソッドにできる」という更なる気持ちよさが…

2020-07-25 22:51:03
田中ひさてる @tanakahisateru

@naka_aki_spl @dankogai 基本的なことが少し違うだけで、あとに続く良さががらっと変わる部分に、言語わかる面白さありますよねー

2020-07-25 23:27:48
Dan Kogai (小飼 弾) @dankogai

@tanakahisateru そんだったら何もかも全部関数でやればよい。Lispはそうしてるぞ。len()を自然にすると他が不自然になる。inconsistentだし、さらに.lengthのように非常によく使う情報にIDEなどで補完が効かないってのは書き味を台無しにする

2020-07-25 23:47:39
田中ひさてる @tanakahisateru

@dankogai なるほどRubyの発想でいくとたしかに map, filter がメソッドで、to_s, to_a など徹底してるところに len だけグローバルはおかしいです。p は許せる範囲。わかります。ただ Python はそれらがぜんぶメソッドではなく関数なので、len も関数インターフェースなのはむしろ一貫性にプラスだと思うのです

2020-07-26 00:01:36
Dan Kogai (小飼 弾) @dankogai

@tanakahisateru いや過度な一貫性がやばいの。len()が何でも数えられるのはいいの。本来数えなくてもいい、computeしなくてもいい場合にもlen()することが強いられるのが問題なの。O(1)なのにO(n)を想定しなきゃならなくなる

2020-07-26 00:36:05
田中ひさてる @tanakahisateru

@dankogai あー、仮に自作ストリームのクラスがあったとして、バカ真面目な人ができるからってそこに迂闊に def __len__ を実装してしまう事件が起きる可能性。そうですね、わかってる人なら想定しなくていい O(n) にはそもそも手をつけない (len 不能のままでほっとく) ので大丈夫なんですけどね...

2020-07-26 00:47:34
田中ひさてる @tanakahisateru

@dankogai あ、もしかして、Java の toString とかequals と同じような、Object 型から継承して全てのクラスに義務付けられるそれみたいなイメージを前提にされていたでしょうか。それとはちょっと違って、比較可能とか計測可能な型のグループだけが比較や計測をやればいいというのが型クラスのイメージです

2020-07-26 00:54:09
Dan Kogai (小飼 弾) @dankogai

違うものを同じように扱えるのはいいことだが、同じように*しか*扱えないのはまるでいいことではない。別物として扱わねばならない場合が(主に計算資源の制約ゆえ)不可避である以上は twitter.com/dankogai/statu…

2020-07-26 00:52:22
Dan Kogai (小飼 弾) @dankogai

あるコレクションが配列なのかリストなのか気にしなくて良いのはO(1)かO(n)かの差を気にしなくてよい場合に限る。len()に一貫性を求めると、O(n)という想定コストもまた一貫してしまう

2020-07-26 01:00:36
Dan Kogai (小飼 弾) @dankogai

@tanakahisateru 型クラスというよりSwiftでいうところのSequenceプロトコル。ここでlen()が受け付けるべき制約は「次の要素を取得できる」「要素が終端に達したことがわかる」だけ。これがCollectionになると、「いきなり要素数が取得できる」要件も満たす。CollectionはSequenceでもあるがその逆は真ではない

2020-07-26 01:30:51
Dan Kogai (小飼 弾) @dankogai

@tanakahisateru Sequence用に書いたlen()はCollectionでも確実に動作するが、動作速度はO(n)になる。いきなり要素数を取得できない以上、「終端に達するまで要素をたぐる」しかないから。どちらかわからない時にlen()を使えってのはいい。でもCollectionであることが明確なら.countも使えることが保証できる

2020-07-26 01:37:19
田中ひさてる @tanakahisateru

@dankogai 丁寧な説明ありがとうございます。そこはわかっているので大丈夫です。なので、map が正確評価の list を返すのではなく sequence の遅延評価サブタイプを返すようになった Python 3 では、以下のコードはエラーがなります len(map(math.log10, range(1,100)))

2020-07-26 01:56:50
田中ひさてる @tanakahisateru

@dankogai len をかけるには list 関数で終端まで評価した配列に変換する必要があります: len(list(map(math.log10, range(1,100))))

2020-07-26 01:58:55
Dan Kogai (小飼 弾) @dankogai

@tanakahisateru Lisp的にはlen()はO(n)なのが当然だし、総称関数の設計としてはSequenceが成立している時点でlen()できるのが正しい

2020-07-26 01:58:02
田中ひさてる @tanakahisateru

@dankogai あ、すみません、間違って説明していました。違うのは名前づけだけの話なんですが、map の入出力は iterable のサブタイプで、sequence という名前は、末尾が予測できる線形リストの総称でした。sequence には len 可能ですが、map が sequence を返さなくなったのが他と変わっています。

2020-07-26 02:34:49
田中ひさてる @tanakahisateru

@dankogai Python の len は思ったよりケチなので、なんでもかんでも受け入れて O(n) になる可能性は実はあまりない、でも、本当に末尾へのスキャンが必要なケースと遅延できるケースを分けず、いきなり sequence の対象範囲を狭めて、明示的な変換を必要としてしまった。ここが是非を問われる箇所と理解しました

2020-07-26 02:52:03
田中ひさてる @tanakahisateru

@dankogai (よく考えたら 2k からホントに 3 やるとなったとき、さんざん言われてた点に結局戻ってきたのかも。自分は、変わってはいるけど、現実問題のために言語に独特な個性が生まれるのを PHP で慣れたので、2 より 3 が好みです)

2020-07-26 02:56:45