Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the "functional way" to avoid passing state-choosing context down the call stack?

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))
like image 482
Larry OBrien Avatar asked Mar 16 '12 02:03

Larry OBrien


1 Answers

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).

like image 151
Daniel C. Sobral Avatar answered Oct 27 '22 10:10

Daniel C. Sobral