2012-03-16 8 views
8

Digamos que tengo un rasgo que tiene dos listas. A veces estoy interesado en el uno, a veces en el otro.¿Cuál es la "manera funcional" de evitar pasar contexto de selección de estado por la pila de llamadas?

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

que tienen una cadena de llamadas a funciones, en la parte superior de la que tengo el contexto tengo que elegir entre las listas, pero en la parte inferior de la que usan el rasgo.

En el paradigma imperativo, paso por el contexto a través de las funciones:

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" 

En el paradigma orientado a objetos, puedo usar estado interno para evitar pasar el contexto abajo:

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

¿Cuál es la forma idiomática de lograr un resultado similar en un lenguaje funcional?

Es decir, me gustaría que la salida de lo siguiente sea similar a la salida de la anterior.

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

ACTUALIZACIÓN: esto se consideraría adecuadamente funcional?

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

Nota pequeña: No tiene que escribir 'extends Object with ListHolder'. Simplemente escriba 'extends ListHolder' (las clases pueden ampliar rasgos). – Jesper

Respuesta

5

Bueno, uno siempre puede usar mónadas y comprensión monádica para manejar este tipo de cosas, pero el meollo del asunto es que en lugar de pasar las opciones a la pila, regresas funciones a la pila, hasta que alguien sabe cómo resolver el problema puede manejarlo.

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

Y luego

println(fTop(fs)(1)) 

Una vez que comience a desarrollar patrones para este tipo de cosas, que terminan con las mónadas de todo tipo (cada especie de mónada representa un patrón particular).

+0

Realmente no recomendaría una mónada aquí, pero ese código es tan elegante que tengo que +1 ... – Owen

+0

"el meollo del asunto es que en lugar de pasar opciones por la pila, devuelves funciones a la pila" Creo que este es el concepto central que mi vieja mente OOP pierde una y otra vez. –

+0

@LarryOBrien Tuve que escribir el código dos veces para hacerlo bien. Es como idiomas extranjeros: es mucho más difícil traducir a/desde un idioma extranjero que solo hablarlo. –

1

Su primera versión imprescindible me parece la más funcional.

Normalmente, la mónada Reader y el transformador de mónada State se utilizan para pasar contexto o estado en la pila de llamadas.

Vea Scalaz state monad examples por ejemplo de mónada de estado y vea este correo de scalaz list thread para una pregunta y respuesta similar.

2

Creo que su respuesta en "ACTUALIZAR" es perfectamente buena. Sí, puedes usar la mónada Reader aquí. ¿Pero por qué molestarse, cuando tienes una solución perfectamente buena que no usa mónadas?

La solución monádica de Daniel es hermosa y elegante, pero encontrará que si los métodos fTop y fMiddle comienzan a complicarse, se necesita mucha sintaxis adicional para "sobrepasar" el parámetro que falta.

Creo que el uso de un class para almacenar el contexto es apropiado porque:

  • Eso es lo que las clases son para: compartir un contexto entre las funciones.

  • Scala tiene una sintaxis mucho mejor para las clases que para las mónadas.

Cuestiones relacionadas