0
votes

I'm wondering if I have a similar scenario to the following, how I can prevent a cyclic implicit conversion?

Edit: A bit of context this is for converting between some classes used as ORM entities and case classes used as DTOs.

class Author(var name: String) {
  def books : List[Book] = List(new Book("title", this))// get books
}

class Book(var title: String, var author: Author)

case class DTOBook(title: String, author: Option[DTOAuthor])

case class DTOAuthor(name: String, books: List[DTOBook])

implicit def author2Author(author: Author) : DTOAuthor = {
  DTOAuthor(author.name, author.books.map(x => x : DTOBook) : List[DTOBook])
}

implicit def book2Book(book: Book) : DTOBook = {
  DTOBook(book.title, Option(book.author : DTOAuthor))
}

val author: DTOAuthor = new Author("John Brown")
2

2 Answers

1
votes

The problem is that your data structure is cyclic. An Author contains Books which contain an Author, which contains Books, etc..

So when you convert Author to DTOAuthor, something like this happens:

  • author2Author is called
  • Inside the first author2Author call, author.books must be converted to List[DTOBook].
  • This means that the author inside each Book must be converted to a DTOAuthor.
  • Repeat

You can workaround this by making the author that is nested within each Book have a list of books that is empty. To do this, you'll have to remove your reliance on the implicit conversion in one place, and manually create the nested DTOAuthor with no books.

implicit def book2Book(book: Book) : DTOBook = {
  DTOBook(book.title, Option(DTOAuthor(book.author.name, Nil)))
}
0
votes

This is not an implicit problem, it's a data structure problem; your data structures are cyclic which complicates things. You'd have exactly the same problem with a "normal", non-implicit conversion function.

You can abuse mutability for this, but the "correct" way is probably the "credit card transformation" described in https://www.haskell.org/haskellwiki/Tying_the_Knot (remember that Scala is not lazy by default, so you need to use explicit laziness by passing around functions). But the best solution is probably to see if you can avoid having these cycles in the data structures at all. In an immutable data structure they're a recipe for pain, e.g. think about what will happen if you do

val updatedAuthor = dtoAuthor.copy(name="newName")
updatedAuthor.books.head.author.get.name

The author's name has changed, but the book still thinks its author has the old name!