无法找到或构造类型的 Read 实例:Option[A]
Cannot find or construct a Read instance for type: Option[A]
为什么 doobie 无法将查询转换为选项[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]
我错过了什么?
如果我正确地恢复了你的代码片段,它就像
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]
有类型 class Read
的实例,前提是类型 [=24] 有类型 class Get
的实例=]
implicit def fromGetOption[A](implicit ev: Get[A]): Read[Option[A]] =
new Read(List((ev, Nullable)), ev.unsafeGetNullable)
因此尝试使用 doobie.Get
修改 class CRUDAbs
的定义
abstract class CRUDAbs[A: Get: ClassTag](val tableName: String)
实际上,让我们 return 到 Read
上下文绑定。它turns out ("Chapter 12 of the book of doobie" mentioned in the above compile error) that the type class Get
is for non-optional (non-nullable) single-variable (single-column) types while the type class Put
is also for optional (nullable) or multi-variable (vector) types. Just let's define an implicit similar to 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
为什么 doobie 无法将查询转换为选项[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]
我错过了什么?
如果我正确地恢复了你的代码片段,它就像
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]
有类型 class Read
的实例,前提是类型 [=24] 有类型 class Get
的实例=]
implicit def fromGetOption[A](implicit ev: Get[A]): Read[Option[A]] =
new Read(List((ev, Nullable)), ev.unsafeGetNullable)
因此尝试使用 doobie.Get
CRUDAbs
的定义
abstract class CRUDAbs[A: Get: ClassTag](val tableName: String)
实际上,让我们 return 到 Read
上下文绑定。它turns out ("Chapter 12 of the book of doobie" mentioned in the above compile error) that the type class Get
is for non-optional (non-nullable) single-variable (single-column) types while the type class Put
is also for optional (nullable) or multi-variable (vector) types. Just let's define an implicit similar to 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