質問

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?

役に立ちましたか?

解決

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.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top