views:

1159

answers:

3

Is it possible to use something similar to where-clauses in Scala? Maybe there is some trick I didn't think of?

Edit:

Thanks for all your answers, they are very much appreciated. To sum up: Local vars, vals and defs can be used to achieve almost the same thing. For lazy evaluation, one can use lazy val (with implicit caching) or function definitions. Ensuring functional purity is left to the programmer.

Now just one question remains: Is there a way of putting the value or function definitions after the expressions they are used in? Sometimes that seems a lot clearer. This is possible with fields/methods of a class or object, but it doesn't seem to work within methods.

One other thing that wasn't mentioned in the answers so far. where-clauses also limit the scope of the expressions defined in them. I haven't found a way to achieve that in Scala either.

+15  A: 

In Hakell, where clauses hold local definitions to a function. Scala does not have explicit where clauses, but the same functionality can be achieved by having local var, val and def.

Local `var` and `val`

In Scala:

def foo(x: Int, y: Int): Int = {
  val a = x + y 
  var b = x * y
  a - b
}

In Haskell:

foo :: Integer -> Integer -> Integer 
foo x y = a - b
        where 
          a = x + y
          b = x * y

Local `def`

In Scala

def foo(x: Int, y: Int): Int = {
  def bar(x: Int) = x * x
  y + bar(x)
}

In Haskell

foo :: Integer -> Integer -> Integer 
foo x y = y + bar x
         where 
           bar x = x * x

Please correct me if I have made any syntax errors in the Haskell example, as I currently have no Haskell compiler installed on this computer :).

More complicated examples can be achieved in similar ways (for example using pattern matching, which both languages have support for). Local functions have exactly the same syntax as any other function, just that their scope is the block they are in.

EDIT: Also see Daniel's answer for such an example and some elaboration on the subject.

EDIT 2: Added a discussion about lazy vars and vals.

Lazy `var` and `val`

Edward Kmett's answer rightly pointed out that Haskell's where clause has laziness and purity. You can do something very similar in Scala using lazy variables. These are only instantiated when needed. Consider the following example:

def foo(x: Int, y: Int) = { 
  print("--- Line 1: ");
  lazy val lazy1: Int = { print("-- lazy1 evaluated "); x^2}
  println();

  print("--- Line 2: ");
  lazy val lazy2: Int = { print("-- lazy2 evaluated "); y^2}
  println();

  print("--- Line 3: ");
  lazy val lazy3: Int = { print("-- lazy3 evaluated ")
    while(true) {} // infinite loop! 
    x^2 + y^2 }
  println();

  print("--- Line 4 (if clause): ");
  if (x < y) lazy1 + lazy2
  else lazy2 + lazy1
}

