View Javadoc
1   package com.guinetik.corefun.examples;
2   
3   import com.guinetik.corefun.Result;
4   
5   /**
6    * Demonstrates usage of the Result monad for functional error handling.
7    *
8    * Result<S, F> represents either a Success containing a value of type S,
9    * or a Failure containing an error of type F. This eliminates null-checking
10   * and exception handling in favor of explicit, composable error handling.
11   */
12  public class ResultExample {
13  
14      public static void main(String[] args) {
15          System.out.println("=== Result<S, F> Examples ===\n");
16  
17          basicUsage();
18          chainingOperations();
19          foldPatternMatching();
20          validationExample();
21          recoveryExample();
22      }
23  
24      /**
25       * Basic creation and inspection of Results.
26       */
27      static void basicUsage() {
28          System.out.println("--- Basic Usage ---");
29  
30          // Creating a success
31          Result<Integer, String> success = Result.success(42);
32          System.out.println("Success: " + success);
33          System.out.println("  isSuccess: " + success.isSuccess());
34          System.out.println("  get(): " + success.get());
35  
36          // Creating a failure
37          Result<Integer, String> failure = Result.failure("Something went wrong");
38          System.out.println("\nFailure: " + failure);
39          System.out.println("  isFailure: " + failure.isFailure());
40          System.out.println("  getError(): " + failure.getError());
41  
42          // Safe value extraction with defaults
43          System.out.println("\nSafe extraction:");
44          System.out.println("  success.getOrElse(-1): " + success.getOrElse(-1));
45          System.out.println("  failure.getOrElse(-1): " + failure.getOrElse(-1));
46  
47          System.out.println();
48      }
49  
50      /**
51       * Chaining operations with map and flatMap.
52       */
53      static void chainingOperations() {
54          System.out.println("--- Chaining Operations ---");
55  
56          // Simulating a user lookup
57          Result<User, String> userResult = findUser(1);
58  
59          // map: transform the success value
60          Result<String, String> nameResult = userResult.map(user -> user.name);
61          System.out.println("User name: " + nameResult);
62  
63          // Chain multiple maps
64          Result<String, String> greeting = userResult
65                  .map(user -> user.name)
66                  .map(name -> "Hello, " + name + "!");
67          System.out.println("Greeting: " + greeting);
68  
69          // flatMap: chain operations that return Results
70          Result<Account, String> accountResult = userResult
71                  .flatMap(user -> findAccount(user.accountId));
72          System.out.println("Account lookup: " + accountResult);
73  
74          // Failure propagates through the chain
75          Result<User, String> notFound = findUser(999);
76          Result<String, String> failedGreeting = notFound
77                  .map(user -> user.name)
78                  .map(name -> "Hello, " + name + "!");
79          System.out.println("Failed lookup chain: " + failedGreeting);
80  
81          System.out.println();
82      }
83  
84      /**
85       * Using fold for pattern matching.
86       */
87      static void foldPatternMatching() {
88          System.out.println("--- Fold (Pattern Matching) ---");
89  
90          Result<User, String> success = findUser(1);
91          Result<User, String> failure = findUser(999);
92  
93          // fold applies one of two functions based on success/failure
94          String successMessage = success.fold(
95                  error -> "Error: " + error,
96                  user -> "Found user: " + user.name
97          );
98          System.out.println("Success fold: " + successMessage);
99  
100         String failureMessage = failure.fold(
101                 error -> "Error: " + error,
102                 user -> "Found user: " + user.name
103         );
104         System.out.println("Failure fold: " + failureMessage);
105 
106         // Practical example: HTTP response
107         int statusCode = success.fold(
108                 error -> 404,
109                 user -> 200
110         );
111         System.out.println("HTTP status: " + statusCode);
112 
113         System.out.println();
114     }
115 
116     /**
117      * Validating Results with predicates.
118      */
119     static void validationExample() {
120         System.out.println("--- Validation ---");
121 
122         Result<Integer, String> age = Result.success(15);
123 
124         // validate: check a condition, fail if not met
125         Result<Integer, String> validated = age.validate(
126                 a -> a >= 18,
127                 a -> "Age " + a + " is below minimum (18)"
128         );
129         System.out.println("Age validation (15): " + validated);
130 
131         Result<Integer, String> adultAge = Result.success(25);
132         Result<Integer, String> validatedAdult = adultAge.validate(
133                 a -> a >= 18,
134                 a -> "Age " + a + " is below minimum (18)"
135         );
136         System.out.println("Age validation (25): " + validatedAdult);
137 
138         // Chain validations
139         Result<String, String> email = Result.success("user@example.com");
140         Result<String, String> validatedEmail = email
141                 .validate(e -> e.contains("@"), e -> "Missing @ symbol")
142                 .validate(e -> e.length() > 5, e -> "Email too short");
143         System.out.println("Email validation: " + validatedEmail);
144 
145         System.out.println();
146     }
147 
148     /**
149      * Recovering from failures.
150      */
151     static void recoveryExample() {
152         System.out.println("--- Recovery ---");
153 
154         Result<User, String> failure = findUser(999);
155         System.out.println("Original: " + failure);
156 
157         // recover: handle failure and potentially return success
158         Result<User, String> recovered = failure.recover(error -> {
159             System.out.println("  Recovering from: " + error);
160             return Result.success(new User(0, "Guest", 0));
161         });
162         System.out.println("Recovered: " + recovered);
163 
164         // Recovery can also fail
165         Result<User, String> stillFailed = failure.recover(error ->
166                 Result.failure("Recovery also failed: " + error)
167         );
168         System.out.println("Failed recovery: " + stillFailed);
169 
170         // Peek for side effects without altering the result
171         failure
172                 .peekSuccess(user -> System.out.println("  Found: " + user.name))
173                 .peekFailure(error -> System.out.println("  Logging error: " + error));
174 
175         System.out.println();
176     }
177 
178     // --- Domain classes for examples ---
179 
180     static class User {
181         final int id;
182         final String name;
183         final int accountId;
184 
185         User(int id, String name, int accountId) {
186             this.id = id;
187             this.name = name;
188             this.accountId = accountId;
189         }
190 
191         @Override
192         public String toString() {
193             return "User{id=" + id + ", name='" + name + "'}";
194         }
195     }
196 
197     static class Account {
198         final int id;
199         final double balance;
200 
201         Account(int id, double balance) {
202             this.id = id;
203             this.balance = balance;
204         }
205 
206         @Override
207         public String toString() {
208             return "Account{id=" + id + ", balance=" + balance + "}";
209         }
210     }
211 
212     // --- Simulated service methods ---
213 
214     static Result<User, String> findUser(int id) {
215         if (id == 1) {
216             return Result.success(new User(1, "Alice", 100));
217         } else if (id == 2) {
218             return Result.success(new User(2, "Bob", 200));
219         }
220         return Result.failure("User not found: " + id);
221     }
222 
223     static Result<Account, String> findAccount(int accountId) {
224         if (accountId == 100) {
225             return Result.success(new Account(100, 1500.00));
226         }
227         return Result.failure("Account not found: " + accountId);
228     }
229 }