【问题标题】:Cannot find or construct a Read instance for type: Option[A]无法找到或构造类型的读取实例:选项 [A]
【发布时间】:2021-11-23 03:01:27
【问题描述】:

为什么 doobie 不能将查询转换为 Option[A]?

abstract class CRUDAbs[A: Read](val tableName: String) extends TransactSQL {
  def table: Fragment = Fragment.const(s"$tableName")
  def columnsList: Array[String] = {
    val cls = classTag[A].runtimeClass
    cls.getDeclaredFields.map(_.getName).map(snakeCase)
  }
  def columns: Fragment = Fragment.const(columnsList.mkString(","))
  def find(id: Int): doobie.Query0[Option[A]] =
    (sql"select " ++ columns ++ sql" from " ++ table ++ sql" where " ++ Fragment.const(
      s"${columnsList.head}"
    ) ++ sql" = $id").query[Option[A]]

我收到一个错误

Cannot find or construct a Read instance for type:

  Option[A]

我错过了什么?

【问题讨论】:

    标签: scala typeclass implicit doobie


    【解决方案1】:

    如果我正确恢复了您的代码片段,就像

    import doobie.Read
    import doobie.implicits.toSqlInterpolator
    import doobie.util.fragment.Fragment
    
    import scala.reflect.{ClassTag, classTag}
    
    object App {
      val snakeCase = ???
    
      abstract class CRUDAbs[A: Read: ClassTag](val tableName: String) /*extends TransactSQL*/ {
        def table: Fragment = Fragment.const(s"$tableName")
    
        def columnsList: Array[String] = {
          val cls = classTag[A].runtimeClass
          cls.getDeclaredFields.map(_.getName).map(snakeCase)
        }
    
        def columns: Fragment = Fragment.const(columnsList.mkString(","))
    
        def find(id: Int): doobie.Query0[Option[A]] =
          (sql"select " ++ columns ++ sql" from " ++ table ++ sql" where " ++ Fragment.const(
            s"${columnsList.head}"
          ) ++ sql" = $id").query[Option[A]]
      }
    }
    

    整个编译错误是

    Cannot find or construct a Read instance for type:
    
      Option[A]
    
    This can happen for a few reasons, but the most common case is that a data
    member somewhere within this type doesn't have a Get instance in scope. Here are
    some debugging hints:
    
    - For Option types, ensure that a Read instance is in scope for the non-Option
      version.
    - For types you expect to map to a single column ensure that a Get instance is
      in scope.
    - For case classes, HLists, and shapeless records ensure that each element
      has a Read instance in scope.
    - Lather, rinse, repeat, recursively until you find the problematic bit.
    
    You can check that an instance exists for Read in the REPL or in your code:
    
      scala> Read[Foo]
    
    and similarly with Get:
    
      scala> Get[Foo]
    
    And find the missing instance and construct it as needed. Refer to Chapter 12
    of the book of doobie for more information.
    
          ) ++ sql" = $id").query[Option[A]]
    

    请注意For Option types, ensure that a Read instance is in scope for the non-Option version.

    Option[A] 类型有一个类型类Read 的实例,前提是A 类型有一个类型类Get 的实例

    implicit def fromGetOption[A](implicit ev: Get[A]): Read[Option[A]] =
      new Read(List((ev, Nullable)), ev.unsafeGetNullable)
    

    https://github.com/tpolecat/doobie/blob/main/modules/core/src/main/scala/doobie/util/read.scala#L76-L77

    所以尝试使用doobie.Get修改类CRUDAbs的定义

    abstract class CRUDAbs[A: Get: ClassTag](val tableName: String)
    

    其实,让我们回到Read 上下文绑定。 turns out(上面的编译错误中提到的“doobie 书第 12 章”)类型类 Get 用于非可选(不可为空)单变量(单列)类型,而类型Put 类也适用于可选(可为空)或多变量(向量)类型。让我们定义一个类似于Read.fromGetOption的隐式

    implicit def fromGetOption[A](implicit ev: Get[A]): Read[Option[A]] =
      new Read(List((ev, Nullable)), ev.unsafeGetNullable)
    

    即让我们定义

    implicit def fromReadOption[A: Read]: Read[Option[A]] = Read[A].map(Some(_)) // Read[A].map(Option(_))
    

    现在下面的代码编译

    import doobie.Read
    import doobie.implicits.toSqlInterpolator
    import doobie.util.fragment.Fragment
    import scala.reflect.{ClassTag, classTag}
    
    object App {
      val snakeCase = ???
    
      implicit def fromReadOption[A: Read]: Read[Option[A]] = Read[A].map(Some(_))
    
      abstract class CRUDAbs[A: Read: ClassTag](val tableName: String) /*extends TransactSQL*/ {
        def table: Fragment = Fragment.const(s"$tableName")
    
        def columnsList: Array[String] = {
          val cls = classTag[A].runtimeClass
          cls.getDeclaredFields.map(_.getName).map(snakeCase)
        }
    
        def columns: Fragment = Fragment.const(columnsList.mkString(","))
    
        def find(id: Int): doobie.Query0[Option[A]] =
          (sql"select " ++ columns ++ sql" from " ++ table ++ sql" where " ++ Fragment.const(
            s"${columnsList.head}"
          ) ++ sql" = $id").query[Option[A]]
      }
    
      case class Chain0(i: Int)
      case class Chain(i: Int, s: String)
      case class Chain1(i: Int, s: Option[String])
      class CRUDChain0 extends CRUDAbs[Chain0]("chain") // compiles
      class CRUDChain extends CRUDAbs[Chain]("chain") // compiles
      class CRUDChain1 extends CRUDAbs[Chain1]("chain") // compiles
    }
    

    https://scastie.scala-lang.org/DmytroMitin/2kjpdtvaSVqxK6IhnmVEVQ


    或者不定义隐式fromReadOption而是用两个隐式参数替换绑定的上下文

    abstract class CRUDAbs[A: ClassTag](val tableName: String)(implicit r: Read[A], optR: Read[Option[A]])
    

    abstract class CRUDAbs[A: Read : ClassTag](val tableName: String)(implicit optR: Read[Option[A]])
    

    kind-projector 语法

    abstract class CRUDAbs[A: Read : λ[X => Read[Option[X]]] : ClassTag](val tableName: String)
    

    https://scastie.scala-lang.org/DmytroMitin/2kjpdtvaSVqxK6IhnmVEVQ/1

    【讨论】:

    • 我尝试这样做,但出现以下错误:could not find implicit value for evidence parameter of type doobie.util.Get[Entities.Chain] class CRUDChain extends CRUDAbs[Chain]("chain")。当我写的时候:class CRUDChain extends CRUDAbs[Chain]("chain")我真的需要为每个域类创建一个 Get 实例吗?
    • @МишаПопов 查看更新。恢复原来的上下文绑定,再定义一个隐含的。您不需要为每个域类创建Get
    • @МишаПопов 或者不定义隐式而是用两个隐式参数替换上下文绑定。查看更新。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-22
    • 2020-09-05
    • 2019-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多