Frage Was ist der "funktionale Weg", um das Übergeben von Statusauswahlkontexten in den Call-Stack zu vermeiden?


Sagen wir, ich habe eine Eigenschaft, die zwei Listen hat. Manchmal interessiert mich der eine, manchmal der andere.

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

Ich habe eine Kette von Funktionsaufrufen, an deren Spitze ich den Kontext habe, den ich zwischen den Listen wählen muss, aber an dessen Ende ich das Merkmal verwende.

Im Imperativ-Paradigma gebe ich den Kontext durch die Funktionen weiter:

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"

Im objektorientierten Paradigma kann ich den internen Status verwenden, um den Kontext nicht zu überschreiten:

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

Was ist der idiomatische Weg, um in einer funktionalen Sprache ein ähnliches Ergebnis zu erzielen?

Das heißt, ich möchte, dass die Ausgabe des Folgenden der Ausgabe von oben ähnlich ist.

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

AKTUALISIEREN: Würde dies als richtig funktional betrachtet?

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

9
2018-03-16 02:18


Ursprung


Antworten:


Nun, man kann immer Monaden und monadische Übersichten verwenden, um mit solchen Dingen fertig zu werden, aber das Herz der Sache ist, dass man, anstatt die Auswahl auf dem Stapel weiterzuleiten, Funktionen im Stapel zurückgibt, bis jemand das Problem lösen kann kann damit umgehen.

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

Und dann

println(fTop(fs)(1))

Sobald Sie beginnen, Muster für diese Art von Dingen zu entwickeln, enden Sie mit Monaden aller Art (jede Art von Monade repräsentiert ein bestimmtes Muster).


6
2018-03-17 02:56



Ich denke deine Antwort unter "UPDATE" ist absolut gut. Ja, Sie können die Reader-Monade hier verwenden. Aber warum, wenn Sie eine perfekte Lösung haben, die keine Monaden verwendet?

Daniels monadische Lösung ist schön und elegant, aber Sie werden finden, wenn die Methoden fTop und fMiddle beginnen, komplizierter zu werden, dass eine Menge zusätzlicher Syntax benötigt wird, um den fehlenden Parameter "zu durchlaufen".

Ich denke mit einem class den Kontext zu speichern ist angemessen, weil:

  • Das ist, was Klassen sind: um einen Kontext zwischen Funktionen zu teilen.

  • Scala hat eine viel schönere Syntax für Klassen als für Monaden.


2
2018-03-17 06:22



Ihre erste imperative Version sieht für mich am funktionalsten aus.

In der Regel werden die Reader-Monade und der State-Monade-Transformer verwendet, um den Kontext weiterzuleiten oder den Call-Stack herunterzufahren.

Sehen Beispiele für Scalaz-State-Monaden zum Beispiel von State Monad und sehen Sie dieses Scalaz Mailing Thread auflisten für eine ähnliche Frage und eine Antwort.


1
2018-03-16 06:34



Die Reader-Monade könnte hilfreich sein. Sehen Tony Morris Blog


1
2018-03-16 08:08