This document presents an approach to applying functional programming principles to the model-view-controller (MVC) architectural pattern. It discusses modeling the components of MVC using functional abstractions like monads, algebraic data types, and libraries like Cats and Monix. The document outlines how the model, view, and controller could be defined in a purely functional way for a tic-tac-toe application. It also discusses some limitations of the approach and areas for further improvement.
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
MVC meets Monad
1. MVC meets Monads
A functional-based design for a classic architectural pattern
Gianluca Aguzzi
Programming And Development Paradigms (PPS)
Deparment of Computer Science and Engineering (DISI)
ALMA MATER STUDIORUM–Università di Bologna, Cesena, Italy
September 1, 2021
2. It is all about composition [Mil19]
Humans tend to divide complex problems in multiple pieces
Then, solve each piece
Finally compose all solutions to solve the initial problem
Functional programming is a good way to compose different solutions :)
G.Aguzzi 1/25
3. Lecture goals
Show an end-to-end functional application
Leverage some well-consolidated functional libraries
Understand limitations (if any) and the improvements
G.Aguzzi 2/25
5. OOP Design
Everything is an object
Clear interface
Incapsulated state
Side effect as methods call
G.Aguzzi 4/25
6. Model
Core logic
Main data to describe model entities (e.g. players?)
Main methods to describe the game logic
1 /* two players (X, O) */
2 enum Player {
3 X, O, None; //Or? Optional?
4 }
5 /* a board 3x3 */
6 interface TicTacToe {
7 Player get(int X, int Y);
8 TicTacToe (or void??) update(int x, int y, Player p);
9 boolean isOver;
10 Player getTurn;
11 }
G.Aguzzi 5/25
7. View
Representation and IO boundary
Describes what type of data it can consume (for rendering porpuse)
Catches how to handle user’s inputs
Ideally, the View could be totally decoupled from the Model
1 //a la' view model
2 interface ViewBoard {
3 ListString getRow(int row);
4 ListListString getAllBoard();
5 }
6 interface View extends ClickCellSource {
7 void render(ViewBoard board);
8 void winner(String player);
9 }
G.Aguzzi 6/25
8. View
Desing pattern
the Input handler is usually solved exploiting the Observer [Gam95] pattern (also
called Listener).
1 public interface ClickCellSource {
2 void attach(Observer observer);
3 interface Observer {
4 void notify(int X, int Y);
5 }
6 }
G.Aguzzi 7/25
9. Controller
Goals
Coordinates the interation between View and Model worlds
Handles concurrency
Adapts data
1 public interface Game extends ClickCellSource.Observer {
2 void start();
3 }
4
5 public class TicTacToeGame implements Game {
6 private final TicTacToe ticTacToe;
7 private final TicTacToeView ticTacToeView;
8
9 public static TicTacToeGame playWith(
10 final TicTacToe ticTacToe,
11 final TicTacToeView ticTacToeView) {...}
12 ....
G.Aguzzi 8/25
10. Putting all together
1 public static void main(String[] args) {
2 final TicTacToeView view = SwingView.createAndShow(800, 600);
3 final TicTacToe model = TicTacToeFactory.startX();
4 final Game game = TicTacToeGame.playWith(model, view);
5 game.start();
6 }
Clean enough, isn’t it?
What do you think?
Try to rethink the design phase using functional abstractions
(Monads? Functions? Algebraic Data Type?)
Let’s go to the functional side :)
G.Aguzzi 9/25
11. Libraries
Cats W [Wel17]
provides abstractions for functional programming in the Scala
programming language.
Monix W
high-performance Scala / Scala.js library for composing asynchronous,
event-based programs.
G.Aguzzi 10/25
12. Task
Definition
Task represents a specification for a possibly lazy or asynchronous
computation, which when executed will produce a data as a result, along
with possible side-effects.
1 trait Task[+A] {
2 final def flatMap[B](f: A = Task[B]): Task[B] = ...
3 final def map[B](f : A = B): Task[B] = ...
4 //some interesting extesions
5 def memoize: Task[A] = ...
6 }
7 object Task {
8 def pure[A](a : A) : Task[A]
9 def defer[A](a : Task[A]) : Task[A]
10 }
What does it refer you to?
G.Aguzzi 11/25
13. A little taste
1 def someComputation(data : Long) : Task[Long] = Task.pure(data * 1000)
2
3 def log(value : String) : Task[Unit] = Task { println(value) }
4
5 val main = for {
6 data - someComputation(4)
7 _ - log(scomputations ends with value $data)
8 } yield (data)
9
10 main.runToFuture
Fiddle W
G.Aguzzi 12/25
14. Observable
Definition
A data type for modelling and processing asynchronous and reactive
streaming of events with non-blocking back-pressure.
Functional Reactive Programming [Has20] [Bla16]
The program is expressed as a reaction to its inputs, or as a flow of data.
We can use Observable to implement it
Out of this talk, if you are interested, Conal Elliot W proposes a lot of materials
about this topic.
G.Aguzzi 13/25
15. An example with Scala.js :)
1 val textInput = input(placeholder := write text here).render
2 //unsafe boundary
3 val subject = PublishSubject[String]()
4 textInput.oninput = _ = subject.onNext(textInput.value)
5 val result = p.render
6 Fiddle.print(div(textInput), result)
7 //safe part
8 val inputStream = subject.share //API of the model
9 val computation = for {
10 text - inputStream
11 _ - Observable.pure(result.innerText = text)
12 } yield()
13
14 computation.foreachL(a = a).runToFuture
Fiddle W
G.Aguzzi 14/25
16. MVC + Functional, Intuition
Model is described using Algebraic Data Type and operations (in a separated
module)
View is modelled using monadic abstraction to wrap side effects
Controller coordinates Model and View without introducing other side effects and
using a monadic manipulation (e.g. via flatmap)
Applied in our case
Observable describes the user’s inputs flow
Other computations (e.g. lazy and with side effects) are wrapped with Task
Let’s start again :) (using Scala)
G.Aguzzi 15/25
17. Model
Domain Data
Immutable data structures, operations can be described in a separated module
We can enhance ADT with type classes
1 sealed trait TicTacToe {
2 def board: Map[Position, Player]
3 }
4 object TicTacToe {
5 val defaultSize: Int = 3
6 type Position = (Int, Int) //type aliases improve readability
7 case class InProgress(turn: Player, board: Map[Position, Player]) extends
TicTacToe
8 case class End(winner: Player, board: Map[Position, Player]) extends
TicTacToe
9 sealed trait Player {
10 def other: Player = ...
11 }
12 case object X extends Player
13 case object O extends Player
14 }
G.Aguzzi 16/25
18. Model
Operations
Behaviour is described in terms of a function that evolves into a TicTacToe
instance
Other ideas? State monad?
1 object TicTacToeOps {
2 def advanceWith(ticTacToe: TicTacToe, hit: Position): TicTacToe = {
3 val updateGame = for {
4 _ - rightPosition(ticTacToe, hit)
5 } yield updateBoard(ticTacToe, hit)
6 updateGame.getOrElse(ticTacToe)
7 }
8 def rightPosition(
9 ticTacToe: TicTacToe,
10 position: Position): Option[TicTacToe] = ....
11 def updateBoard(
12 ticTacToe: TicTacToe,
13 position: Position): TicTacToe = ...
14 ...
15 }
G.Aguzzi 17/25
19. View
Input-Output management
IO is something outside of the model
Boundary wraps something outside the control of the domain logic (e.g. GUI,
Console, Socket)
Boundary, conceptually, can be defined as independent from a specific model data
(and input data)
There are any problems with this solution?
1 trait Boundary[-Model, +Input] { //why generic?
2 def init(): Task[Unit] = Task.pure {}
3
4 def input: Observable[Input]
5
6 def consume(model: Model): Task[Unit]
7 }
G.Aguzzi 18/25
20. Controller
Application strucutres
The application flow could be either reactive or proactive
When it is reactive, the Controller updates the Model only when a new input occurs
When it is proactive, the Controller updates the Model independently from the
input flow
Intuition
Controller is a module (object) that exposes two function, i.e. reactive and
proactive
Both functions, return a Task (i.e. wrap a computation)
Can we build it independently from Model and View?
We only need a function accepting an Input (I) and evolving the Model (M)
returning a Task[M] (why?).
An initial Model
A boundary used for our application
G.Aguzzi 19/25
24. Recap
We can clearly isolate side effects using Monads (Task, Observable)
Then we can arbitrarily compose them to build an imperative-like behavior
Model is side-effect free
The program built is referential transparent
Concurrency is handled through Monads themself (via shift, Fiber, ...)
Final remark
“The IO monad does not make a
function pure. It just makes it obvi-
ous that it’s impure”
Martin Odersky
[Ale17]
G.Aguzzi 23/25
25. Discussion
My effort consists in merging MVC concepts with monads/functional programming
Have you found some impurities? Or some lacks?
Do you prefer the OOP solution or the Functional solution? And why?
Possible lacks (you can try to solve them as exercise :D)
Error handling?
Concurrency fine-control? Parallelism?
Pure monadic view (my interface exposes side effects, isn’t it?)
Cleaner Controller (monad transform?)
Other application domain
Other GUI supports
G.Aguzzi 24/25