11

Say I have a trait that has two lists. Sometimes I'm interested in the one, sometimes in t'other.

trait ListHolder {
  val listOne = List("foo", "bar")
  val listTwo = List("bat", "baz")
}

I have a chain of function calls, at the top of which I have the context I need to choose between the lists, but at the bottom of which I use the trait.

In the imperative paradigm, I pass down the context through the functions:

class Imperative extends Object with ListHolder {
  def activeList(choice : Int) : List[String] = {
    choice match {
      case 1 => listOne
      case 2 => listTwo
    }
  }
}

def iTop(is : List[Imperative], choice : Int) = {
  is.map{iMiddle(_, choice)}
}

def iMiddle(i : Imperative, choice : Int) = {
  iBottom(i, choice)
}

def iBottom(i : Imperative, choice : Int) = {
  i.activeList(choice)
}

val ps = List(new Imperative, new Imperative)
println(iTop(ps, 1)) //Prints "foo, bar" "foo,bar"
println(iTop(ps, 2)) //Prints "bat, baz" "bat, baz"

In the object-oriented paradigm, I can use internal state to avoid passing the context down:

class ObjectOriented extends Imperative {
  var variable = listOne
}

def oTop(ps : List[ObjectOriented], choice : Int) = {
  ps.map{ p => p.variable = p.activeList(choice) }
  oMiddle(ps)
}

def oMiddle(ps : List[ObjectOriented]) = oBottom(ps)

def oBottom(ps : List[ObjectOriented]) = {
  ps.map(_.variable)  //No explicitly-passed-down choice, but hidden state
}

val oops = List(new ObjectOriented, new ObjectOriented)

println(oTop(oops, 1))
println(oTop(oops, 2))

What is the idiomatic way to achieve a similar outcome in a functional language?

That is, I would like the output of the following to be similar to the output from the above.

class Functional extends Object with ListHolder{
  //IDIOMATIC FUNCTIONAL CODE
}

def fTop(fs : List[Functional], choice : Int) = {
    //CODE NEEDED HERE TO CHOOSE LIST
    fMiddle(fs)
}

def fMiddle(fs : List[Functional]) = {
   //NO CHANGES ALLOWED
   fBottom(fs)
}

def fBottom(fs : List[Functional]) = {
  fs.map(_.activeList) //or similarly simple
}

def fs = List(new Functional, new Functional)

println(fTop(fs, 1))
println(fTop(fs, 2))

UPDATE: Would this be considered properly functional?

class Functional extends Imperative with ListHolder{}

class FunctionalWithList(val activeList : List[String]) extends Functional{}

def fTop(fs : List[Functional], band : Int) = {
  fMiddle(fs.map(f => new FunctionalWithList(f.activeList(band))))
}

def fMiddle(fs : List[FunctionalWithList]) = {
  //NO CHANGES ALLOWED
  fBottom(fs)
}

def fBottom(fs : List[FunctionalWithList]) = {
  fs.map(_.activeList)
}

def fs = List(new Functional, new Functional)

println(fTop(fs, 1))
println(fTop(fs, 2))
Larry OBrien
  • 8,484
  • 1
  • 41
  • 75
  • Small note: You don't have to write `extends Object with ListHolder`. Just write `extends ListHolder` (classes can extend traits). – Jesper Mar 16 '12 at 08:00

4 Answers4

8

Well, one can always use monads and monadic comprehensions to get handle this kind of thing, but the heart of the matter is that instead of passing choices down the stack, you return functions up the stack, until someone who knows how to solve the problem can handle it.

def fTop(fs : List[Functional]) = {
    fMiddle(fs)
}

def fMiddle(fs : List[Functional]) = {
   fBottom(fs)
}

def fBottom(fs : List[Functional]) = {
 (choice: Int) => fs map (_ activeList choice)
}

And then

println(fTop(fs)(1))

Once you start to develop patterns for this kind of thing, you end up with monads of all sorts (each kind of monad represents a particular pattern).

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • 2
    I wouldn't actually recommend a monad here but that code is so elegant I have to +1 it... – Owen Mar 17 '12 at 06:27
  • 2
    "the heart of the matter is that instead of passing choices down the stack, you return functions up the stack, " I think this is the core concept that my old OOP mind misses time and again. – Larry OBrien Mar 17 '12 at 17:32
  • 2
    @LarryOBrien I had to write the code twice to get it right. It's like foreign languages: it is much more difficult to translate to/from a foreign language than just to speak it. – Daniel C. Sobral Mar 18 '12 at 05:06
2

Your first imperative version looks the most functional to me.

Typically the Reader monad and the State monad transformer are used to pass context or state down the call stack.

See Scalaz state monad examples for example of state monad and see this scalaz mailing list thread for a similar question and an answer.

Community
  • 1
  • 1
huynhjl
  • 41,520
  • 14
  • 105
  • 158
2

The Reader monad might be helpful. See Tony Morris's blog

jopasserat
  • 5,721
  • 4
  • 31
  • 50
Landei
  • 54,104
  • 13
  • 100
  • 195
2

I think your answer under "UPDATE" is perfectly good. Yes, you can use the Reader monad here. But why bother, when you have a perfectly good solution that doesn't use monads?

Daniel's monadic solution is beautiful and elegant, but you will find, if the methods fTop and fMiddle start to get more complicated, that a lot of extra syntax is needed to "walk over" the missing parameter.

I think using a class to store the context is appropriate because:

  • That's what classes are for: to share a context between functions.

  • Scala has a much nicer syntax for classes than for monads.

Owen
  • 38,836
  • 14
  • 95
  • 125