Reputation: 14309
I have a use-case maybe not specific to Slick but to for-comprehensions. The high level pattern is like this but it leads to a compiler error in enum3:
(for {
enum1 <- // ...
enum2 <- // ...
enum3 <- optionArg.fold(empty result).map { ... }
} yield ())
Then I end up doing the following that compiles but has code duplication namely of enum1 and enum2:
optionArg match {
case (Some(arg)) => {
(for {
enum1 <- // ...
enum2 <- // ...
enum3 <- // do something with arg
} yield ())
}
case None => {
(for {
enum1 <- // ...
enum2 <- // ...
} yield ())
}
}
}
In concrete I have that this repetitive variant compiles:
/**
* Returns the inserted `oauth2Info` instance including the params. We first
* look up the `LoginInfo` by the relevant search criteria, fetching its `userId`
* which is then used to persist a `OAuth2Info` and multiple `OAuth2InfoParam`.
*
* @param extLoginInfo The login info for which the auth info should be added.
* @param extOAuth2Info The TOTP info to add containing the params.
* @return the inserted `oauth2Info` instance including the params.
*/
def add(extLoginInfo: ExtLoginInfo, extOAuth2Info: ExtOAuth2Info): Future[ExtOAuth2Info] = {
val insertion = extOAuth2Info.params match {
case Some(params) => {
(for {
userId <- LoginInfo.filter { loginInfo => loginInfo.providerId === extLoginInfo.providerID && loginInfo.providerKey === extLoginInfo.providerKey }.map(_.userId).result.head
_ <- (OAuth2Info += OAuth2InfoRow(userId, extOAuth2Info.accessToken, extOAuth2Info.tokenType, extOAuth2Info.expiresIn, extOAuth2Info.refreshToken))
_ <- DBIOAction.sequence(params.map { param => (OAuth2InfoParam += OAuth2InfoParamRow(userId, param._1, param._2)) })
} yield ())
}
case None => {
(for {
userId <- LoginInfo.filter { loginInfo => loginInfo.providerId === extLoginInfo.providerID && loginInfo.providerKey === extLoginInfo.providerKey }.map(_.userId).result.head
_ <- (OAuth2Info += OAuth2InfoRow(userId, extOAuth2Info.accessToken, extOAuth2Info.tokenType, extOAuth2Info.expiresIn, extOAuth2Info.refreshToken))
} yield ())
}
}
db.run(insertion.transactionally).map(_ => extOAuth2Info)
}
and this succinct desired variant doesn't:
def add(extLoginInfo: ExtLoginInfo, extOAuth2Info: ExtOAuth2Info): Future[ExtOAuth2Info] = {
val insertion = (for {
userId <- LoginInfo.filter { loginInfo => loginInfo.providerId === extLoginInfo.providerID && loginInfo.providerKey === extLoginInfo.providerKey }.map(_.userId).result.head
_ <- (OAuth2Info += OAuth2InfoRow(userId, extOAuth2Info.accessToken, extOAuth2Info.tokenType, extOAuth2Info.expiresIn, extOAuth2Info.refreshToken))
_ <- extOAuth2Info.params.fold(DBIOAction.seq()) { params =>
DBIOAction.sequence(params.map { param => (OAuth2InfoParam += OAuth2InfoParamRow(userId, param._1, param._2)) })
}
} yield ()).transactionally
db.run(insertion).map(_ => extOAuth2Info)
}
Gives compiles error:
[play-silhouette-seed] $ compile
[info] Formatting 1 Scala source ProjectRef(uri("file:/home/skywalker/code/play-silhouette-seed/"), "root")(compile) ...
[info] Compiling 1 Scala source to /home/skywalker/code/play-silhouette-seed/target/scala-2.12/classes ...
[error] /home/skywalker/code/play-silhouette-seed/app/models/daos/OAuth2InfoDaoImpl.scala:58:28: type mismatch;
[error] found : slick.dbio.DBIOAction[scala.collection.immutable.Iterable[Int],slick.dbio.NoStream,slick.dbio.Effect.Write]
[error] required: slick.dbio.DBIOAction[Unit,slick.dbio.NoStream,slick.dbio.Effect]
[error] DBIOAction.sequence(params.map { param => (OAuth2InfoParam += OAuth2InfoParamRow(userId, param._1, param._2)) })
[error] ^
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 1 s, completed May 28, 2019 10:42:00 AM
Upvotes: 1
Views: 91
Reputation: 262494
This is a problem with fold
when the compiler cannot type-infer the proper generic types for the expression. It does not properly widen the type returned from the first expression to the type of the second one. You need to help the compiler a bit.
It also happens in situations like this
Some(123).fold(None)(_ => Some("123"))
// type mismatch; found: Some[String] required: None.type
You can change fold
to map().getOrElse()
(then the "good type" comes first and get be inferred).
Or you can add type annotations somewhere, such as
private val noAction: slick.dbio.DBIOAction[Iterable[Int],NoStream,Effect.Write]
= DBIOAction.sequence()
theOption.fold(noAction)(params => .... )
Also, if you find yourself duplicating code, this can be mitigated by moving the common expression out. With functional (side-effect-free) code like Slick, this kind of refactoring is quite safe. You can just build a couple of val
or def
with the Slick actions you need to run and compose them later.
def add(extLoginInfo: ExtLoginInfo, extOAuth2Info: ExtOAuth2Info): Future[ExtOAuth2Info] = {
val getUserId = LoginInfo.filter { loginInfo => loginInfo.providerId === extLoginInfo.providerID && loginInfo.providerKey === extLoginInfo.providerKey }.map(_.userId).result.head
val insertOAuth = for {
userId <- getUserId
_ = (OAuth2Info += OAuth2InfoRow(userId, extOAuth2Info.accessToken, extOAuth2Info.tokenType, extOAuth2Info.expiresIn, extOAuth2Info.refreshToken))
} yield userId
val insertion = extOAuth2Info.params match {
case Some(params) => {
(for {
userId <- insertOAuth
_ <- DBIOAction.sequence(params.map { param => (OAuth2InfoParam += OAuth2InfoParamRow(userId, param._1, param._2)) })
} yield ())
}
case None =>
insertOAuth
}
db.run(insertion.transactionally).map(_ => extOAuth2Info)
}
Upvotes: 1