This page is no longer maintained — Please continue to the home page at www.scala-lang.org

Implicit conversion doesn't work with primitive type definition but does work with class definition

9 replies
wrburdick
Joined: 2009-09-21,
User offline. Last seen 3 years 3 weeks ago.
Using type PK[T] = Long doesn't seem to work with an implicit conversion to T, but class PK[T](val pk: Long) does.  Is this how the compiler is supposed to behave?
I'm using version 2.10.0.r25881-b20111024020227 and I get this compilation error, "value name is not a member of pktest.PK_DEFS.PK[pktest.Person]" on line 38 (println(p2.friend.name)) -- it's not using the implicit definition to convert p2.friend from a PK[Person] to a Person when PK[T] is defined as a type of Long, but it does work when PK[T] is defined as a class.

Bill
(the original post is below, with source...)
On Thu, Oct 13, 2011 at 5:50 PM, Bill Burdick <bill.burdick@gmail.com> wrote:
I'm making a DB-type toolkit and I'd like to convert between objects and primary keys, implicitly.  My code works if I define primary keys as wrapped longs, but it won't compile if I define them as a parameterized type of long.
If I don't use a manifest on the implicit conversion, I get this error, "value name is not a member of Nothing," but if I do use a manifest, I get this error, "value name is not a member of pktest.PK_DEFS.PK[pktest.Person]".  Here's my example code, with the PK[T] type uncommented and the PK[T] class commented out.

Can anyone help me with this?


-- Bill

object PK_DEFS { type PK[T] = Long val NULL_PK = -1 def pkFor[T](pk: Long) = pk.asInstanceOf[T]// class PK[T](val pk: Long) {override def toString() = "ID("+pk+")"} // val NULL_PK = null// def pkFor[T](pk: Long) = new PK[T](pk)
trait HasPK[T] {def pk: PK[T]}
implicit def pk2Obj[T: Manifest](pk: PK[T]): T = storage(pk).asInstanceOf[T] implicit def obj2Pk[T](obj: HasPK[T]) = obj.pk
var storage = Map[PK[_], Any]()
def store(obj: HasPK[_]) = storage += obj.pk -> obj def get[T](pk: PK[T]) = storage(pk) }
import PK_DEFS._
class Person(val pk: PK[Person], var name: String, var friend: PK[Person]) extends HasPK[Person] {  override def toString = "Person("+name+")" }
object PkTest {  import PK_DEFS._
  def main(args: Array[String]) {  val p1 = new Person(pkFor(1), "joe", NULL_PK)  store(p1)  val p2 = new Person(pkFor(2), "fred", p1)  store(p2)  println(p1.pk)  println(p2.friend)  println(p2.friend.name)   }}

adriaanm
Joined: 2010-02-08,
User offline. Last seen 31 weeks 4 days ago.
Re: Implicit conversion doesn't work with primitive type defini

type PK[T] = Long
implicit def pk2Obj[T: Manifest](pk: PK[T]): T = storage(pk).asInstanceOf[T]
 
 val p1 = new Person(pkFor(1), "joe", NULL_PK)  store(p1)  val p2 = new Person(pkFor(2), "fred", p1)  store(p2)  println(p2.friend.name)
so, after implicit conversion you'd expect something like: pk2Obj[Person](p2.friend).name, right?
the problem is that your PK[T] type alias discards T, so that type inference can't figure out what T should be
when inferring T in pk2Obj, it'll compare the type of the provided argument (p2.friend: PK[Person]) with PK[T], and chose T so that PK[Person] is a valid argument for a method that expects a PK[T] -- thus, PK[Person] <: PK[T] is the constraint that must be solved however, before solving that constraint, type aliases are expanded in this case, so we end up comarping Long <: Long, which is trivially true
thus, type inference has nothing to go on and cops out with Nothing
for the Manifest case, we regard Nothing as failure and don't even apply the conversion in practice this could be fixed, but I'm not sure the fix wouldn't break other code, or slow down the compiler the main problem is the spec: I haven't yet checked what it says, but I suspect it's a bit of a gray area (that should be made more precise)
one way to work around this is to keep the PK type member abstract in the inferface mixed into user code, and only concretize it in the implementation (see below)

cheersadriaan


