In the previous section we introduced the trampoline. It is primarily designed to address stack overflow (stackoverflow) errors. Trampoline type is a data structure, and its design idea is to replace the stack with heap: corresponding to the traditional recursive algorithm running on the stack on the state of the program, the recursive algorithm with trampoline is stored in the trampoline data structure. The data structure is on the heap, so it is possible to change the stack with the heap. This method of solving the problem by replacing the function call with the data structure also provides a broader development space for functional programming.
We know that any operation involving IO will face a stack overflow problem. This is because IO is typically targeted at the amount of data that cannot be predicted and repeats the loop operation. So the IO algorithm design will also adopt the same data structure as trampoline. Or we should use trampoline data structures and algorithms to design the IO component library. So thinking then we have to trampoline the depth of the abstract. Free Monad is the extension of trampline. Before introducing the free Monad, let's start with a realistic example:
Suppose we are going to write a function of a bank transfer, we might first derive the function signature:
12 context:authorization with Logger with ErrorHandler with Storage): Unit
First we take the parameter injection (parameter injection) method here: Inject the context object into the transfer function input parameter. This context object includes authentication, operation tracking, error handling, data access, and so on. This is a traditional OOP programming model. For a functional programmer: This context object allows you to perform a series of operations. This includes IO operations, which means you can do something with side effects (side effect). Then this function is not able to implement the function combination (functions composition). The transfer function is not a function that a functional programmer should use.
Perhaps we should try to design this function from a functional programming perspective: Design in a non-metamorphic (immutability) manner advocated by functional programming, that is, returning something to the function caller.
For example, we can return a program that describes the operation to the function caller: a sequence of commands (instruction):
1 def transfer (amount:double, From:account, To:account, User:user): List[instruction]
This version is definitely a functional version. It is not enough, however, if the instruction type includes interactive operations. Let's look at the data types of simple interactions:
1 // Interaction Data Types 2 // question, waiting to return a string type answer 3 Case class extends 4// Inform, no results returned 5caseclass Extends Interact[unit]
If we return a sequence of commands according to the idea above:
1 Val PRG = List (Ask ("What ' s your first name?") ),2 Ask ("What's your last name?") ),3Tell ("Hello??????"))
This program PRG is flawed: it is not possible to interact. It seems that if you can store the Ask command in a temporary variable, you can achieve your goal. So if we rewrite this PRG as follows:
1 for {2 x <-Ask ("What ' s your first name?") )3 y <-Ask ("What's your last name?") )4 _ <-Tell (S "Hello $y $x!" )5 } yield ()
Isn't that the Monad style? The original solution is to turn the interaction type trait Interact[a] into monad on the line.
However, to turn interact into a monad, you must implement the unit and Flatmap two functions, check interact trait, obviously this is not possible.
Then we should put the following efforts on how to turn into monad this aspect. Since we mentioned in this proposition that free monad is the Monad line. So with free monad can you turn interact into Monad?
Let's take a look at this free MONAD type structure:
12caseclassextends free[f,a]3case classextends free[f,a]
This free result is just too similar to trampoline. If free is a monad, then we should have to implement its Flatmap function:
1 trait Free[f[_],a] {2def unit (A:A) =Return (a)3def flatmap[b] (f:a = free[f,b]): free[f,b] = ThisMatch {4 CaseReturn (a) =F (a)5 //Remember trampoline FlatMap (FlatMap (B,g), f) = = FlatMap (b, (x:any) = FlatMap (g (x), F))6 CaseBind (Fa,g) = Bind (FA, (x:any) =g (x) FlatMap f)7 //The following is the function combination method. With the same function8 //Case Bind (fa,g) = bind (FA, G andthen (_ FlatMap F))9 }Tendef map[b] (f:a = B): free[f,b] = flatMap (A =Return (f (a))) One A } - Case classReturn[f[_],a] (A:A)extendsFree[f,a] - Case classBind[f[_],i,a] (a:f[i], f:i = Free[f,a])extendsFree[f,a]
we can use the following lift function to upgrade interact[a] to Free[f,a] :
1 Implicit def Lift[f[_],a] (Fa:f[a]): free[f,a] = Bind (FA, (a:a) = Return (A))2 / /> Lift: [f[_], A] (Fa:f[a]) Ch13.ex6.free[f,a]
With lift, we can. The PRG is upgraded to Monad:
1Trait Interact[a]//Interaction Data Types2 //question, waiting to return a string type answer3 Case classAsk (prompt:string)extendsInteract[string]4 //informed that no results were returned5 Case classTell (Msg:string)extendsInteract[unit]6 7Implicit def Lift[f[_],a] (Fa:f[a]): free[f,a] = Bind (FA, (a:a) = =Return (a))8 //> Lift: [f[_], A] (Fa:f[a]) Ch13.ex6.free[f,a]9 for {TenX <-Ask ("What ' s your first name?")) OneY <-Ask ("What's your last name?")) A_ <-Tell (S "Hello $y $x!") -} yield ()//> Res0:ch13.ex6.free[ch13.ex6.interact,unit] = Bind (Ask ( What's your first Nam - //| e?),<function1>)
This is because the type conversion in the implicit scope allows interact to be upgraded to free, and it is a monad, so we can use for-comprehension.
Well, how does this program describe what to do when it's done? The free Monad includes two functions that are unrelated to each other and can be considered separately. This is called the separation of concerns (separation of concern). The two functions of free monad are monad, and interpreter (interpreter). We use Monad to describe the program algorithm, and use the Interpreter interpreter program to form a running code for a specific operating environment.
Free Monad's interpreter realizes the separation of algorithms and operations. The Interpreter program operation is implemented by a transform function. This function translates f[_] Such an algorithm into g[_] as a monad running code for a running environment. This conversion is a natural conversion (Natural transformation), and its function is as follows:
1 trait ~>[f[_],g[_]] {2 def Apply[a] (Fa:f[a]): G[a]3 }
Obviously, this constructor (constructor) interprets the incoming f[a] into g[a].
Now interpreter runs a one-to-one g[_] transformation of an expression in an algorithm f[_]. Just as the elements in the list structure are handled in the same way, we can use the folding algorithm to implement the transformation of expressions in the F[_] structure:
1 This Match {2 Case Return (a) = monad[g].unit (a)3case Bind (b,g) = Monad[g].flatmap (f (b)) (A = > g (a). Foldmap (f))4 }
We see that foldmap the expression in free Monad f[_] with the Monad G state. Note that the bind state is loop-recursive.
Now we can try the simplest interpretation: f,id conversion:
1ype Id[a] =A2Implicit val Idmonad:monad[id] =NewMonad[id] {3def Unit[a] (a:a) =a4def Flatmap[a,b] (fa:a) (f:a = b): b =f (FA)5}//> Idmonad:ch13.ex6.monad[ch13.ex6.id] = [email protected]6 //| 530C127Object Consoleextends(Interact ~>Id) {8def Apply[a] (I:interact[a]): A =I match {9 CaseAsk (Prompt) = {Ten println (Prompt) One ReadLine A } - CaseTell (msg) =println (msg) - } the}
Operation above that paragraph interact program: Because the ID does not produce any effect, the interact to the ID conversion is the direct operation interact expression:
1Val PRG = for {2X <-Ask ("What ' s your first name?"))3Y <-Ask ("What's your last name?"))4_ <-Tell (S "Hello $y $x!")5} yield ()//> Prg:ch13.ex6.free[ch13.ex6.interact,unit] = Bind (Ask (what ' s your first n6 //| ame?),<function1>)7 8Prg.foldmap (Console)
Or we can try a little more complicated interpretation:
1Type Tester[a] = map[string, String] = =(list[string], A)2Implicit val Testermonad =NewMonad[tester] {3def Unit[a] (A:A) = (_ =(List (), a))4def Flatmap[a,b] (Ta:tester[a]) (f:a = Tester[b]): tester[b] = {5m = {6Val (l1,a) =ta (m)7Val (l2,b) =F (a) (m)8(L1 + +l2,b)9 }Ten } One}//> Testermonad:ch13.ex6.monad[ch13.ex6.tester]{def unit[a] (a:a): Map[strin A //| g,string] = (list[nothing], A)} = [email protected] - //| 8 -Object TestConsoleextends(Interact ~>Tester) { thedef Apply[a] (I:interact[a]): tester[a] =I match { - CaseAsk (Prompt) = m + =(List (), M (prompt)) - CaseTell (msg) = _ + =(List (msg), ()) - } +}
Above we put the interactive information in the run interact into the map[string,string] structure. Here a conversion of interact to a function map=> (List,a) is performed.
1Val PRG = for {2X <-Ask ("What ' s your first name?"))3Y <-Ask ("What's your last name?"))4_ <-Tell (S "Hello $y $x!")5} yield ()//> Prg:ch13.ex6.free[ch13.ex6.interact,unit] = Bind (Ask (what ' s your first n6 //| ame?),<function1>)7 8Prg.foldmap (TestConsole)
In the previous section we discussed the trampoline. The main purpose is to solve the unavoidable stack overflow problem in functional algorithm. If we use free monad to solve the IO problem, the stack overflow problem is unavoidable. We should consider using the trampoline type in free monad. This allows us to safely use the free monad to generate any type of monad and solve the stack overflow problem with heap replacement stack in the operation.
Functional Programming (30)-Functional Io:free Monad-monad production line