クラス内でtypeで型を定義する際に、インスタンス変数の型を使った時の挙動

16 views
Skip to first unread message

関隆

unread,
Dec 16, 2009, 4:40:00 AM12/16/09
to scal...@googlegroups.com
こんにちは、関隆です。

いま、lift-jsonを
 http://github.com/dpp/liftweb/tree/master/lift-base/lift-json/
にある、サンプルをもとに実験しています。

ところが、
-----
import net.liftweb.json.JsonParser.parse
import net.liftweb.json.JsonAST._

val json_str = """{"a":1,"b":2,"c":3}"""

val list = for {
JObject(child) <- parse(json_str)
item <- child
} yield item.values

list.foreach(println _)
-----
が、(盛大に処理途中のソースコードを出力しながら)
error: fatal error (server aborted): type mismatch;
found : (String, item.value.Values)
required: (String, net.liftweb.json.JsonAST.JField#value.Values)
というエラーで止まる現象に遭遇しました。

ちょっと変えて
-----
import net.liftweb.json.JsonParser.parse

val json_str = """{"a":1,"b":2,"c":3}"""

val list = for (
item <- parse(json_str).children
) yield item.values

list.foreach(println _)
-----
だと問題なくうまくいきます。

http://github.com/dpp/liftweb/blob/master/lift-base/lift-json/src/main/scala/net/liftweb/json/JsonAST.scala
をみると・・・

case class JField(name: String, value: JValue) extends JValue {
type Values = (String, value.Values)
def values = (name, value.values)
override def apply(i: Int): JValue = value(i)
}
の様になっており、
Values型がvalueの実際のインスタンスによって
動的に決まるようになっているからかもしれないなぁ・・・
などと想像はできるのですが、これ以上は追いきれておりません。
lift-jsonのというよりは、コンパイラのバグっぽい気もします。

当面の問題は解決はしているのですが、
どうもこの辺の型の挙動が理解できておりません。

どなたか、ご教授いただければ幸いです。

どうぞよろしくお願いいたします。

--
関 隆(SEKI Takashi)
hawk...@nifty.com
hawk...@gmail.com

Kota Mizushima

unread,
Dec 16, 2009, 6:51:51 AM12/16/09
to scal...@googlegroups.com
水島です。
こちらでも同じサンプルを試して見ましたが、同様にコンパイラがエラーメッセージを吐いて落ちますね。
まずこの時点で言えることは、コンパイラが落ちたことは、プログラムが間違いであろうとなかろうとバグだという
ことです。なので、この点は(既にバグレポートが無ければ)バグレポートを書いて修正を要求するのが良いと
思います。ただ、このプログラムが型エラーになったことに関しては、コンパイラのバグかどうかは微妙な
ところかと思います。

まず、引用元のプログラムのシンタックスシュガーを除去すると以下のようになります。

import net.liftweb.json.JsonParser.parse
import net.liftweb.json.JsonAST._

val json_str = """{"a":1,"b":2,"c":3}"""

val list = parse(json_str).filter{
case JObject(_) => true; case _ => false
}.flatMap{
case JObject(child) => child.map{item => (item.values)}
}

list.foreach(println _)

このとき、item.valuesの型は(String, item.value.Values)型になりますが(このように、変数に依存する型を
Scalaではパス依存型といいます)、flatMap[B]の型パラメータBを推論するのにitemを使用することができないので(
itemはスコープを抜けているため)、コンパイラはその代わりにitemの型であるJFieldを
使用してB = (String, JField#value.Values)(JField#value.Valuesはプログラム上では表記不可能な型)
と推論しているようです。そして、この型とitem.valuesの型である(String, item.value.Values)型が非互換である
ため、型チェッカにはねられてしまうようです。正直言って自分もこの辺り(パス依存型)の動作は完全に理解できてない
ので、間違っている可能性もありますが…。

とりあえず、簡単な対策としては、items.valueを(String, JValue#Values)型にアップキャストして以下のように
してやるとコンパイルを通るようになります。
# 正直なところ、JField#Valuesの型を(String, value.Values)から
# (String, JValue#Values)に変更してもらえれば、それが一番良いような気がしていますが…

import net.liftweb.json.JsonParser.parse
import net.liftweb.json.JsonAST._

val json_str = """{"a":1,"b":2,"c":3}"""

val list = for {
JObject(child) <- parse(json_str)
item <- child

} yield (item.values:(String, JValue#Values))

list.foreach(println _)

2009年12月16日18:40 関隆 <hawk...@gmail.com>:

> --
>
> このメールは Google グループのグループ「scala-be」の登録者に送られています。
> このグループに投稿するには、scal...@googlegroups.com にメールを送信してください。
> このグループから退会するには、scala-be+u...@googlegroups.com にメールを送信してください。
> 詳細については、http://groups.google.com/group/scala-be?hl=ja からこのグループにアクセスしてください。
>
>
>

--
Kota Mizushima
e-mail: hau5...@tree.odn.ne.jp,mizu...@gmail.com

関隆

unread,
Dec 16, 2009, 9:55:34 AM12/16/09
to scal...@googlegroups.com
水島さんへ

こんばんは、関隆です。

ご返事ありがとうございます。

あまりちゃんとは調べてませんが、
とりあえず、Tracに新しいTicketを投稿してみました。

詳しい説明もありがとうございます。
なんとなくは理解できましたが、勉強不足を痛感しました。(^^;
特に「パス依存型」というのは今回が初耳でした。
Scalaは本当に型に関して奥深いですねぇ・・・

1点、 JValue#Valuesというのは、単純に
「JValue クラスで定義されているValues型」
という意味で良いのでしょうか。

どうぞよろしくお願いいたします。

2009年12月16日20:51 Kota Mizushima <mizu...@gmail.com>:

Kota Mizushima

unread,
Dec 16, 2009, 12:26:04 PM12/16/09
to scal...@googlegroups.com
水島です。

2009年12月16日23:55 関隆 <hawk...@gmail.com>:


> 水島さんへ
>
> こんばんは、関隆です。
>
> ご返事ありがとうございます。
>
> あまりちゃんとは調べてませんが、
> とりあえず、Tracに新しいTicketを投稿してみました。

おお。どうもありがとうございます。

>
> 詳しい説明もありがとうございます。
> なんとなくは理解できましたが、勉強不足を痛感しました。(^^;
> 特に「パス依存型」というのは今回が初耳でした。
> Scalaは本当に型に関して奥深いですねぇ・・・
>
> 1点、 JValue#Valuesというのは、単純に
> 「JValue クラスで定義されているValues型」
> という意味で良いのでしょうか。

そういう理解で良いと思います。T1#T2のように書いた場合(type projectionと呼びます)、
単にT1のメンバであるT2の型という意味になります(Javaの
T1.T2と同じ意味ですね)。一方、t1.T2(t1は型ではなく値で、t1の型はT1とする)の
場合、変数t1が指しているオブジェクトのメンバである型T2という
意味になります。前者の場合、どのT2であっても、T1#T2型の変数に
代入できますが、後者の場合、t1のインスタンスから生成されたT2型しか
t1.T2型に代入できない点が異なります。

関隆

unread,
Dec 16, 2009, 7:46:04 PM12/16/09
to scal...@googlegroups.com
水島さんへ

おはようございます、関隆です。

解説ありがとうございます。

>前者の場合、どのT2であっても、T1#T2型の変数に
> 代入できますが、後者の場合、t1のインスタンスから生成されたT2型しか
> t1.T2型に代入できない点が異なります。

なるほど、これで、


>>> # 正直なところ、JField#Valuesの型を(String, value.Values)から
>>> # (String, JValue#Values)に変更してもらえれば、それが一番良いような気がしていますが…

の言わんとしていることが理解できました。

ありがとうございました。

ちなみに、登録したバグレポートは
 https://lampsvn.epfl.ch/trac/scala/ticket/2804
これです。
自動翻訳を使っているので英語的にはおかしい可能性大ですが、
まぁ、意味は通じるでしょう・・・(^^;

2009年12月17日2:26 Kota Mizushima <mizu...@gmail.com>:

関隆

unread,
Dec 17, 2009, 2:55:55 AM12/17/09
to scal...@googlegroups.com
水島さんへ

こんにちは、関隆です。

質問ばかりで恐縮です。もう2点だけ・・・

直感的には動作がわかったためサラッと流してしまっていて、
実はよくわかっていなかった点なんですが、

1:
 >>>> } yield (item.values:(String, JValue#Values))
 の行の
 「:(String, JValue#Values)」
 の部分は、

 言語仕様
  http://www.scala-lang.org/docu/files/ScalaReference.pdf
 の、80ページにある。
  6.13 Typed Expressions
  Expr1 ::= PostfixExpr ‘:’ CompoundType
 を利用しているとの理解でよいでしょうか。

2:
 この部分、今回の場合は
 } yield (item.values.asInstanceOf[(String, JValue#Values)])
 と置き換えても動くようですが、

 asInstanceOfとの違いをどう理解するのが良いのでしょうか。

どうぞよろしくお願いいたします。

2009年12月17日9:46 関隆 <hawk...@gmail.com>:

Kota Mizushima

unread,
Dec 17, 2009, 5:30:33 AM12/17/09
to scal...@googlegroups.com
関さん:

水島です。

2009年12月17日16:55 関隆 <hawk...@gmail.com>:


> 水島さんへ
>
> こんにちは、関隆です。
>
> 質問ばかりで恐縮です。もう2点だけ・・・
>
> 直感的には動作がわかったためサラッと流してしまっていて、
> 実はよくわかっていなかった点なんですが、
>
> 1:
>  >>>> } yield (item.values:(String, JValue#Values))
>  の行の
> 「:(String, JValue#Values)」
> の部分は、
>
> 言語仕様
> http://www.scala-lang.org/docu/files/ScalaReference.pdf
> の、80ページにある。
> 6.13 Typed Expressions
> Expr1 ::= PostfixExpr ‘:’ CompoundType
> を利用しているとの理解でよいでしょうか。

はい。その部分で合っています。

>
> 2:
> この部分、今回の場合は
> } yield (item.values.asInstanceOf[(String, JValue#Values)])
> と置き換えても動くようですが、
>
> asInstanceOfとの違いをどう理解するのが良いのでしょうか。

・asInstanceOfは無理矢理型を変換するため、型安全ではない(型を変換した
後に妙な動作(ClassCastExceptionを含む)が起きる可能性がある)

のに対して、

・expr:typeは、exprがtypeに変換できない(この変換というのは、implicit conversionによるものも
含みます)場合はコンパイル時に型エラーが出るため、型安全である

というのが大きな違いです。

関隆

unread,
Dec 17, 2009, 7:31:47 AM12/17/09
to scal...@googlegroups.com
水島さんへ

こんばんは、関隆です。

解説ありがとうございます。

なるほど、良く分かりました。
ありがとうございました。

2009年12月17日19:30 Kota Mizushima <mizu...@gmail.com>:

Reply all
Reply to author
Forward
0 new messages