Reputation: 12306
I am facing some weird problem with typeclass below: for some reason implicit object ContentUploader is not resolved on call to upload method of DemoActor
.
import akka.actor.Actor
import java.io.File
import org.slf4j.LoggerFactory
class DemoActor extends Actor {
import DemoActor.UploaderImpl._
override def receive = {
case (x: DemoActor.Content) =>
DemoActor.upload(x)
}
}
object DemoActor {
val LOG = LoggerFactory.getLogger("DemoActor")
sealed trait UploadData {
val data: Array[File]
}
case class Content(data: Array[File]) extends UploadData
case class UploadResult(url: String, contentType: String, size: Long)
trait S3Uploader[T <: UploadData] {
def uploadToS3(filez: Array[File]): Iterable[UploadResult]
}
object UploaderImpl {
val LOG = LoggerFactory.getLogger("Uploader")
private def contentType(name: String): String = {
"application/octet-stream"
}
private def doUpload(filez: Array[File], bucketName: String) = {
LOG.debug("Uploading: {} to {}", filez, bucketName)
filez.flatMap {
case f =>
try {
val key = f.getName
val mime = contentType(f.getName)
Some(UploadResult("http://" + bucketName + ".s3.amazonaws.com/" + key, mime, f.length()))
} catch {
case e =>
LOG.error("Can not upload", e)
None
}
}
}
implicit object ContentUploader extends S3Uploader[Content] {
lazy val bucketName = "resources.aws.bucketname"
lazy val awsSecret = "resources.aws.secret.key"
lazy val awsAccess = "resources.aws.access.key"
override def uploadToS3(filez: Array[File]) = doUpload(filez, bucketName)
}
}
def upload[T <: UploadData](src: T)(implicit uploader: S3Uploader[T]) = uploader.uploadToS3(src.data)
}
What have I missed here?
UPD
if I move definition of class for DemoActor inside object DemoActor, like
import akka.actor.Actor
import java.io.File
import org.slf4j.LoggerFactory
object DemoActor {
val LOG = LoggerFactory.getLogger("DemoActor")
sealed trait UploadData {
val data: Array[File]
}
case class Content(data: Array[File]) extends UploadData
case class UploadResult(url: String, contentType: String, size: Long)
trait S3Uploader[UploadData] {
def uploadToS3(filez: Array[File]): Iterable[UploadResult]
}
object UploaderImpl {
val LOG = LoggerFactory.getLogger("Uploader")
private def contentType(name: String): String = {
"application/octet-stream"
}
private def doUpload(filez: Array[File], bucketName: String) = {
LOG.debug("Uploading: {} to {}", filez, bucketName)
filez.flatMap {
case f =>
try {
val key = f.getName
val mime = contentType(f.getName)
Some(UploadResult("http://" + bucketName + ".s3.amazonaws.com/" + key, mime, f.length()))
} catch {
case e =>
LOG.error("Can not upload", e)
None
}
}
}
implicit object ContentUploader extends S3Uploader[DemoActor.Content] {
lazy val bucketName = "resources.aws.bucketname"
lazy val awsSecret = "resources.aws.secret.key"
lazy val awsAccess = "resources.aws.access.key"
override def uploadToS3(filez: Array[File]) = doUpload(filez, bucketName)
}
}
def upload[T <: UploadData](src: T)(implicit uploader: S3Uploader[T]) = uploader.uploadToS3(src.data)
class DemoActor extends Actor {
import DemoActor.UploaderImpl._
override def receive = {
case (x: DemoActor.Content) =>
DemoActor.upload(x)
}
}
}
then everything works well. Are there some issues with namespacing?
Upvotes: 1
Views: 562
Reputation: 297305
It is not finding it because implicit forward references must be explicitly typed to be considered, and this one isn't.
If this is confusing, maybe two ways of fixing it might make it clear. First, you can declare the type of the implicit. Remove the implicit
from the object, and declare a val
pointing to it:
implicit val contentUploader: S3Uploader[DemoActor.Content] = ContentUploader
The second way is moving the class DemoActor
declaration to the end of the file, so it stays after the the object DemoActor
declaration.
The reason it works like this is that the compiler must search for the implicit before the rest of the file is fully typed, so it doesn't know, at that time, that object ContentUploader
satisfy the search.
Upvotes: 5