@@ -16,6 +16,7 @@ The example list is:
1616* <<optionBind, Option bind>>
1717* <<optionFilter, Option filter>>
1818* <<optionMap, Option map>>
19+ * <<ioWalkthrough, IO Walkthrough>>
1920
2021== Array Exists [[arrayExists]]
2122
@@ -258,3 +259,82 @@ public final class Option_map {
258259}
259260----
260261
262+
263+ == IO Walkthrough [[ioWalkthrough]]
264+ https://github.com/functionaljava/functionaljava/blob/master/demo/src/main/java/fj/demo/IOWalkthrough.java[Github Source]
265+
266+ Demonstrates how to work with the IO type.
267+
268+ [source,java]
269+ ----
270+ // IO is just a container to defer a computation (lazy), with the intention
271+ // to encapsulate computations that either consume and/or produce side-effects
272+ // the computation is not (yet) executed on creation hence it can be treated
273+ // like a value
274+
275+ final IO<Unit> askName = () -> {
276+ System.out.println("Hi, what's your name?");
277+ return Unit.unit();
278+ };
279+
280+ // fj.data.IOFunctions contains a lot of convenience functions regarding IO, the
281+ // above example could be rewritten with IOFunctions.stdoutPrintln
282+ // we now create an IO value to prompt for the name if executed
283+
284+ IO<Unit> promptName = IOFunctions.stdoutPrint("Name: ");
285+
286+ // we can compose these two values with fj.data.IOFunctions.append, since they
287+ // both are not interested in any runtime value
288+
289+ IO<Unit> askAndPromptName = IOFunctions.append(askName, promptName);
290+
291+ // now we create an IO value to read a line from stdin
292+
293+ final IO<String> readName = () -> new BufferedReader(new InputStreamReader(System.in)).readLine();
294+
295+ // this is the same as IOFunctions.stdinReadLine()
296+
297+ // now we create a function which takes a string, upper cases it and creates
298+ // an IO value that would print the upper cased string if executed
299+
300+ final F<String, IO<Unit>> upperCaseAndPrint = F1Functions.<String, IO<Unit>, String>o(IOFunctions::stdoutPrintln).f(String::toUpperCase);
301+
302+ // we now want to compose reading the name with printing it, for that we need to
303+ // have access to the runtime value that is returned when the
304+ // IO value for read is executed, hence we use fj.data.IOFunctions.bind instead
305+ // of fj.data.IOFunctions.append
306+
307+ final IO<Unit> readAndPrintUpperCasedName = IOFunctions.bind(readName, upperCaseAndPrint);
308+
309+ // so append is really just a specialised form of bind, ignoring the runtime
310+ // value of the IO execution that was composed before us
311+
312+ final IO<Unit> program = IOFunctions.bind(askAndPromptName, ignored -> readAndPrintUpperCasedName);
313+
314+ // this is the same as writing IOFunctions.append(askAndPromptName, readAndPrintUpperCasedName)
315+
316+ // we have recorded the entire program, but have not run anything yet
317+ // now we get to the small dirty part at the end of our program where we actually
318+ // execute it
319+
320+ // we can either choose to just call program.run(), which allows the execution to escape
321+ // or we use safe to receive an fj.data.Either with the potential exception on the
322+ // left side
323+
324+ toSafeValidation(program).run().on((IOException e) -> { e.printStackTrace(); return Unit.unit(); });
325+
326+ // doing function composition like this can be quite cumbersome, since you will end
327+ // up nesting parenthesis unless you flatten it out by
328+ // assigning the functions to variables like above, but you can use the fj.F1W
329+ // syntax wrapper for composing single-argument functions and fj.data.IOW
330+ // for composing IO values instead, the entire program can be written like so:
331+
332+ IOW.lift(stdoutPrintln("What's your name again?"))
333+ .append(stdoutPrint("Name: "))
334+ .append(stdinReadLine())
335+ .bind(F1W.lift((String s) -> s.toUpperCase())
336+ .andThen(IOFunctions::stdoutPrintln))
337+ .safe().run().on((IOException e) -> { e.printStackTrace(); return Unit.unit(); });
338+ ----
339+
340+
0 commit comments