enris8
enris8

Reputation: 31

Pattern matching in anonymous function in Scala

I'm a beginner Scala developer having problems with a coding exercise (5.12) taken by the book "Functional Programming in Scala" by Paul Chiusano.

I have this function here, called unfold, that takes an initial state and a function for producing a stream with the next states:

def unfold[A,S](z: S)(f: S => Option[(A,S)]): Stream[A] = f(z) match {
  case Some((h,t)) => h #:: unfold(t)(f)
  case _ => Stream.empty
} 

For example, with this function one can create an infinite stream of objects, e.g.

def constant[A](a: A): Stream[A] = unfold(a)(_ => Some(a,a))

Now, I want to create the Fibonacci sequence, then I type:

def fibs: Stream[Int] = unfold((0,1))((a,b) => Some(a,(b,a+b)))

I get these errors:

If I use the case keyword inside the anonymous function passed to unfold, like:

{ case (a,b) => Some(a,(b,a+b))}

everything's fine.

So my question is: what's the difference between the two implementations? Is it something about type inference that I don't understand?

Upvotes: 3

Views: 3449

Answers (2)

radumanolescu
radumanolescu

Reputation: 4161

It seems you have hit one of the rules about how the compiler interprets your code:

A normal function of one arg:

scala> val g = (x: Int) => x + 1
g: Int => Int = <function1>

The data type for a tuple of ints is Tuple2(Int, Int):
scala> (3,4)
res3: (Int, Int) = (3,4)

scala> val a: Tuple2[Int, Int] = (3,4)
a: (Int, Int) = (3,4)

But this does not work:

scala> val f = ((a,b): (Int, Int)) => a+b
<console>:1: error: not a legal formal parameter.
Note: Tuples cannot be directly destructured in method or function parameters.
      Either create a single parameter accepting the Tuple1,
      or consider a pattern matching anonymous function: `{ case (param1, param1) => ... }
val f = ((a,b): (Int, Int)) => a+b
              ^
This works:
scala> val f = (x: Tuple2[Int, Int]) => x._1 + x._2
f: ((Int, Int)) => Int = <function1>

This is what you thought you could do:

val f = ((a, b): (Int, Int)) => Some(a, (b, a + b))

and then (in the context of unfold) the (0,1) gets understood as type (Int, Int), so when you call unfold you can omit the type declaration and just write ((a, b)) => Some(a, (b, a + b)). But it does not work because "Tuples cannot be directly destructured in method or function parameters". The function f above does not compile even on its own, outside of your unfold. Instead, this compiles: val g = (x: Tuple2[Int, Int]) => Some(x._1, (x._2, x._1 + x._2))

Upvotes: 0

Dima
Dima

Reputation: 40510

 { (a,b) => Some(a, (b, a+b)) }

Is a function that takes two arguments - a and b, and returns an option (of a tuple). This is not what you need. You want to define a function that takes a single (tuple) argument. One way to do that would be something like this:

 { tuple => Some(tuple._1, (tuple._2, tuple._1 + tuple._2)) }

Which you could expand into something like this:

 { tuple => 
     val a = tuple._1
     val b = tuple._2
     // Can also do it shorter, with automatic unapply here:
     // val (a,b) = tuple
     // this is already pretty similar to what you have with `case`, right?
     Some(a, (b, a+b))
 }

This looks long, but scala has a special syntax construct that lets you deconstruct complex parameters to anonymous functions "on the fly", using syntax similar to pattern matching (it is actually calling unapply on the input, same as pattern match would do):

 { case(a, b) => Some(a, (b, a+b)) }

This is exactly the same as above.

Upvotes: 5

Related Questions