0
votes

Kind of stuck trying to port an old php application to scala, what I want: I have nodes (model) that look like this:

id | parent_id

now for a given node

case class Node(id: Option[Long], parentID: Option[Long])

private def filterQuery(id: Long): Query[Nodes, Node, Seq] = nodes.filter(_.id === id)

override def find(id: Long): Future[Option[Node]] =
{
    try db.run(filterQuery(id).result.headOption)
    finally db.close
}

I want to get all the parents recursively, like:

private def getAllParents(node: Node): List[Node] = node.parentID match
{
  case l:Long => List(node) + getAllParents(find(l))
  case None => List(node)
}

of course this doesn't work.

  1. the syntax is not right. I need to add the node to the list AND call it for it's parent
  2. the recursive call wouldn't work because it expects Node but gets Future[Option[Node]], not sure how to resolve this either :(

I remember doing Haskell a few years back and my functional style is a (huge) bit rusty.

edit: I see that I would probably need to have the list as parameter as well... argh, I'm so stuck -.-

1

1 Answers

0
votes

First, you don't need to include the list as a parameter, but you will need to use a nested function to do that.

The main issue here is that find returns a Future[Option[Node]], so that means you need to work entirely "inside" futures. A Future is a monad, which basically means two things:

  1. You can never directly access value inside the Future
  2. You interact with the contained value by passing functions to it through map and flatMap, which give you a new Future in return.

I would suggest you read some articles about how future-based programming works, here's a decent one I found, but as a simplified example, suppose I have :

val x: Future[Int] = Future { 5 }

If I want to add 1 to the contained value, I cannot access it directly, but rather I can call map:

val y: Future[Int] = x.map{i => i + 1}

Likewise, if I have some function like :

def addOne(i: Int): Future[Int] = //...

I can use it with my future like :

val y: Future[Int] = x.flatMap{i => addOne(i)}

Getting back to your situation, this means that getAllParents must return a Future[List[Node]] and we will do the recursion using flatMap, where you create a new future every time you call find and chain them together as you traverse through them.

private def getAllParents(node: Node): Future[List[Node]] = {
  def loop(node: Node, build: List[Node]): Future[List[Node]] = {
    node.parentId match {
      case None => Future.successful(node :: build)
      case Some(parentId) => find(parentId).flatMap{parentOption => parentOption match {
        case Some(parentNode) => loop(parentNode, node :: build)
        case None => throw new Exception(s"couldn't find parent for $node")
      }}
    }
  }
  loop(node, Nil)
}

So you can see that loop is sort of a recursive function, but the recursion is done inside a Future. If the node has no parent, then we basically stick the end result directly in a new Future and call it a day. If the node does have a parent, we then call find, which gives us a Future[Option[Node]].

Now to do the recursion, we call flatMap on this returned Future, and we have to give it a function of type Option[Node] => Future[List[Node]]. Now we have a function that has access to both node and its parent, and we can add node to the list and call loop again on its parent.