Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: Importing implicit conversions in all subpackages of some package

I defined implicit conversions in an object. Let's call the object Implicits and there is one implicit conversion in it.

package com.gmail.naetmul.stackoverflow.app

object Implicits {
  implicit def int_to_intEx(x: Int): IntEx = IntEx(x)
}

This object is in some package. I want to use this implicit conversion in every code in the package com.gmail.naetmul.stackoverflow.app and all of its sub-packages like com.gmail.naetmul.stackoverflow.app.something.anything.everything.

So I made the package object of com.gmail.naetmul.stackoverflow.app.

package com.gmail.naetmul.stackoverflow

package object app {
  import com.gmail.naetmul.stackoverflow.app.Implicits._
}

But it didn't work outside of the exact package object. So I changed the object Implicits to trait and let the package object extend it.

package com.gmail.naetmul.stackoverflow.app

trait Implicits {
  implicit def int_to_intEx(x: Int): IntEx = IntEx(x)
}
package com.gmail.naetmul.stackoverflow

import com.gmail.naetmul.stackoverflow.app.Implicits

package object app extends Implicits {
  // some code
}

The implicit conversion worked in the package com.gmail.naetmul.stackoverflow.app. However, it either worked or did not work in the sub-package.

For example)

File A.scala

package com.gmail.naetmul.stackoverflow.app.database

class A {
  // Here, the implicit conversion did NOT work.
}

File B.scala

package com.gmail.naetmul.stackoverflow.app
package database

class B {
  // Here, the implicit conversion DID work.
}

So the question is:

  1. Should I use trait instead of object in this case (using with the package object, but defined outside)?

  2. Is there another way to use the implicit conversions in subpackages? I mean, import only once, and use them everywhere. The way that worked in B.scala seems fine, but the Eclipse's default package statement is like A.scala, so I have to change them manually.

like image 879
Naetmul Avatar asked Jan 06 '14 04:01

Naetmul


1 Answers

To question 1, it can be useful to stash implicits in superclasses as "LowPriorityImplicits", but that doesn't seem to be the use case here.

To question 2, nested packages is the usual way to bring the implicits into scope. The spec 9.2 on "packagings" uses the magic phrase, "visible under their simple names."

But there are a few strategies for exploiting implicit scope.

I'd guess from your partial example that you want to enhance Int with a method like odd. For this use case, supplying a missing member, you have to supply a conversion as you've done.

But there are other choices for API design. For example:

Given an API:

package goofy

object API { 
  def api(i: IntEx) = i.odd
}

and a client of the API in a subpackage:

package goofy.foo.bar.baz

import org.junit.Test

class UseIntExTest {
  import goofy.API._
  @Test def usage(): Unit = {
    assert(api(3))
  }
}

where you've imported the API and you get the implicit scope "for free".

The conversion is in a companion object:

package goofy

class IntEx(val i: Int) {
  def even = i % 2 == 0
  def odd  = !even
}

object IntEx {
  implicit private[goofy] def `enhanced IntEx`(i: Int): IntEx = new IntEx(i)
}

This works because IntEx is an expected type. You can also exploit type parameters in the same way.

For completeness, to show that the conversion is constrained to subpackages:

package roofy

import org.junit.Test 

class BadUsageTest {
  import goofy.API._
  @Test def usage(): Unit = {
    //assert(api(3))  // DNC
  }
}
like image 100
som-snytt Avatar answered Oct 22 '22 02:10

som-snytt