|
|
Although I cannot use the Scala language at work because the productivity of our team would crater, I have still an interest in a language I would love to be able to use in a business world. Deciding that perhaps I was too harsh on the language, I have been playing with a couple of Scala libraries to try and give the language another shot in my off hours work. I just spent the better part of 7 hours playing with Slick 3.2.0 in Akka and Scala. I was massively frustrated and it turned out that yet again it is an implicit conversion that hosed me. Consider the following slick project.
Users.scala package persistence.entities
import slick.driver.H2Driver.api._
case class User(id: Long, name: String)
class UserTable(tag: Tag) extends Table[User](tag, "user") { def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = (id, name) <> (User.tupled, User.unapply) }
UsersDAL.scala package persistence.dal
import persistence.entities.{Tables, User, UserTable} import slick.jdbc.JdbcProfile import slick.lifted.Query
import scala.concurrent.Future
trait UserDAL { def findById(id: Long) : Future[Option[User]] }
class UserDALImpl(implicit val db: JdbcProfile#Backend#Database) extends UserDAL { override def findById(userId: Long) = null
def foo (userId : Long) = { val filter: Query[UserTable, User, Seq] = Tables.users.filter(_.id == userId) db.run(filter.result) } }
Should be simple right? No. The db.run(filter.result) line does not compile on the result call. OK, time to comb the Slick documentation. No dice, my code looks just like theirs does fundamentally. So why is the result method not available. Do I have the wrong type? Nope. What about the wrong library? Nope. Update from 3.1.1 to 3.2.0 ? Done ... but ... no. What about the wrong structure for my table? Am I missing something? Comb example code ... and nope. OK, I am frustrated as all get out. In the meantime my irritation grows when I see example code like the following.
override def insert(rows: Seq[A]): Future[Seq[Long]] = { db.run(tableQ returning tableQ.map(_.id) ++= rows.filter(_.isValid)) }
Good god, how to decode that mess. Obviously run is taking something as a parameter but exactly what is the order of operations here? Anyway, I digress.
Back to the subject at hand, I am at a complete loss for how to explain the problem here so, I decide to create a post on the Slick forums to figure out what I am doing wrong. I think, for simplicity sake lets combine all of our classes into one file.
package persistence.entities
import java.time.Instant import slick.driver.H2Driver.api._ import slick.jdbc.JdbcProfile import slick.lifted.{Query, TableQuery} import scala.concurrent.Future
case class User(id: Long, name: String)
class UserTable(tag: Tag) extends Table[User](tag, "user") { def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = (id, name) <> (User.tupled, User.unapply) }
object Users { val users = TableQuery[UserTable]
trait UserDAL { def findById(id: Long) : Future[Option[User]] }
class UserDALImpl(implicit val db: JdbcProfile#Backend#Database) extends UserDAL { override def findById(userId: Long) = null
def foo (userId : Long) = { val filter: Query[UserTable, User, Seq] = Tables.users.filter(_.id == userId) db.run(filter.result) } } }
OK, now that we have combined them we are ready to post this .. but wait ... it compiles now. What The Fork? Oh wait. There was an implicit conversion going on in there somewhere. There was something in the new file converting our query type into something that had the result method predefined. Magically, behind the scenes and with the right import it doesn't happen and the code doesn't compile.After experimentally deleting imports i discover it's import slick.driver.H2Driver.api._ that is the home of the implicit conversion. So I go look at the file and nope, no implicit. I drill and drill and finally find it. Wonderful. 7+ hours wasted for this. The fun part is the implict conversion happened as a result of the implicit conversion of an implicit conversion of an implicit conversion. Do the designers of Scala think this is actually good? You are just supposed to "know" that the conversion of a conversion of a conversion is happening I guess. Or is the goal to make the code so cryptic and opaque that the source of the library is entirely unreadable?
Given my original UsersDAL file there was no way for me to know that to make the code compile I had to find some implict conversion that could turn a Query into a DBIOAction. There is no means for you to know where this conversion is, what file has to be imported. All of the example code just assumes you must already know. Its fine copying the example code but if you modify, reorganize code into many files you break the delicately balanced Jenga tower and it doesn't work.
So after playing around for a few days with this project, I can see that if I ever brought this code into a business environment my CEO would absolutely murder me as it would take 6 months to add the most basic of features, not to mention 1+ years to train a new developer to be even moderately productive. Once again, its the implicit and its use in the language that is at the core of the problem. It actually makes me sad, but it would be wholesale irresponsible of me as a Principal Software Architect to recommend these techs in a business that makes its money putting out product for users in a short time.
So why post this? Because otherwise i LIKE Scala and wish it could be different. But its becoming like the JDK, they have decided that is the direction they are going and no one on the planet can convince them otherwise. OK now that I am done stating my opinion, I am ready to get flamed again. :-)
-- Robert Simmons Jr.
--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
|
|
I can really agree with you that Slick’s documentation leaves a little to be desired. The content is all there but hard to navigate.
But you have in fact _not_ followed their way of implementing code. You are directly importing the driver profile but forgetting the api._-import. Sure, you get very hard to decipher error messages - I wonder if something can be done about that. But you are still missing an import.
I’ve spent/ wasted, a matter of perspective really, a lot of time on Slick and really really like it. A book that totally did it for me is Essential Slick by Richard Dallaway and Jonathan Ferguson. Look it up. (No really!)
There are some things to Slick that, once understood, sort of reveals the whole pie in one go.
Queries produce results by executing actions onto the database. Both of those steps are asynchronous and depending on how you compose your actions, there may be a quirk or two around there too.
Patrik
Although I cannot use the Scala language at work because the productivity of our team would crater, I have still an interest in a language I would love to be able to use in a business world. Deciding that perhaps I was too harsh on the language, I have been playing with a couple of Scala libraries to try and give the language another shot in my off hours work. I just spent the better part of 7 hours playing with Slick 3.2.0 in Akka and Scala. I was massively frustrated and it turned out that yet again it is an implicit conversion that hosed me. Consider the following slick project.
Users.scala package persistence.entities
import slick.driver.H2Driver.api._
case class User(id: Long, name: String)
class UserTable(tag: Tag) extends Table[User](tag, "user") { def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = (id, name) <> (User.tupled, User.unapply) }
UsersDAL.scala package persistence.dal
import persistence.entities.{Tables, User, UserTable} import slick.jdbc.JdbcProfile import slick.lifted.Query
import scala.concurrent.Future
trait UserDAL { def findById(id: Long) : Future[Option[User]] }
class UserDALImpl(implicit val db: JdbcProfile#Backend#Database) extends UserDAL { override def findById(userId: Long) = null
def foo (userId : Long) = { val filter: Query[UserTable, User, Seq] = Tables.users.filter(_.id == userId) db.run(filter.result) } }
Should be simple right? No. The db.run(filter.result) line does not compile on the result call. OK, time to comb the Slick documentation. No dice, my code looks just like theirs does fundamentally. So why is the result method not available. Do I have the wrong type? Nope. What about the wrong library? Nope. Update from 3.1.1 to 3.2.0 ? Done ... but ... no. What about the wrong structure for my table? Am I missing something? Comb example code ... and nope. OK, I am frustrated as all get out. In the meantime my irritation grows when I see example code like the following.
override def insert(rows: Seq[A]): Future[Seq[Long]] = { db.run(tableQ returning tableQ.map(_.id) ++= rows.filter(_.isValid)) }
Good god, how to decode that mess. Obviously run is taking something as a parameter but exactly what is the order of operations here? Anyway, I digress.
Back to the subject at hand, I am at a complete loss for how to explain the problem here so, I decide to create a post on the Slick forums to figure out what I am doing wrong. I think, for simplicity sake lets combine all of our classes into one file.
package persistence.entities
import java.time.Instant import slick.driver.H2Driver.api._ import slick.jdbc.JdbcProfile import slick.lifted.{Query, TableQuery} import scala.concurrent.Future
case class User(id: Long, name: String)
class UserTable(tag: Tag) extends Table[User](tag, "user") { def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = (id, name) <> (User.tupled, User.unapply) }
object Users { val users = TableQuery[UserTable]
trait UserDAL { def findById(id: Long) : Future[Option[User]] }
class UserDALImpl(implicit val db: JdbcProfile#Backend#Database) extends UserDAL { override def findById(userId: Long) = null
def foo (userId : Long) = { val filter: Query[UserTable, User, Seq] = Tables.users.filter(_.id == userId) db.run(filter.result) } } }
OK, now that we have combined them we are ready to post this .. but wait ... it compiles now. What The Fork? Oh wait. There was an implicit conversion going on in there somewhere. There was something in the new file converting our query type into something that had the result method predefined. Magically, behind the scenes and with the right import it doesn't happen and the code doesn't compile.After experimentally deleting imports i discover it's import slick.driver.H2Driver.api._ that is the home of the implicit conversion. So I go look at the file and nope, no implicit. I drill and drill and finally find it. Wonderful. 7+ hours wasted for this. The fun part is the implict conversion happened as a result of the implicit conversion of an implicit conversion of an implicit conversion. Do the designers of Scala think this is actually good? You are just supposed to "know" that the conversion of a conversion of a conversion is happening I guess. Or is the goal to make the code so cryptic and opaque that the source of the library is entirely unreadable?
Given my original UsersDAL file there was no way for me to know that to make the code compile I had to find some implict conversion that could turn a Query into a DBIOAction. There is no means for you to know where this conversion is, what file has to be imported. All of the example code just assumes you must already know. Its fine copying the example code but if you modify, reorganize code into many files you break the delicately balanced Jenga tower and it doesn't work.
So after playing around for a few days with this project, I can see that if I ever brought this code into a business environment my CEO would absolutely murder me as it would take 6 months to add the most basic of features, not to mention 1+ years to train a new developer to be even moderately productive. Once again, its the implicit and its use in the language that is at the core of the problem. It actually makes me sad, but it would be wholesale irresponsible of me as a Principal Software Architect to recommend these techs in a business that makes its money putting out product for users in a short time.
So why post this? Because otherwise i LIKE Scala and wish it could be different. But its becoming like the JDK, they have decided that is the direction they are going and no one on the planet can convince them otherwise. OK now that I am done stating my opinion, I am ready to get flamed again. :-)
-- Robert Simmons Jr.
--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
|
|
--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
|
|
It occurs to me that I was using the term "extension methods" extremely loosely here to cover "any kind of implicit conversion enabling invocation of a method that doesn't exist on the type it's called on".
Technically, Scala doesn't have extension methods at all, and implicit classes which serve the same purpose as a dedicated extension method feature are not the only way to get methods appearing--any implicit conversion can do it. But the point is that if you see foo.bar and foo's type doesn't have a bar method on it, it shouldn't be a huge surprise.
--Rex
--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
|
|
I and my team love Scala and Slick for our business, but we empathise with your feelings about implicits.
We strongly discourage the use of implicits in our own code, and import them only when necessary (e.g. Futures and scala.concurrent.ExecutionContext.Implicits.global). We also went through all our code, ripped out all the scala.collection.JavaConversions, and replaced them with scala.collection.JavaConverters.
We don't like implicits because they are "magic"... They are invisible and change the behavior of the code.
But, luckily, you can (mostly) avoid implicits if you disapprove on them :-)
P
--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
|
|
I like Slick a lot. But if you want to avoid some of the more advanced ways scala can be used, I would recommend using a different database library. I hear good things about ScalikeJDBC from people with that kind of preference. I and my team love Scala and Slick for our business, but we empathise with your feelings about implicits.
We strongly discourage the use of implicits in our own code, and import them only when necessary (e.g. Futures and scala.concurrent.ExecutionContext.Implicits.global). We also went through all our code, ripped out all the scala.collection.JavaConversions, and replaced them with scala.collection.JavaConverters.
We don't like implicits because they are "magic"... They are invisible and change the behavior of the code.
But, luckily, you can (mostly) avoid implicits if you disapprove on them :-)
--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
|
|
Perhaps have a look at what I wrote here: https://groups.google.com/d/msg/scala-user/atl2dHv1gMY/NeQVI_5KAgAJAlthough I cannot use the Scala language at work because the productivity of our team would crater, I have still an interest in a language I would love to be able to use in a business world. Deciding that perhaps I was too harsh on the language, I have been playing with a couple of Scala libraries to try and give the language another shot in my off hours work. I just spent the better part of 7 hours playing with Slick 3.2.0 in Akka and Scala. I was massively frustrated and it turned out that yet again it is an implicit conversion that hosed me. Consider the following slick project.
Users.scala package persistence.entities
import slick.driver.H2Driver.api._
case class User(id: Long, name: String)
class UserTable(tag: Tag) extends Table[User](tag, "user") { def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = (id, name) <> (User.tupled, User.unapply) }
UsersDAL.scala package persistence.dal
import persistence.entities.{Tables, User, UserTable} import slick.jdbc.JdbcProfile import slick.lifted.Query
import scala.concurrent.Future
trait UserDAL { def findById(id: Long) : Future[Option[User]] }
class UserDALImpl(implicit val db: JdbcProfile#Backend#Database) extends UserDAL { override def findById(userId: Long) = null
def foo (userId : Long) = { val filter: Query[UserTable, User, Seq] = Tables.users.filter(_.id == userId) db.run(filter.result) } }
Should be simple right? No. The db.run(filter.result) line does not compile on the result call. OK, time to comb the Slick documentation. No dice, my code looks just like theirs does fundamentally. So why is the result method not available. Do I have the wrong type? Nope. What about the wrong library? Nope. Update from 3.1.1 to 3.2.0 ? Done ... but ... no. What about the wrong structure for my table? Am I missing something? Comb example code ... and nope. OK, I am frustrated as all get out. In the meantime my irritation grows when I see example code like the following.
override def insert(rows: Seq[A]): Future[Seq[Long]] = { db.run(tableQ returning tableQ.map(_.id) ++= rows.filter(_.isValid)) }
Good god, how to decode that mess. Obviously run is taking something as a parameter but exactly what is the order of operations here? Anyway, I digress.
Back to the subject at hand, I am at a complete loss for how to explain the problem here so, I decide to create a post on the Slick forums to figure out what I am doing wrong. I think, for simplicity sake lets combine all of our classes into one file.
package persistence.entities
import java.time.Instant import slick.driver.H2Driver.api._ import slick.jdbc.JdbcProfile import slick.lifted.{Query, TableQuery} import scala.concurrent.Future
case class User(id: Long, name: String)
class UserTable(tag: Tag) extends Table[User](tag, "user") { def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name")
def * = (id, name) <> (User.tupled, User.unapply) }
object Users { val users = TableQuery[UserTable]
trait UserDAL { def findById(id: Long) : Future[Option[User]] }
class UserDALImpl(implicit val db: JdbcProfile#Backend#Database) extends UserDAL { override def findById(userId: Long) = null
def foo (userId : Long) = { val filter: Query[UserTable, User, Seq] = Tables.users.filter(_.id == userId) db.run(filter.result) } } }
OK, now that we have combined them we are ready to post this .. but wait ... it compiles now. What The Fork? Oh wait. There was an implicit conversion going on in there somewhere. There was something in the new file converting our query type into something that had the result method predefined. Magically, behind the scenes and with the right import it doesn't happen and the code doesn't compile.After experimentally deleting imports i discover it's import slick.driver.H2Driver.api._ that is the home of the implicit conversion. So I go look at the file and nope, no implicit. I drill and drill and finally find it. Wonderful. 7+ hours wasted for this. The fun part is the implict conversion happened as a result of the implicit conversion of an implicit conversion of an implicit conversion. Do the designers of Scala think this is actually good? You are just supposed to "know" that the conversion of a conversion of a conversion is happening I guess. Or is the goal to make the code so cryptic and opaque that the source of the library is entirely unreadable?
Given my original UsersDAL file there was no way for me to know that to make the code compile I had to find some implict conversion that could turn a Query into a DBIOAction. There is no means for you to know where this conversion is, what file has to be imported. All of the example code just assumes you must already know. Its fine copying the example code but if you modify, reorganize code into many files you break the delicately balanced Jenga tower and it doesn't work.
So after playing around for a few days with this project, I can see that if I ever brought this code into a business environment my CEO would absolutely murder me as it would take 6 months to add the most basic of features, not to mention 1+ years to train a new developer to be even moderately productive. Once again, its the implicit and its use in the language that is at the core of the problem. It actually makes me sad, but it would be wholesale irresponsible of me as a Principal Software Architect to recommend these techs in a business that makes its money putting out product for users in a short time.
So why post this? Because otherwise i LIKE Scala and wish it could be different. But its becoming like the JDK, they have decided that is the direction they are going and no one on the planet can convince them otherwise. OK now that I am done stating my opinion, I am ready to get flamed again. :-)
-- Robert Simmons Jr.
--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
|
|
--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.
|
|