In the previous period we discussed the IO processing process: process[i,o]. We say process is like a TV signal box with both input and output ends. The process can be connected to the input of another process with the output of one process to form a sequence of complete IO processes with multiple data processing functions. However, the two inputs of the synthetic IO process need to be connected to a data source, while the other end may receive a data receiving device such as a file, a display, etc. In this article, we briefly introduce the IO data source and IO data receiver sink.
We first use a separate data type to represent the source of the data in a simple demonstration, which has no relation to the process type:
Import processlib._object Sourcesink {trait source[o] { //The following helper function is to treat Source as a list of O class def |>[o2] (P:process[o,o2]): Source[o2] //Glue a Process p to it input O def filter (f:o = Boolean): source[o] = this |> proce Ss.filter (f)//to P input O def map[o2] (f:o = O2): Source[o2] = this |> process.lift (f) def take (n:int): source[ O] = this |> process.take (n) //intercept the first n O def takewhile (f:o = = Boolean): source[o] = this |> process.takewhi Le (f) def drop (n:int): source[o] = this |> process.drop (n)//Skip Top n O def dropwhile (f:o = Boolean): source[ O] = this |> process.dropwhile (f)}
as you can see from the above trait: source works by pasting the input of a process into the output of source. We can use this |> to glue a bunch of process to the output of the source, such as: SRC.PROC1.PROC2.PROC3. But first we have to define PROC1,PROC2,PROC3 as the source component function, because source is a completely independent type.
Let's take a look at a source special case:
Case Class Resourcer[r,i,o] ( A read-only resource for//source Acquire:io[r], //Resource Usage Portal resource handle release:r = IO [Unit],//Finish using resource cleanup function step:r = Io[option[i]],//resource content read function Trans:process[i,o]// output mode) extends Source[o] {def |& Gt [O2] (P:process[o,o2]): source[o2] = //Implement abstract function resourcer (Acquire,release,step,trans |> p)// Each input produces a resourcer. Its trans and P pipe docking}
This is a read-only data source. We see that all of the actions are embedded in the IO type, which can delay the generation of side effects to some source interpreter. Here we can just use the simplest IO to illustrate:
Trait Io[a] {self = def run:a def map[b] (f:a = B): io[b] = new Io[b] {def run = f (self.run)} def flatmap[b] (f:a = io[b]): io[b] = new Io[b] {def run = f (self.run). Run}}object IO { def unit[a] (A: = = A): io[a] = new Io[a] {def run = A} def Flatmap[a,b] (Fa:io[a]) (f:a = io[b]) = FA flatMap F def Apply[a] (A : = = a): io[a] = unit (a)//syntax for IO {.}}
This IO type has been practiced in previous discussions.
Now let's take a look at the resourcer example of a file read:
Object Source {Import java.io._def lines (filename:string): source[string] = //read String from file filename resourcer ( //Create an instance of source io {io). Source.fromfile (FileName)}, //Resource (Src:io. Source) = = IO {src.close}, //Cleanup (Src:io. Source) = = IO { //Read lazy val iterator = Src.getlines if (iterator.hasnext) Some (iterator.next) Else None Read back to None }, process.passunchanged)//process[i,i], read what enter what}
Now we can write a program like this:
Source.lines ("Input.txt"). Count.exists{_ >= 40000} //> res0:ch15. Sourcesink.source[boolean] = Resourcer (ch15. sourcesink$io$ $anon $ //| [Email protected],<function1>,<function1>,await (<function1>)]
Oh, remember to put count and exists in source trait:
def exists (f:o = Boolean): Source[boolean] = this |> process.exists (f) def count:source[int] = this |> Process. Count
The above expression can be described as just the description of the IO process. The actual side effects are generated in the interpreter:
def Collect:io[indexedseq[o]] = { //Read data source return Io[indexedseq[o]], use Io.run to actually operate Def Tryor[a] (A: = a) (cleanup:io[ Unit]): a = //Operation expression A, an exception immediately clears the field try A catch {case e:exception = Cleanup.run; throw e} @annotation. tailrec
//This is a tail recursion algorithm, according to Trans State def go (Acc:indexedseq[o], Cleanup:io[unit], step:io[option[i], Trans:process[i,o]): Indexedseq[o] = Trans Match {case Halt () = Cleanup.run; ACC //Stop State, clear field case Emit (out,next) = Go (Tryor (out +: ACC) (cleanup), cleanup, step, next)//accumulate ACC case Await (iproc) = Tryor (Step.run) (cleanup) match { C9/>case None = Cleanup.run; ACC //Finish cleaning up the scene case si = Go (Acc,cleanup,step,iproc (SI)) //read into the element as process input to change the process state } } Acquire map {res = go (Indexedseq (), Release (RES), step (res), trans)}//Start reading}
Note: The on-site cleanup will prevent resources from leaking, regardless of the read completion or the Midway failure exit. It can be inferred that the interpreter is still safe.
As with source, we use a separate type of sink to represent the data receiving side for a brief explanation:
Trait Sink[i] {def <|[ I2] (P:process[i2,i]): Sink[i2]//p output to Sink input def filter (f:i = Boolean): sink[i] = this <| Process.filter (f) //Receive I def from P map[i2] (f:i2 = i): sink[i2] = this <| Process.lift (f)//convert received I2 into I def take (n:int): sink[i] = this <| Process.take (n) //Receive First n I def takewhile (f:i = Boolean) from P: sink[i] = this <| Process.takewhile (f) def drop (N:int): sink[i] = this <| Process.drop (n)//filter out first n I def dropwhile (f:i = Boolean): sink[i] = this <| Process.dropwhile (f)}
This is similar to source trait. Note that the process connection is reversed: the P points to the sink.
Similarly, a write-only resource instance is as follows:
Case Class Resourcew[r,i,i2] ( //write-only resource acquire:io[r], //Resource Usage portal, resource handle release:r = io[ Unit], //cleanup function rcvf:r = (I2 = Io[unit]),//Receive mode TRANS:PROCESS[I,I2] //process ) extends sink[ I] { def <|[ I2] (P:process[i2,i]): sink[i2] = resourcew (acquire,release,rcvf,p |> Trans)//Manufacturing a Resourcew instance, from p to trans }
This is similar to Resourcer. Or is connected to the process in the opposite direction: from P to trans.
The following is a sink component that writes to a file:
Object Sink {Import java.io._ def file (filename:string, Append:boolean = False): sink[string] =//result is sink[string]. You must use interpreter to calculate resourcew ( //is a Resourcew instance IO {new FileWriter (filename,append)},//Create FileWriter (w:filewriter) = io {w.close}, //Release FileWriter (w:filewriter) = (s:string) + = io {w.write (s)},< c6/>//Write process.passunchanged //write Data not processed )}
During the learning process, it is found that the Source,sink type independent of the process type makes integration of the expression types of the IO algorithm difficult. This also limits the functionality of the component. We cannot realize the simple and elegant expression of functional programming. In the following discussion, we will focus on analyzing the process of having a data source function, hoping to improve the way we express ourselves.
Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.
Functional Programming (36)-Functional stream Io:io Data source-io Source & Sink