Here lazy1, lazy2 and lazy3 are all lazy variables. lazy3 is never instantiated (therefore this code never enters in an infinite loop) and the order of instantiation of lazy1 and lazy2 depends on the arguments of the function. For example when you call foo(1,2) you will get lazy1 instantiated before lazy2 and when you call foo(2,1) you will get the reverse. Try the code out in the scala interpreter and see the printout! (I won't put it here as this answer is already quite long).

You could achieve similar results if instead of lazy variables you used no-argument functions. In the example above, you could replace every lazy val with a def and achieve similar results. The difference is that lazy variables are cached (aka only evaluated once) but a def is evaluated every time it is invoked.

EDIT 3: Added a discussion about scoping, see question.

Scope of local definitions

Local definitions have the scope of the block they are declared in, as expected (well, most of the time, in rare situations they can escape the block, like when using mid-stream variable binding in for loops) . Therefore local var, val and def can be used to limit the scope of an expression. Take the following example:

object Obj {
  def bar = "outer scope"

  def innerFun() {
    def bar = "inner scope"
    println(bar) // prints inner scope
  }

  def outerFun() {
    println(bar) // prints outer scope
  }

  def smthDifferent() {
    println(bar) // prints inner scope ! :)
    def bar = "inner scope"
    println(bar) // prints inner scope
  }

  def doesNotCompile() {
    { 
      def fun = "fun" // local to this block
      42 // blocks must not end with a definition... 
    }
    println(fun)
  }

}

Both innerFun() and outerFun() behave as expected. The definition of bar in innerFun() hides the bar defined in the enclosing scope. Also, the function fun is local to its enclosing block, so it cannot be used otherwise. The method doesNotCompile() ... does not compile. It is interesting to note that both println() calls from the smthDifferent() method print inner scope. Therefore, yes, you can put definitions after they are used inside methods! I wouldn't recommend though, as I think it is bad practice (at least in my opinion). In class files, you can arrange method definitions as you like, but I would keep all the defs inside a function before they are used. And vals and vars ... well ... I find it awkward to put them after they are used.

Also note that each block must end with an expression not with a definition, therefore you cannot have all the definitions at an end of a block. I would probably put all the definitions at the start of a block, and then write all my logic producing a result at the end of that block. It does seem more natural that way, rather then:

{
// some logic

// some defs

// some other logic, returning the result
}

As I previously said, you cannot end a block with just // some defs. This is where Scala slightly differs from Haskell :).

EDIT 4: Elaborated on defining stuff after using them, prompted by Kim's comment.

Defining 'stuff' after using them

This is a tricky thing to implement in a language that has side effects. In a pure-no-side-effect world, the order would not be important (methods would not depend on any side-effects). But, as Scala allows side effects, the place where you define a function does matter. Also, when you define a val or var, the right hand side must be evaluated in place in order to instantiate that val. Consider the following example:

// does not compile :)
def foo(x: Int) = {

  // println *has* to execute now, but
  // cannot call f(10) as the closure 
  // that you call has not been created yet!
  // it's similar to calling a variable that is null
  println(f(10))

  var aVar = 1

  // the closure has to be created here, 
  // as it cannot capture aVar otherwise
  def f(i: Int) = i + aVar

  aVar = aVar + 1

  f(10)
}

The example you give does work though if the vals are lazy or they are defs.

def foo(): Int = {
  println(1)
  lazy val a = { println("a"); b }
  println(2)
  lazy val b = { println("b"); 1 }
  println(3)
  a + a
}

This example also nicely shows caching at work (try changing the lazy val to def and see what happens :)

I still thing in a world with side effects it's better to stick with having definitions before you use them. It's easier to read source code that way.

-- Flaviu Cipcigan

Flaviu Cipcigan
Deleted my example. Go with this.
Shaun
That's really strange. Your example works, but try this one: def notGood():Int = { val a = b val b = 1 a } The error I get is "forward reference extends over definition of value a". Doesn't work with "def b = 1" either.
Kim
+5  A: 

Similar, yes. I won't go into details, as Flaviu has already, but I'll give an example from the Wikipedia.

Haskell:

calc :: String -> [Float]
calc = foldl f [] . words
  where 
    f (x:y:zs) "+" = (y + x):zs
    f (x:y:zs) "-" = (y - x):zs
    f (x:y:zs) "*" = (y * x):zs
    f (x:y:zs) "/" = (y / x):zs
    f xs y = read y : xs

Those definitions are just definitions local to calc. So, in Scala, we would do this:

def calc(s: String): List[Float] = {
  def f(s: List[Float], op: String) = (s, op) match {
    case (x :: y :: zs, "+") => (y + x) :: zs
    case (x :: y :: zs, "-") => (y - x) :: zs
    case (x :: y :: zs, "*") => (y * x) :: zs
    case (x :: y :: zs, "/") => (y / x) :: zs
    case (xs, y) => read(y) :: xs
  }

  s.words.foldLeft(List[Float]())(f)
}

Since Scala has no equivalent of read, you might define it as below, for the purpose of running this particular example:

def read(s: String) = s.toFloat

Scala doesn't have words either, much to my chagrin, though it's easily defined:

implicit toWords(s: String) = new AnyRef { def words = s.split("\\s") }

Now, Haskell's definition is more compact for various reasons:

  • It has a more powerful type inference, so nothing beyond the type of calc itself needed to be declared. Scala can't do that because of a conscious design decision to be object-oriented with the class model.

  • It has an implicit pattern matching definition, whereas, in Scala, you have to declare the function and then declare the pattern match.

  • Its handling of currying is just plain superior to Scala's, as far as conciseness goes. This is a result of various decisions, about the class model and the operator notation, in which handling currying was considered not as important.

  • Haskell has special treatment for lists, making it possible to have a more concise syntax for them. In Scala, lists are treated like any other class, the effort being made, instead, to ensure that any class can be as compact as List can be in Scala.

So, there are various reasons for why Scala does what it does, though I'd love an implicit pattern matching definition. :-)

Daniel
+2  A: 

You can use var and val to provide local variables, but that is different than Haskell's where clause in two fairly important aspects: laziness and purity.

Haskell's where clause is useful because laziness and purity alows the compiler to only instantiate the variables in the where clause that are actually used.

This means you can write a big long local definition, and drop a where clause below it and there is no consideration needed for the order of effects (because of purity) and no consideration needed for if each individual code branch needs all of the definitions in the where clause, because laziness allows unused terms in the where clause to exist just as thunks, which purity allows the compiler to choose to elide from the resulting code when they aren't used.

Scala, unfortunately, has neither of these properties, and so cannot provide a full equivalent to Haskell's where clause.

You need to manually factor out the vars and vals that you use and put them in before the statements that use them, much like ML let statements.

Edward Kmett
Actually you can do something very similar using `lazy` `val`s and `var`s. See my edited answer for details :). Haskell's syntax is a bit more terse as everything is lazy by default in Haskell, while in scala you have to flag something to be lazy.
Flaviu Cipcigan