trait DB { type PK[T] def NULL_PK[T]: PK[T] def pkFor[T](pk: Long) = pk.asInstanceOf[T]
trait HasPK[T] {def pk: PK[T]}
implicit def pk2Obj[T: Manifest](pk: PK[T]): T = storage(pk).asInstanceOf[T] implicit def obj2Pk[T](obj: HasPK[T]) = obj.pk
var storage = Map[PK[_], Any]()
def store(obj: HasPK[_]) = storage += obj.pk -> obj def get[T](pk: PK[T]) = storage(pk) }
trait Program extends DB {  class Person(val pk: PK[Person], var name: String, var friend: PK[Person]) extends HasPK[Person] {    override def toString = "Person("+name+")"   }    def test = {  val p1 = new Person(pkFor(1), "joe", NULL_PK)  store(p1)  val p2 = new Person(pkFor(2), "fred", p1)  store(p2)  println(p1.pk)  println(p2.friend)  println(p2.friend.name)   }}
trait DBImpl extends DB {  type PK[T]= Long  def NULL_PK[T] = -1}
object PkTest extends Program with DBImpl with App {   test}
Hubert Plociniczak
Joined: 2009-09-12,
User offline. Last seen 42 years 45 weeks ago.
Re: Implicit conversion doesn't work with primitive type defini
On 10/25/2011 12:10 PM, Adriaan Moors wrote:
CAJQegshOdVoOhQmvb8Og-1epsAqYhguiQksw7y8t4Oy4ovTuAA [at] mail [dot] gmail [dot] com" type="cite">
type PK[T] = Long
implicit def pk2Obj[T: Manifest](pk: PK[T]): T = storage(pk).asInstanceOf[T]
 
 val p1 = new Person(pkFor(1), "joe", NULL_PK)  store(p1)  val p2 = new Person(pkFor(2), "fred", p1)  store(p2)  println(p2.friend.name)
so, after implicit conversion you'd expect something like: pk2Obj[Person](p2.friend).name, right?
the problem is that your PK[T] type alias discards T, so that type inference can't figure out what T should be
when inferring T in pk2Obj, it'll compare the type of the provided argument (p2.friend: PK[Person]) with PK[T],  and chose T so that PK[Person] is a valid argument for a method that expects a PK[T] -- thus, PK[Person] <: PK[T] is the constraint that must be solved however, before solving that constraint, type aliases are expanded in this case, so we end up comarping Long <: Long, which is trivially true
thus, type inference has nothing to go on and cops out with Nothing
for the Manifest case, we regard Nothing as failure and don't even apply the conversion   in practice this could be fixed, but I'm not sure the fix wouldn't break other code, or slow down the compiler the main problem is the spec: I haven't yet checked what it says, but I suspect it's a bit of a gray area (that should be made more precise)

There is even a bug related to that (I mean Nothing as a failure): https://issues.scala-lang.org/browse/SI-1570

hubert

CAJQegshOdVoOhQmvb8Og-1epsAqYhguiQksw7y8t4Oy4ovTuAA [at] mail [dot] gmail [dot] com" type="cite">
one way to work around this is to keep the PK type member abstract in the inferface mixed into user code, and only concretize it in the implementation (see below)

cheers adriaan


trait DB { type PK[T] def NULL_PK[T]: PK[T] def pkFor[T](pk: Long) = pk.asInstanceOf[T]
trait HasPK[T] {def pk: PK[T]}
implicit def pk2Obj[T: Manifest](pk: PK[T]): T = storage(pk).asInstanceOf[T] implicit def obj2Pk[T](obj: HasPK[T]) = obj.pk
var storage = Map[PK[_], Any]()
def store(obj: HasPK[_]) = storage += obj.pk -> obj def get[T](pk: PK[T]) = storage(pk) }
trait Program extends DB {   class Person(val pk: PK[Person], var name: String, var friend: PK[Person]) extends HasPK[Person] {     override def toString = "Person("+name+")"   }      def test = {  val p1 = new Person(pkFor(1), "joe", NULL_PK)  store(p1)  val p2 = new Person(pkFor(2), "fred", p1)  store(p2)  println(p1.pk)  println(p2.friend)  println(p2.friend.name)   } }
trait DBImpl extends DB {   type PK[T]= Long   def NULL_PK[T] = -1 }
object PkTest extends Program with DBImpl with App {   test }

wrburdick
Joined: 2009-09-21,
User offline. Last seen 3 years 3 weeks ago.
Re: Implicit conversion doesn't work with primitive type defini
On Tue, Oct 25, 2011 at 5:10 AM, Adriaan Moors <adriaan.moors@epfl.ch> wrote:
one way to work around this is to keep the PK type member abstract in the inferface mixed into user code, and only concretize it in the implementation (see below)

cheersadriaan


