0

I'm completely new to Lift. My goal is to make a small application which will let me enter data about scientific articles (studies) into a database.

I haven't gotten around to making a database yet, still playing with getting an entry form to work. So I decided that I will hold a few studies in memory, in a list of a companion object to the Study class. I also created an accumulator variable to dispense unique IDs, as long as I don't have a DBMS managing the IDs.

As a smoke test, I visited the page showing the list of studies (seeded with two studies in code), then visited the form page, entered a new study, and navigated again to the list of studies. I was surprised to see that my new study has the ID of 1 instead of 3, so at some point, my accumulator variable must have been reset. But the ListBuffer collecting studies was not reset, because it showed all three studies. Adding more studies results in the counter incrementing by 1 every time.

The literature I have found on Lift (the two free books on Liftweb, as well as Gilberto Garcia's Lift Application Development Cookbook) are incomplete and are more like a collection of mini-tutorials, they don't explain how Lift works.

So what is the actual lifecycle of my Study object, and why did one mutable variable get reset after re-opening the page but not another?

package org.rumtscho.litsuche
package study

import scala.collection.mutable.ListBuffer

class Study private[study](
    val id: Int,
    val handmadeAuthorRef: String,  
    val humanReadableDescription: String ) 
{
}

object Study {
  val seedStudies = List(
          new Study(dispenseNextFreeId, "Brooks1975", "Tells us that throwing more programmers at a project makes it run late, not early"), 
          new Study(dispenseNextFreeId, "Dijkstra1968", "Recognizes that we need structured code")
      )

   private var studiesList = seedStudies.to[ListBuffer]
   private var currentId = 0


   private def dispenseNextFreeId: Int = {
    currentId = currentId + 1
    return currentId
  }

   def allStudies = studiesList.toList

   def addStudy(reference: String, description: String): Unit = {
     studiesList += new Study(dispenseNextFreeId, reference, description)
  }
}

Here is the representation of the three studies:

The output of the above

update My understanding of what is happening (could be wrong, of course):

  1. I open the page showing the list of studies. This calls allStudies. studiesList is initialized to contain Brooks1975 and Dijkstra1968. During the construction of the studies, currentId is initialized to 0 and then incremented to 2.

  2. I open the entry form and add a third study. addStudy retrieves allStudies from memory, without initializing it again. It initializes currentId to 0. I end up with a third study with the ID 1.

  3. I display all studies, then return to the entry form page. This time, addStudy retrieves both allStudies and currentId from memory. I get a fourth study with the ID of 2.

The comments have pointed out that this is probably Scala-specific and not related to Lift. But still, I don't understand why currentId is initialized two times (in steps 1 and 2), and not either once (as soon as the object itself is created) or every time it is read. I would have expected the first behavior, but even reinitializing every time seems more logical than randomly reinitializing one time only.

rumtscho
  • 2,474
  • 5
  • 28
  • 42
  • 1
    Isn't the problem that your currentId has a value of 0 before you call addStudy, even though you've already seeded 2 studies into your studiesList? You should probably initialize currentId to studiesList.length, not to 0? – Ethan Jewett Feb 12 '15 at 14:59
  • @EthanJewett this is exactly what I'm asking. Why is currentId re-initialized when I request the page again, but studiesList isn't reinitialized to seedStudies.to[ListBuffer]? When can I expect a reinitialization and when no reinitialization? – rumtscho Feb 12 '15 at 15:03
  • 1
    What @EthanJewett is saying is that the error has nothing to do with Lift, it is Scala's initialization order. You define a list, then the next step in the initialization is to reset the currentId back to 0. If you move the line `priveate var currentId = 0` to the top of your companion object (above the `seedStudies` declaration) it should work as expected. – jcern Feb 12 '15 at 15:24
  • @jcern OK, I admit that my idea of Scala initialization order is foggy. I added a description of what appears to happen from my point of view, maybe you can find where I'm mistaken and explain it in an answer? – rumtscho Feb 12 '15 at 15:44
  • What happens if you do what @jcern suggests and move your currentId assignment to the top of the Study object? – Ethan Jewett Feb 12 '15 at 16:00

1 Answers1

3

Go into the scala REPL, enter paste mode (:paste) command, and put in the following:

def increment {
  currentId = currentId + 1
}

increment
increment

var currentId = 0

then try

var currentId = 0

def increment {
  currentId = currentId + 1
}

increment
increment

In the first example, currentId ends up with value 0. In the second, it ends up with value 2. Why does this happen? I'm not an expert on Scala declaration, but it seems that this is the same problem you are running in to.

It seems that the solution is as @jcern suggests. In general, I'd say put all your declarations at the top of your classes or objects, and always declare before using a variable, and not the other way around.

Ethan Jewett
  • 6,002
  • 16
  • 25
  • These questions might give a bit more insight into the initialization order: http://stackoverflow.com/questions/12184997/scala-and-forward-references/12185918#12185918 and http://stackoverflow.com/questions/7762838/forward-references-why-does-this-code-compile – jcern Feb 12 '15 at 17:37