- finalfusion
- 7029
- 1
- 22
- 0
@kmizu 動的言語では議論の余地なく悪いスタイルです。静的型言語では型チェックが効くのでそこまでではないかもしれません。個人的には賛成しませんが。でも、動的言語との比較という文脈に使うのはフェアじゃないですよね。
2010-05-08 19:22:16@yukihiro_matz それは順序が逆で、静的型付けが無い「から」悪いスタイルになってるのでは?もちろん、静的型付け言語でも悪いスタイルであるかもしれませんが。
2010-05-08 20:12:36@yukihiro_matz それは順序が逆で、静的型付けが無い「から」悪いスタイルになってるのでは?もちろん、静的型付け言語でも悪いスタイルであるかもしれませんが。
2010-05-08 20:12:36動的言語では議論の余地なく悪いスタイルである、という点についてちょっと考えてみたのだけど、これはLiskov則を破っている、という点に尽きるのかな。たとえば、Rubyで、実際の中身はHashだけどユーザはEnumerableということしかわからないという場合を考える。
2010-05-08 20:25:55このとき、h.map{|k, v| [v, k]}とかしたときに、mapがHashを返すようになっている場合、(キーの重複によってエントリが削除されることで)、いつの間にかEnumerableで列挙できる要素数が減っているということが考えられる。
2010-05-08 20:28:35Enumerableのユーザはmapしたら要素数が増減するなんて事は普通は予期しないはずで、これは明らかに契約違反。よって、このようなHash#mapがHashを返し得るようなのは悪い仕様だと言えそう。
2010-05-08 20:29:56一方、Scalaの場合だけど、Map(RubyのHash相当)をRubyにおけるEnumerableに相当するTraversable型にアップキャストして、Traversableに対してmapを呼び出した場合、
2010-05-08 20:33:37つまり、val t: Traversable[(String, Int)] = Map("A" -> 2, "B" -> 2, "C" -> 3); t.map{ case (k, v) => (v, k) } とした場合、
2010-05-08 20:34:45Map(2 -> "A", 3 -> "C")みたいに勝手に要素数が減って大弱り、なんてことはなく、ちゃんとList((2, "A"), (2, "B"), (3, "C"))](静的な型はTraversable)が返ってくる。
2010-05-08 20:36:24Map#mapで返すべきオブジェクトの型を推論するに当たって、レシーバの「静的な」型も考慮に入れて推論されるので、これでうまく行くようになっている。MapをそのスーパータイプであるTraversableとして扱って問題無いので、この点ではLiskov則は破っていない、と言える。
2010-05-08 20:39:38というわけで、仮にLiskov則を基準とした場合、Scala 2.8のMap#mapの仕様は別に悪いスタイルではない、という事が言えるのではないか。それ以外の基準についても検証する必要があるけど、たとえばどういう基準があるだろうか?
2010-05-08 20:41:17@kmizu 同じ字面で違うものが返るのはLSPに反するでしょう。静的型ではコンパイル時に検出できる(から致命的ではない)でしょうけど、だからといって「良く」はならないと思います。
2010-05-08 21:03:07@yukihiro_matz いや、この場合は反しないでしょう。Traversableが要求されている箇所にMapを渡しても、考えた限りでは問題が発生しないようになっています。map.map{ case ... } で関数の型によって異なるオブジェクトが返るのは
2010-05-08 21:07:24@yukihiro_matz 単なるオーバーローディングの範疇であり、LSPは関係ないです。
2010-05-08 21:07:56@mametter なるほど。ちょっとその変で食い違ってたのかもしれません。ありがとうございます。ただ、Liskovはその辺についてはかなり深く考えてますよ。Liskov則の元になった
2010-05-08 21:13:24@mametter 「A behavioral notion of subtyping」という論文では、LSPと一言で片付けられてしまう原則に関するフォーマルな定義を含めた深い議論を見ることができます。
2010-05-08 21:14:24@mametter implicit conversionみたいな怪しげな機能についてはさすがに考察してないですけど、Liskovの論文ではメソッドオーバーライド時のrenamingがあるような場合のサブタイピングについて考察がなされています。
2010-05-08 21:18:22@mametter で、メソッドオーバーライド時のrenamingがあったりすると、スーパータイプのものを「字面上で」サブタイプのものにそのまま置き換えると問題が発生するケースがあるわけで、今回のケースと似た問題だと思います。
2010-05-08 21:20:31てなわけで、自分はLiskov則について言及するときは、おおむねその論文を意識しているので、その辺でズレが生じていた、のかも。
2010-05-08 21:24:30val m: Map[String, Int] = Map("A" -> 1, "B" -> 3, "C" -> 2); m.map{ case (k, v) => (v, k) }のとき、呼び出しの型は、map[(Int, String), Map[Int, String]]
2010-05-08 21:31:42一方、val t: Traversable[(String, Int)] = m; t.map{ case (k, v) => (v, k) }のとき、型はmap[(Int, String), Traversable[(Int, String)]] で、#scala
2010-05-08 21:33:25つまり、「字面上は」同じでも、この場合、m.map{ ... }とt.map{ ... }は、実はオーバーロードされた別バージョンを呼び出しているのに等しいので、字面上だけの置換可能性を元に議論されちゃたまらんと思うわけです。
2010-05-08 21:35:03@kmizu まあ、確かに型情報の違いによって字面上は同じでも別バージョン、というのはわかるんですが、字面が同じってことは人間に与える印象も同じってことなんで、それで違っちゃうのはつらいなあと。同様の理由でメソッドオーバーロードも乱用は危険だと思ってます。
2010-05-08 21:48:31@yukihiro_matz はい。その点に関する懸念は理解できます。ただ、悪いかどうかについては十分議論の余地がある話だと考えている、といったところです。
2010-05-08 21:56:58