Thanks for the help!  It looks like doing that ends up boxing the Long, anyway (defining private final java.lang.Object pk in Person), so I may as well just use my own ID wrapper. :(

Bill
adriaanm
Joined: 2010-02-08,
User offline. Last seen 31 weeks 4 days ago.
Re: Implicit conversion doesn't work with primitive type defini

On Tue, Oct 25, 2011 at 4:29 PM, Bill Burdick <bill.burdick@gmail.com> wrote:
On Tue, Oct 25, 2011 at 5:10 AM, Adriaan Moors <adriaan.moors@epfl.ch> wrote:
one way to work around this is to keep the PK type member abstract in the inferface mixed into user code, and only concretize it in the implementation
  It looks like doing that ends up boxing the Long, anyway (defining private final java.lang.Object pk in Person),
indeed, that's unfortunate -- it would be nice to support something like this explicitly rather than "by accident" (thanks to alias types being expanded late in most cases)
(I'm thinking some kind of hybrid of Scala's type members, Haskell's newtype, and ML's opaque types)
cheers adriaan
milessabin
Joined: 2008-08-11,
User offline. Last seen 33 weeks 3 days ago.
Re: Implicit conversion doesn't work with primitive type defini

On Tue, Oct 25, 2011 at 6:11 PM, Adriaan Moors wrote:
> On Tue, Oct 25, 2011 at 4:29 PM, Bill Burdick
> wrote:
>>
>> On Tue, Oct 25, 2011 at 5:10 AM, Adriaan Moors
>> wrote:
>>>
>>> one way to work around this is to keep the PK type member abstract in the
>>> inferface mixed into user code, and only concretize it in the implementation
>>
>>   It looks like doing that ends up boxing the Long, anyway (defining
>> private final java.lang.Object pk in Person),
>
> indeed, that's unfortunate -- it would be nice to support something like
> this explicitly rather than "by accident" (thanks to alias types being
> expanded late in most cases)
> (I'm thinking some kind of hybrid of Scala's type members, Haskell's
> newtype, and ML's opaque types)

That would be very nice to have, but in the meantime you can
approximate the newtype behaviour, at least sufficiently to guide
implicit resolution,

type Tagged[T] = { type Tag = T }
type @@[T, U] = T with Tagged[U]

def tag[T] = new {
def apply[U](u : U) : U with Tagged[T] = u.asInstanceOf[U @@ T]
}

type PK[T] = Long @@ T

implicit def pk2Obj[T: Manifest](pk: PK[T]): T = null.asInstanceOf[T]

Sample REPL session,

scala> trait Foo
defined trait Foo

scala> val lf = tag[Foo](1L)
lf: Long with Tagged[Foo] = 1

scala> lf : Foo
res0: Foo = null

scala> trait Bar
defined trait Bar

scala> val lb = tag[Bar](2L)
lb: Long with Tagged[Bar] = 2

scala> lb : Bar
res1: Bar = null

Cheers,

Miles

wrburdick
Joined: 2009-09-21,
User offline. Last seen 3 years 3 weeks ago.
Re: Implicit conversion doesn't work with primitive type defini
YOW!  I saw a post about this thing earlier and my eyes rolled back in my head!  It just happened again.  I'll try this out.

Bill

On Tue, Oct 25, 2011 at 4:31 PM, Miles Sabin <miles@milessabin.com> wrote:
On Tue, Oct 25, 2011 at 6:11 PM, Adriaan Moors <adriaan.moors@epfl.ch> wrote:
> On Tue, Oct 25, 2011 at 4:29 PM, Bill Burdick <bill.burdick@gmail.com>
> wrote:
>>
>> On Tue, Oct 25, 2011 at 5:10 AM, Adriaan Moors <adriaan.moors@epfl.ch>
>> wrote:
>>>
>>> one way to work around this is to keep the PK type member abstract in the
>>> inferface mixed into user code, and only concretize it in the implementation
>>
>>   It looks like doing that ends up boxing the Long, anyway (defining
>> private final java.lang.Object pk in Person),
>
> indeed, that's unfortunate -- it would be nice to support something like
> this explicitly rather than "by accident" (thanks to alias types being
> expanded late in most cases)
> (I'm thinking some kind of hybrid of Scala's type members, Haskell's
> newtype, and ML's opaque types)

That would be very nice to have, but in the meantime you can
approximate the newtype behaviour, at least sufficiently to guide
implicit resolution,

type Tagged[T] = { type Tag = T }
type @@[T, U] = T with Tagged[U]

def tag[T] = new {
 def apply[U](u : U) : U with Tagged[T] = u.asInstanceOf[U @@ T]
}

type PK[T] = Long @@ T

implicit def pk2Obj[T: Manifest](pk: PK[T]): T = null.asInstanceOf[T]

Sample REPL session,

scala> trait Foo
defined trait Foo

scala> val lf = tag[Foo](1L)
lf: Long with Tagged[Foo] = 1

scala> lf : Foo
res0: Foo = null

scala> trait Bar
defined trait Bar

scala> val lb = tag[Bar](2L)
lb: Long with Tagged[Bar] = 2

scala> lb : Bar
res1: Bar = null

Cheers,


Miles

--
Miles Sabin
tel: +44 7813 944 528
gtalk: miles@milessabin.com
skype: milessabin
http://www.chuusai.com/
http://twitter.com/milessabin

wrburdick
Joined: 2009-09-21,
User offline. Last seen 3 years 3 weeks ago.
Re: Implicit conversion doesn't work with primitive type defini
Thank you, it worked like a charm!  Now I have to batter my brain into understanding the code...

Bill

On Tue, Oct 25, 2011 at 4:43 PM, Bill Burdick <bill.burdick@gmail.com> wrote:
YOW!  I saw a post about this thing earlier and my eyes rolled back in my head!  It just happened again.  I'll try this out.

Bill

On Tue, Oct 25, 2011 at 4:31 PM, Miles Sabin <miles@milessabin.com> wrote:
On Tue, Oct 25, 2011 at 6:11 PM, Adriaan Moors <adriaan.moors@epfl.ch> wrote:
> On Tue, Oct 25, 2011 at 4:29 PM, Bill Burdick <bill.burdick@gmail.com>
> wrote:
>>
>> On Tue, Oct 25, 2011 at 5:10 AM, Adriaan Moors <adriaan.moors@epfl.ch>
>> wrote:
>>>
>>> one way to work around this is to keep the PK type member abstract in the
>>> inferface mixed into user code, and only concretize it in the implementation
>>
>>   It looks like doing that ends up boxing the Long, anyway (defining
>> private final java.lang.Object pk in Person),
>
> indeed, that's unfortunate -- it would be nice to support something like
> this explicitly rather than "by accident" (thanks to alias types being
> expanded late in most cases)
> (I'm thinking some kind of hybrid of Scala's type members, Haskell's
> newtype, and ML's opaque types)

That would be very nice to have, but in the meantime you can
approximate the newtype behaviour, at least sufficiently to guide
implicit resolution,

type Tagged[T] = { type Tag = T }
type @@[T, U] = T with Tagged[U]

def tag[T] = new {
 def apply[U](u : U) : U with Tagged[T] = u.asInstanceOf[U @@ T]
}

type PK[T] = Long @@ T

implicit def pk2Obj[T: Manifest](pk: PK[T]): T = null.asInstanceOf[T]

Sample REPL session,

scala> trait Foo
defined trait Foo

scala> val lf = tag[Foo](1L)
lf: Long with Tagged[Foo] = 1

scala> lf : Foo
res0: Foo = null

scala> trait Bar
defined trait Bar

scala> val lb = tag[Bar](2L)
lb: Long with Tagged[Bar] = 2

scala> lb : Bar
res1: Bar = null

Cheers,


Miles

--
Miles Sabin
tel: +44 7813 944 528
gtalk: miles@milessabin.com
skype: milessabin
http://www.chuusai.com/
http://twitter.com/milessabin


wrburdick
Joined: 2009-09-21,
User offline. Last seen 3 years 3 weeks ago.
Re: Implicit conversion doesn't work with primitive type defini
On Wed, Oct 26, 2011 at 4:05 PM, Bill Burdick <bill.burdick@gmail.com> wrote:
Thank you, it worked like a charm!  Now I have to batter my brain into understanding the code...

Bill

On Tue, Oct 25, 2011 at 4:43 PM, Bill Burdick <bill.burdick@gmail.com> wrote:
YOW!  I saw a post about this thing earlier and my eyes rolled back in my head!  It just happened again.  I'll try this out.

Bill

On Tue, Oct 25, 2011 at 4:31 PM, Miles Sabin <miles@milessabin.com> wrote:
That would be very nice to have, but in the meantime you can
approximate the newtype behaviour, at least sufficiently to guide
implicit resolution,

Thanks, very much Miles!  This is really great, because now I can profile the two approaches to see which one is better for my apps.  I suspect that using naked Longs will be better, because the space may be roughly the same, since references are 1/2 the size of longs (on 32-bit systems and 64-bit implementations that support compressed references) and shared references may compensate for the overhead of id wrapper objects, but I think naked Longs will be faster to use, because you don't have to go through an intermediate object to get the id.

Bill
milessabin
Joined: 2008-08-11,
User offline. Last seen 33 weeks 3 days ago.
Re: Implicit conversion doesn't work with primitive type defini

On Fri, Oct 28, 2011 at 4:42 AM, Bill Burdick wrote:
> Thanks, very much Miles!  This is really great, because now I can profile
> the two approaches to see which one is better for my apps.  I suspect that
> using naked Longs will be better, because the space may be roughly the same,
> since references are 1/2 the size of longs (on 32-bit systems and 64-bit
> implementations that support compressed references) and shared references
> may compensate for the overhead of id wrapper objects, but I think naked
> Longs will be faster to use, because you don't have to go through an
> intermediate object to get the id.

If boxing is a deal breaker for you, then I'd suggest taking a close
look at the generated bytecode if there seems to be a problem. I found
that it took a few extra steps to ensure that all boxing was
eliminated when using my tagging trick,

https://gist.github.com/89c9b47a91017973a35f

Cheers,

Miles

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland