10

In my Scala project, almost all my files have these imports:

import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import eu.timepit.refined.numeric._

import spire.math._
import spire.implicits._

import com.wix.accord._
import com.wix.accord.dsl._

import codes.reactive.scalatime._

import better.files._

import java.time._

import scala.collection.mutable
...
...

What is the best way to DRY this in Scala? Can I specify all of them for my project (using some kind of sbt plugin?) or at the package level?

pathikrit
  • 32,469
  • 37
  • 142
  • 221

2 Answers2

7

I've seen a few approaches that kinda solve what you're looking for. Check out

Imports defined

https://github.com/mongodb/casbah/blob/master/casbah-core/src/main/scala/Implicits.scala

Small example of this approach:

object Imports extends Imports with commons.Imports with query.Imports with query.dsl.FluidQueryBarewordOps

object BaseImports extends BaseImports with commons.BaseImports with query.BaseImports

object TypeImports extends TypeImports with commons.TypeImports with query.TypeImports

trait Imports extends BaseImports with TypeImports with Implicits

@SuppressWarnings(Array("deprecation"))
trait BaseImports {
  // ...
  val WriteConcern = com.mongodb.casbah.WriteConcern
  // More here ...
}

trait TypeImports {
  // ...
  type WriteConcern = com.mongodb.WriteConcern
  // ... 
}

Imports used

https://github.com/mongodb/casbah/blob/master/casbah-core/src/main/scala/MongoClient.scala

When they use this import object, it unlocks all your type aliases for you. For example, WriteConcern

import com.mongodb.casbah.Imports._
// ...
def setWriteConcern(concern: WriteConcern): Unit = underlying.setWriteConcern(concern)

Essentially they wrap up all the imports into a common Import object, then just use import com.mycompany.Imports._

Doobie does something similar where most of the end-users just import doobie.imports._

https://github.com/tpolecat/doobie/blob/series/0.3.x/yax/core/src/main/scala/doobie/imports.scala

Again, a sample from this pattern:

object imports extends ToDoobieCatchSqlOps with ToDoobieCatchableOps {
  /**
   * Alias for `doobie.free.connection`.
   * @group Free Module Aliases
   */
  val FC   = doobie.free.connection

  /**
   * Alias for `doobie.free.statement`.
   * @group Free Module Aliases
   */
  val FS   = doobie.free.statement

  // More here ...
}

The main difference in this approach between the package object style is you get more control over what/when to import. I've used both patterns, usually a package object for common utility methods I'll need across an internal package. And for libraries, specifically the users of my code, I can attach certain implicit definitions to an import object like in doobie mentioned above that will unlock a DSL syntax for the user using a single import.

Brian Pendleton
  • 839
  • 4
  • 13
  • 3
    Put the aliases into package object com.acme, then each file is `package com.acme ; package projectx`. No need to import. – som-snytt Oct 09 '16 at 22:49
  • @som-snytt: I don't understand your comment - perhaps post a new answer? You can use the imports from my original question – pathikrit Aug 23 '18 at 13:38
  • 1
    @pathikrit I meant like the other answer, pick up the aliases from enclosing package. But soon you can use `-Yimports` to do your custom Predef. I think the syntax for the option may change before 2.13, but `-Yimports:com.acme._` or similar. – som-snytt Aug 24 '18 at 01:32
4

I would probably go with the scala.Predef approach: basically, alias the types and expose the objects I want to make available. So e.g.

package com.my

package object project {
  type LocalDate = java.time.LocalDate
  type LocalDateTime = java.time.LocalDateTime
  type LocalTime = java.time.LocalTime

  import scala.collection.mutable

  type MutMap[A, B] = mutable.Map[A, B]
  val MutMap = mutable.Map
  // And so on....
}

Now, wherever you start a file with package com.my.project, all of the above will be automatically available. Btw, kudos also to @som-snytt for pointing this out.

Community
  • 1
  • 1
Yawar
  • 11,272
  • 4
  • 48
  • 80
  • Can you answer this in terms of the imports in OP's original question? I understand how simple stuff from Scala and Java library might work this way but what about all the cats/spire imports? – pathikrit Aug 23 '18 at 13:40
  • @pathikrit to get lots of implicits imported into lots of files I don't have a good solution for you. In fact nowadays I would recommend not to even do blanket imports at the top of the file, but rather import in as tight a scope (locally) as possible: https://gist.github.com/yawaramin/f39b109a1acc9b8263ab38259cbf88f5 – Yawar Aug 25 '18 at 10:31