Skip to content

Commit ca19d28

Browse files
committed
Merge pull request #254 from fakraemer/master
Provide an IO walkthrough example demonstrating basic composition
2 parents cbeec83 + 47b58b8 commit ca19d28

3 files changed

Lines changed: 94 additions & 0 deletions

File tree

core/src/main/java/fj/data/IOFunctions.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,13 @@ public static IO<Unit> stdoutPrintln(final String s) {
495495
};
496496
}
497497

498+
public static IO<Unit> stdoutPrint(final String s) {
499+
return () -> {
500+
System.out.print(s);
501+
return Unit.unit();
502+
};
503+
}
504+
498505
public static IO<LazyString> getContents() {
499506
Stream<IO<Integer>> s = Stream.<IO<Integer>>repeat(() -> (int) stdinBufferedReader.read());
500507
return IOFunctions.map(sequenceWhile(s, i -> i != -1), s2 -> LazyString.fromStream(s2.<Character>map(i -> {

core/src/main/java/fj/data/IOW.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ public A run() throws IOException {
2525
return io.run();
2626
}
2727

28+
public SafeIO<Validation<IOException, A>> safe() {
29+
return IOFunctions.toSafeValidation(io);
30+
}
31+
2832
public <B> IOW<B> map(F<A, B> f) { return lift(IOFunctions.map(io, f)); }
2933

3034
public <B> IOW<B> bind(F<A, IO<B>> f) { return lift(IOFunctions.bind(io, f)); }
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package fj.demo;
2+
3+
import fj.F;
4+
import fj.F1Functions;
5+
import fj.F1W;
6+
import fj.Unit;
7+
import fj.data.IO;
8+
import fj.data.IOFunctions;
9+
import fj.data.IOW;
10+
11+
import java.io.BufferedReader;
12+
import java.io.IOException;
13+
import java.io.InputStreamReader;
14+
15+
import static fj.data.IOFunctions.stdinReadLine;
16+
import static fj.data.IOFunctions.stdoutPrint;
17+
import static fj.data.IOFunctions.stdoutPrintln;
18+
import static fj.data.IOFunctions.toSafeValidation;
19+
20+
/**
21+
* Demonstrates how to use <code>IO</code> and basic function composition.
22+
*/
23+
public class IOWalkthrough {
24+
public static void main(String[] args) {
25+
26+
// IO is just a container to defer a computation (lazy), with the intention to encapsulate computations that either
27+
// consume and/or produce side-effects
28+
// the computation is not (yet) executed on creation hence it can be treated like a value
29+
30+
final IO<Unit> askName = () -> {
31+
System.out.println("Hi, what's your name?");
32+
return Unit.unit();
33+
};
34+
35+
// fj.data.IOFunctions contains a lot of convenience functions regarding IO, the above example could be rewritten with IOFunctions.stdoutPrintln
36+
// we now create an IO value to prompt for the name if executed
37+
38+
IO<Unit> promptName = IOFunctions.stdoutPrint("Name: ");
39+
40+
// we can compose these two values with fj.data.IOFunctions.append, since they both are not interested in any runtime value
41+
42+
IO<Unit> askAndPromptName = IOFunctions.append(askName, promptName);
43+
44+
// now we create an IO value to read a line from stdin
45+
46+
final IO<String> readName = () -> new BufferedReader(new InputStreamReader(System.in)).readLine();
47+
48+
// this is the same as IOFunctions.stdinReadLine()
49+
50+
// now we create a function which takes a string, upper cases it and creates an IO value that would print the upper cased string if executed
51+
52+
final F<String, IO<Unit>> upperCaseAndPrint = F1Functions.<String, IO<Unit>, String>o(IOFunctions::stdoutPrintln).f(s -> s.toUpperCase());
53+
54+
// we now want to compose reading the name with printing it, for that we need to have access to the runtime value that is returned when the
55+
// IO value for read is executed, hence we use fj.data.IOFunctions.bind instead of fj.data.IOFunctions.append
56+
57+
final IO<Unit> readAndPrintUpperCasedName = IOFunctions.bind(readName, upperCaseAndPrint);
58+
59+
// so append is really just a specialised form of bind, ignoring the runtime value of the IO execution that was composed before us
60+
61+
final IO<Unit> program = IOFunctions.bind(askAndPromptName, ignored -> readAndPrintUpperCasedName);
62+
63+
// this is the same as writing IOFunctions.append(askAndPromptName, readAndPrintUpperCasedName)
64+
65+
// we have recorded the entire program, but have not run anything yet
66+
// now we get to the small dirty part at the end of our program where we actually execute it
67+
// we can either choose to just call program.run(), which allows the execution to escape
68+
// or we use safe to receive an fj.data.Either with the potential exception on the left side
69+
70+
toSafeValidation(program).run().on((IOException e) -> { e.printStackTrace(); return Unit.unit(); });
71+
72+
// doing function composition like this can be quite cumbersome, since you will end up nesting parenthesis unless you flatten it out by
73+
// assigning the functions to variables like above, but you can use the fj.F1W syntax wrapper for composing single-argument functions and fj.data.IOW
74+
// for composing IO values instead, the entire program can be written like so:
75+
76+
IOW.lift(stdoutPrintln("What's your name again?"))
77+
.append(stdoutPrint("Name: "))
78+
.append(stdinReadLine())
79+
.bind(F1W.lift((String s) -> s.toUpperCase()).andThen(IOFunctions::stdoutPrintln))
80+
.safe().run().on((IOException e) -> { e.printStackTrace(); return Unit.unit(); });
81+
}
82+
}
83+

0 commit comments

Comments
 (0)