¿Cómo construyo un sistema que tenga todo lo siguiente ?
- Usar funciones puras con objetos inmutables.
- Solo pase a los datos de una función que la función necesita, no más (es decir, ningún objeto de estado de aplicación grande)
- Evite tener demasiados argumentos para las funciones.
- Evite tener que construir nuevos objetos solo con el propósito de empaquetar y desempaquetar parámetros a funciones, simplemente para evitar que se pasen demasiados parámetros a funciones. Si voy a empacar varios elementos en una función como un solo objeto, quiero que ese objeto sea el propietario de esos datos, no algo construido temporalmente
Me parece que la mónada estatal rompe la regla # 2, aunque no es obvio porque está entretejida a través de la mónada.
Tengo la sensación de que necesito usar lentes de alguna manera, pero se escribe muy poco al respecto para los lenguajes no funcionales.
Antecedentes
Como ejercicio, estoy convirtiendo una de mis aplicaciones existentes de un estilo orientado a objetos a un estilo funcional. Lo primero que intento hacer es aprovechar al máximo el núcleo interno de la aplicación.
Una cosa que he escuchado es que cómo manejar el "Estado" en un lenguaje puramente funcional, y esto es lo que creo que hacen las mónadas estatales, es que lógicamente, ustedes llaman una función pura ", pasando el estado del mundo tal como es ", luego, cuando la función regresa, le devuelve el estado del mundo tal como ha cambiado.
Para ilustrar, la forma en que puede hacer un "hola mundo" de una manera puramente funcional es algo así como, pasa su programa en ese estado de la pantalla y recibe el estado de la pantalla con "hola mundo" impreso en él. Entonces, técnicamente, estás llamando a una función pura, y no hay efectos secundarios.
En base a eso, revisé mi aplicación y: 1. Primero puse todo el estado de mi aplicación en un solo objeto global (GameState) 2. En segundo lugar, hice que GameState fuera inmutable. No puedes cambiarlo. Si necesita un cambio, debe construir uno nuevo. Hice esto agregando un constructor de copia, que opcionalmente toma uno o más campos que cambiaron. 3. Para cada aplicación, paso el GameState como parámetro. Dentro de la función, después de hacer lo que va a hacer, crea un nuevo GameState y lo devuelve.
Cómo tengo un núcleo funcional puro y un bucle en el exterior que alimenta ese GameState en el bucle de flujo de trabajo principal de la aplicación.
Mi pregunta:
Ahora, mi problema es que, GameState tiene unos 15 objetos inmutables diferentes. Muchas de las funciones en el nivel más bajo solo operan en algunos de esos objetos, como mantener la puntuación. Entonces, digamos que tengo una función que calcula el puntaje. Hoy, GameState se pasa a esta función, que modifica el puntaje creando un nuevo GameState con un nuevo puntaje.
Algo sobre eso parece estar mal. La función no necesita la totalidad de GameState. Solo necesita el objeto Score. Así que lo actualicé para aprobar el puntaje y devolver solo el puntaje.
Eso parecía tener sentido, así que fui más allá con otras funciones. Algunas funciones requerirían que pase 2, 3 o 4 parámetros desde GameState, pero a medida que utilicé el patrón en todo el núcleo externo de la aplicación, paso cada vez más el estado de la aplicación. Como, en la parte superior del ciclo de flujo de trabajo, llamaría a un método, que llamaría a un método que llamaría a un método, etc., hasta el punto donde se calcula el puntaje. Eso significa que el puntaje actual se pasa a través de todas esas capas solo porque una función en la parte inferior va a calcular el puntaje.
Entonces ahora tengo funciones con a veces docenas de parámetros. Podría poner esos parámetros en un objeto para reducir el número de parámetros, pero luego me gustaría que esa clase sea la ubicación maestra del estado de la aplicación de estado, en lugar de un objeto que simplemente se construye en el momento de la llamada simplemente para evitar pasar en múltiples parámetros, y luego descomprimirlos.
Así que ahora me pregunto si el problema que tengo es que mis funciones están demasiado anidadas. Este es el resultado de querer tener funciones pequeñas, así que refactorizo cuando una función llega a ser grande y la divido en múltiples funciones más pequeñas. Pero hacer eso produce una jerarquía más profunda, y todo lo que se pasa a las funciones internas debe pasar a la función externa, incluso si la función externa no está operando directamente en esos objetos.
Parecía que simplemente pasar GameState por el camino evitaba este problema. Pero vuelvo al problema original de pasar más información a una función que la función necesita.