Каков «функциональный» способ избежать передачи контекста выбора состояния в стек вызовов? - PullRequest
10 голосов
/ 16 марта 2012

Скажем, у меня есть черта, которая имеет два списка. Иногда меня интересует один, иногда другой.

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

У меня есть цепочка вызовов функций, в верхней части которой есть контекст, который мне нужно выбирать между списками, но в нижней части которого я использую черту.

В императивной парадигме я передаю контекст через функции:

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"

В объектно-ориентированной парадигме я могу использовать внутреннее состояние, чтобы избежать передачи контекста:

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

Каков идиоматический способ достижения аналогичного результата на функциональном языке?

То есть я хотел бы, чтобы вывод следующего был похож на вывод из приведенного выше.

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: Будет ли это считаться должным образом функциональным?

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

Ответы [ 4 ]

7 голосов
/ 17 марта 2012

Что ж, всегда можно использовать монады и монадические понимания, чтобы справиться с подобными вещами, но суть в том, что вместо передачи выбора вниз по стеку вы возвращаете функции вверх по стеку, пока кто-то, кто знает, какрешить проблему можно с ней справиться.

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

А потом

println(fTop(fs)(1))

Как только вы начнете разрабатывать шаблоны для такого рода вещей, вы получите всевозможные монады (каждыймонады представляет определенный образец).

2 голосов
/ 17 марта 2012

Я думаю, что ваш ответ в разделе "ОБНОВЛЕНИЕ" совершенно хорош. Да, вы можете использовать монаду Reader здесь. Но зачем, если у вас есть отличное решение, которое не использует монады?

Монадическое решение Даниэля красиво и элегантно, но вы обнаружите, что если методы fTop и fMiddle начнут усложняться, то потребуется много дополнительного синтаксиса, чтобы "обойти" отсутствующий параметр.

Я думаю, использование class для сохранения контекста целесообразно, потому что:

  • Для этого предназначены классы: для совместного использования контекста между функциями.

  • В Scala синтаксис для классов гораздо лучше, чем для монад.

1 голос
/ 16 марта 2012

Монада Reader может быть полезна. См. блог Тони Морриса

1 голос
/ 16 марта 2012

Ваша первая императивная версия выглядит наиболее функциональной для меня.

Обычно монада Reader и преобразователь монад State используются для передачи контекста или состояния вниз по стеку вызовов.

См. Примеры монады состояний Scalaz для примера монады состояний и см. Эту рассылку скалаза В списке аналогичный вопрос и ответ.

...