If you thought Monads are a mystery, then this demonstration would show you how to evolve your code towards a Monad without knowing about it. This demo will neither go into any Category Theory nor begin with monadic laws. Instead, we will start with typical code that you see in your daily life as a developer, attempt to DRY (Don't Repeat Yourself) it up and eventually use Monad to remove duplication and verbosity. You'll also see how Monads make your code more declarative and succinct by sequencing the steps in your domain logic.
Also, we know in Java8 Checked Exceptions + λ == Pain! To be more precise, we will evolve a Try<t> (exception handling monad) which is missing in Java8, similar to one found in Scala.
2. class User {
private final String id;
private final String name;
User(final String id, final String name) {
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
}
Setting the context…
3. class Authenticator {
public User login(String id, String pwd) throws Exception {
// throw new Exception("password mismatch");
return new User(id, "Dhaval");
}
public User gmailLogin(String id, String pwd) throws Exception {
// throw new IOException("some problem");
return new User(id, "Dhaval");
}
public void twoFactor(User user, long pwd) {
// throw new RuntimeException("twoFactor Incorrect key");
}
}
class Dispatcher {
static void redirect(URL target) {
System.out.println("Going to => " + target);
}
}
4. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
TheHappyPath
5. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
Do this first
TheHappyPath
6. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
Do this first
Do this Second
TheHappyPath
7. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
Do this first
Do this Second
Do this Third
TheHappyPath
8. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
Modifies user that got
declared outside the
block scope of try.
Modifies target that
got declared outside the
block scope
of the try-catch
9. Pattern of Repeated
Behaviour
• Consume some data, return a result and pass it
to the next function for its consumption.
user = authenticator.login(userid, pwd);
• This is a very common pattern, where we mutate
a temp variable, only to be passed to the next
function in chain.
authenticator.twoFactor(user, twoFactorPwd);
10. Can we chain the two
try blocks and pass
data around implicitly?
11. But before chaining, we need to start with what is near
to us.
We have these functions wrapped in try blocks.
So thats our starting-point (near to us).
Ability to chain the functions wrapped in try blocks is
our goal and is far.
“From Near to Far”
12. • From Near to Far.
• That which is obvious and close to us now is the starting
point, a.k.a the inflection point.
• That which is far, and can be abstract is our goal.
• Take baby steps.
General Principles in
Refactoring
13. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
TheHappyPath
14. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
1
Essential code
wrapped in try
boiler-plate
TheHappyPath
15. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
1
Essential code
wrapped in try
boiler-plate
2
Essential Code
wrapped in try
boiler-plate
TheHappyPath
16. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
1
Essential code
wrapped in try
boiler-plate
2
Essential Code
wrapped in try
boiler-plate
3
Eventually
TheHappyPath
17. DRYing Boiler-plate
• Create a method that takes in different code blocks for try.
• Use lambda to wrap each code block and defer evaluation.
• As checked exception is thrown in the first try block, a regular
Java8 Supplier<T> will not work
• Instead pass the code block using exceptional Supplier<T>
- lets call it SupplierThrowsException<T>
• Exceptional SAM wraps the code that throws exception in a
lambda, catches it and returns T.
• We can do the same for the next try computation executed on
success.
18. Tiding up…
@FunctionalInterface
interface SupplierThrowsException<T, E extends Exception> {
T get() throws E;
}
class Test {
static <T, E extends Exception>
T tryWith(SupplierThrowsException<T, E> s) {
try {
return s.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception { … }
}
19. Tidingup…
class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = Test.tryWith(() -> authenticator.login(userid, pwd));
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = Test.tryWith(() -> authenticator.gmailLogin(userid, pwd));
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
target = Test.tryWith(() -> {
authenticator.twoFactor(user, twoFactorPwd);
return dashboard;
});
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
20. Tidingup…
class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = Test.tryWith(() -> authenticator.login(userid, pwd));
} catch (Exception es) {
try {
user = Test.tryWith(() -> authenticator.gmailLogin(userid, pwd));
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
target = Test.tryWith(() -> {
authenticator.twoFactor(user, twoFactorPwd);
return dashboard;
});
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
Compilation Error!!
“local variables referenced
from a lambda expression
must be final or effectively
final”
authenticator.twoFactor(user, twoFactorPwd);
21. Lets Pause and Reflect…
• Can we contain the user in a CUSTOM
CONTAINER, so that we can pass it to the next
computation in sequence, without needing to
reach outside the block scope of try-catch?
• Yes, that will solve the problem of declaring a user
outside.
• The container will hold the user within it (implicit data) and
pass it to the next computation in sequence.
22. Create a
Try<T> Container
Checked or
Unchecked
Exception.
Success
Failure
Value
Input(s)
Computation
It holds either the success value or the exception
resulting from the executing computation.
23. class Test {
static <T, E extends Exception>
T tryWith(SupplierThrowsException<T, E> s) {
try {
return s.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception { … }
}
IntroducingTry<T>
24. class Test {
static <T, E extends Exception>
T tryWith(SupplierThrowsException<T, E> s) {
try {
return s.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception { … }
}
IntroducingTry<T> class Try<T> {
static <T, E extends Exception>
T with(SupplierThrowsException<T, E> s) {
try {
return s.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
25. Introduce Polymorphic
variants of Try<T>
• Success<T> - holds successful result of
computation.
• Failure<T> - holds exception from
computation.
26. VariantsofTry<T> class Try<T> {
static <T, E extends Exception>
Try<T> with(SupplierThrowsException<T, E> s) {
try {
return new Success<>(s.get());
return s.get();
} catch (Exception e) {
return new Failure<>(e);
throw new RuntimeException(e);
}
}
}
class Success<T> extends Try<T> {
private final T value;
Success(T value) { this.value = value; }
}
class Failure<T> extends Try<T> {
private final Exception e;
Failure(Exception e) { this.e = e; }
}
27. ApplyingTry<T>
class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
Try<User> user;
Authenticator authenticator = new Authenticator();
try {
user = Try.with(() -> authenticator.login(userid, pwd));
} catch (Exception es) {
try {
user = Try.with(() -> authenticator.gmailLogin(userid, pwd));
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
Try<URL> target;
try {
long twoFactorPwd = 167840;
target = Try.with(() -> {
authenticator.twoFactor(user, twoFactorPwd);
return dashboard;
});
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
28. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
Try<User> user;
Authenticator authenticator = new Authenticator();
try {
user = Try.with(() -> authenticator.login(userid, pwd));
} catch (Exception es) {
try {
user = Try.with(() -> authenticator.gmailLogin(userid, pwd));
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
Try<URL> target;
try {
long twoFactorPwd = 167840;
target = Try.with(() -> {
authenticator.twoFactor(user, twoFactorPwd);
return dashboard;
});
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
Compilation Error
“Incompatible types: Try<User>
cannot be converted to
User”
authenticator.twoFactor(user, twoFactorPwd);
ApplyingTry<T>
29. Time to address the
question:
How can we chain
the next try block?
30. Introducing chain
• Allows us to chain the two try blocks.
• Facilitate passing result/exception of earlier
computation on left (which is this) to the one
on right.
// (LEFT) returns Try<User>
Try.with(() -> authenticator.login(userid, pwd));
// (RIGHT) returns Try<URL>
Try.with(() -> {
authenticator.twoFactor(user, twoFactorPwd);
return dashboard;
});
31. Signature of chain
• Consumes a function that goes from: User -> Try<URL>
• Generically, a function from T -> Try<U>
• Produces Try<URL>
• Generically Try<U>
• Try<U> chain(Function<T, Try<U>> f)
Try.with(() -> authenticator.login(userid, pwd))
.chain(user -> Try.with(() -> {
authenticator.twoFactor(user, twoFactorPwd);
return dashboard;
}); // returns Try<URL>
32. Addingchain abstract class Try<T> {
static <T, E extends Exception>
Try<T> with(SupplierThrowsException<T, E> s) { … }
public abstract <U> Try<U> chain(Function<T, Try<U>> f);
}
class Success<T> extends Try<T> {
private final T value;
Success(T value) { this.value = value; }
public <U> Try<U> chain(Function<T, Try<U>> f) {
try { return f.apply(value); }
catch (Exception e) { return new Failure<U>(e); }
}
}
class Failure<T> extends Try<T> {
private final Exception e;
Failure(Exception e) { this.e = e; }
public <U> Try<U> chain(Function<T, Try<U>> f) {
return (Try<U>) this;
}
}
33. class Test {
public static void main(String[] args) throws Exception {
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
Authenticator authenticator = new Authenticator();
Try<URL> target = Try.with(() -> authenticator.login(userid, pwd))
.chain(user -> Try.with(() -> {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
return new URL("http://dashboard");
}));
Dispatcher.redirect(target);
}
}
Applying chain
34. class Test {
public static void main(String[] args) throws Exception {
URL loginPage = new URL("http://login");
String userid = "softwareartisan";
String pwd = "1234";
Authenticator authenticator = new Authenticator();
Try<URL> target = Try.with(() -> authenticator.login(userid, pwd))
.chain(user -> Try.with(() -> {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
return new URL("http://dashboard");
}));
Dispatcher.redirect(target);
}
}
Applying chain
Compilation Error
“Incompatible types: Try<URL>
cannot be converted to
URL”
Dispatcher.redirect(target);
35. Addingget()
abstract class Try<T> {
static <T, E extends Exception>
Try<T> with(SupplierThrowsException<T, E> s) {
try { return new Success<>(s.get()); }
catch (Exception e) { return new Failure<>(e); }
}
public abstract <U> Try<U> chain(Function<T, Try<U>> f);
public abstract T get();
}
class Success<T> extends Try<T> {
private final T value;
Success(T value) { this.value = value; }
public <U> Try<U> chain(Function<T, Try<U>> f) { … }
public T get() { return value; }
}
class Failure<T> extends Try<T> {
private final Exception e;
Failure(Exception e) { this.e = e; }
public <U> Try<U> chain(Function<T, Try<U>> f) { … }
public T get() {
throw new RuntimeException("Failure cannot return", e);
}
}
36. class Test {
public static void main(String[] args) throws Exception {
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
Authenticator authenticator = new Authenticator();
Try<URL> target = Try.with(() -> authenticator.login(userid, pwd))
.chain(user -> Try.with(() -> {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
return new URL("http://dashboard");
}));
Dispatcher.redirect(target.get());
}
}
Applying get
37. Visualizing the pipes
so far…
Try.with (system login)
chain (two factor authentication)
URL
Unhandled
Errors
Dashboard
38. class Test {
public static void main(String[] args) throws Exception {
URL loginPage = new URL("http://login");
String userid = "softwareartisan";
String pwd = "1234";
Authenticator authenticator = new Authenticator();
Try<URL> target = Try.with(() -> authenticator.login(userid, pwd))
.chain(user -> Try.with(() -> {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
return new URL("http://dashboard");
}));
Dispatcher.redirect(target.get());
}
}
// We have yet to tackle:
// 1. Recovery using gmailLogin
// 2. What about failure?
// 3. What about redirection to login URL upon failure (because
// of either login, or gmailLogin or twoFactor).
PendingItems
39. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
FailureRecovery
40. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
Try this first
FailureRecovery
41. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
Try this first
FailureRecovery
42. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
Try this first
FailureRecovery
In case of failure,
recover with
43. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
Try this first
Do this Second
FailureRecovery
In case of failure,
recover with
44. Introducing recoverWith
• Inside a Try<T> if the computation throws an
exception, we need to give it a chance to recover
from failure.
• Create a method recoverWith on Try<T> that
allows us to chain the code block inside catch.
• What will recoverWith’s signature look like?
• Consumes a function that goes from Throwable ->
Try<User>, generically, a function from Throwable ->
Try<U>
• Produces Try<User>, generically Try<U>
45. AddingrecoverWith
abstract class Try<T> {
static <T, E extends Exception>
Try<T> with(SupplierThrowsException<T, E> s) { … }
public abstract <U> Try<U> chain(Function<T, Try<U>> f);
public abstract T get();
public abstract <U> Try<U> recoverWith(Function<Exception, Try<U>> f);
}
class Success<T> extends Try<T> {
private final T value;
Success(T value) { this.value = value; }
public <U> Try<U> chain(Function<T, Try<U>> f) { … }
public T get() { return value; }
public <U> Try<U> recoverWith(Function<Exception, Try<U>> f) {
return (Try<U>) this;
}
}
class Failure<T> extends Try<T> {
private final Exception e;
Failure(Exception e) { this.e = e; }
public <U> Try<U> chain(Function<T, Try<U>> f) { … }
public T get() { … }
public <U> Try<U> recoverWith(Function<Exception, Try<U>> f) {
try { return f.apply(e); }
catch (Exception e) { return new Failure<U>(e); }
}
}
46. Applying recoverWith
class Test {
public static void main(String[] args) throws Exception {
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
Authenticator authenticator = new Authenticator();
Try<URL> target = Try.with(() -> authenticator.login(userid, pwd))
.recoverWith(e -> Try.with(() ->
authenticator.gmailLogin(userid, pwd)))
.chain(user -> Try.with(() -> {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
return new URL("http://dashboard");
}))
Dispatcher.redirect(target.get());
}
}
48. PendingItemsclass Test {
public static void main(String[] args) throws Exception {
URL loginPage = new URL("http://login");
String userid = "softwareartisan";
String pwd = "1234";
Authenticator authenticator = new Authenticator();
Try<URL> target = Try.with(() -> authenticator.login(userid, pwd))
.recoverWith(e -> Try.with(() ->
authenticator.gmailLogin(userid, pwd)))
.chain(user -> Try.with(() -> {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
return new URL("http://dashboard");
}))
Dispatcher.redirect(target.get());
}
}
// We have yet to tackle:
// 1. Recovery using gmailLogin
// 2. What about failure?
// 3. What about the login URL redirection upon failure due to
// either login, or gmailLogin or twoFactor.
49. class Test {
public static void main(String[] args) throws Exception {
URL dashboard = new URL("http://dashboard");
URL loginPage = new URL("http://login");
String userid = "softwareartisan";
String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
IfRecoveryfails…
50. class Test {
public static void main(String[] args) throws Exception {
URL dashboard = new URL("http://dashboard");
URL loginPage = new URL("http://login");
String userid = "softwareartisan";
String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
IfRecoveryfails…
51. class Test {
public static void main(String[] args) throws Exception {
URL dashboard = new URL("http://dashboard");
URL loginPage = new URL("http://login");
String userid = "softwareartisan";
String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
IfRecoveryfails…
If recovery fails,
then do this and
go back
52. class Test {
public static void main(String[] args) throws Exception {
URL dashboard = new URL("http://dashboard");
URL loginPage = new URL("http://login");
String userid = "softwareartisan";
String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
IfRecoveryfails…
If recovery fails,
then do this and
go back
Don’t execute
this at all
53. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
IftwoFactorfails…
54. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
IftwoFactorfails…
55. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
IftwoFactorfails…
If twoFactor
fails, set target
to loginPage
56. Introducing orElse
• If recovery fails or if the next computation in the chain fails.
What do we do?
• Can we attach handlers as in the Chain of Responsibility
pattern?
• Create a method orElse on Try<T> that allows us to set-
up a handler chain.
• What will orElse’ signature look like?
• Consumes a Try<URL>,
• Generically, a Try<U>
• Produces Try<URL>, generically Try<U>
57. AddingorElse abstract class Try<T> {
static <T, E extends Exception>
Try<T> with(SupplierThrowsException<T, E> s) { … }
public abstract <U> Try<U> chain(Function<T, Try<U>> f);
public abstract T get();
public abstract <U> Try<U> recoverWith(Function<Exception, Try<U>> f);
public abstract Try<T> orElse(Try<T> other);
}
class Success<T> extends Try<T> {
private final T value;
Success(T value) { this.value = value; }
public <U> Try<U> chain(Function<T, Try<U>> f) { … }
public T get() { return value; }
public <U> Try<U> recoverWith(Function<Exception, Try<U>> f) { … }
public Try<T> orElse(Try<T> other) { return this; }
}
class Failure<T> extends Try<T> {
private final Exception e;
Failure(Exception e) { this.e = e; }
public <U> Try<U> chain(Function<T, Try<U>> f) { … }
public T get() { … }
public <U> Try<U> recoverWith(Function<Exception, Try<U>> f) { … }
public Try<T> orElse(Try<T> other) { return other; }
}
58. Applying orElse
class Test {
public static void main(String[] args) {
final String userid = "softwareartisan";
final String pwd = "1234";
Authenticator authenticator = new Authenticator();
Try<URL> target = Try.with(() -> authenticator.login(userid, pwd))
.recoverWith(e -> Try.with(() ->
authenticator.gmailLogin(userid, pwd)))
.chain(user -> Try.with(() -> {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
return new URL("http://dashboard");
}))
.orElse(Try.with(() -> new URL("http://login"))
Dispatcher.redirect(target.get());
}
}
60. PendingItems class Test {
public static void main(String[] args) {
final String userid = "softwareartisan";
final String pwd = "1234";
Authenticator authenticator = new Authenticator();
Try<URL> target = Try.with(() -> authenticator.login(userid, pwd))
.recoverWith(e -> Try.with(() ->
authenticator.gmailLogin(userid, pwd)))
.chain(user -> Try.with(() -> {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
return new URL("http://dashboard");
}))
.orElse(Try.with(() -> new URL("http://login"))
Dispatcher.redirect(target.get());
}
}
// We have yet to tackle:
// 1. Recovery using gmailLogin
// 2. What about failure?
// 3. What about the login URL redirection upon failure due to
// either login, or gmailLogin or twoFactor.
61. Getting from get is
Dangerous
• For Success, we get the actual value.
• For Failure, it throws.
• To do something useful with Success, we
consume its value.
• Whereas upon Failure, we have nothing to
consume.
62. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
Aftergettingvalue…
were-direct
63. class Test {
public static void main(String[] args) throws Exception {
final URL dashboard = new URL("http://dashboard");
final URL loginPage = new URL("http://login");
final String userid = "softwareartisan";
final String pwd = "1234";
User user;
Authenticator authenticator = new Authenticator();
try {
user = authenticator.login(userid, pwd);
} catch (Exception es) {
try {
user = authenticator.gmailLogin(userid, pwd);
} catch(Exception eg) {
Dispatcher.redirect(loginPage);
return;
}
}
URL target;
try {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
target = dashboard;
} catch (Exception tfe) {
target = loginPage;
}
Dispatcher.redirect(target);
}
}
Perform
Side-effect
Aftergettingvalue…
were-direct
64. Introducing forEach
• What will forEach’s signature look like?
• Consumes a function that goes from URL -> (),
• Generically, a function from T -> (), this is our friendly
Java8 Consumer<T>
• Produces nothing - void
65. AddingforEach
abstract class Try<T> {
static <T, E extends Exception>
Try<T> with(SupplierThrowsException<T, E> s) { … }
public abstract <U> Try<U> chain(Function<T, Try<U>> f);
public abstract T get();
public abstract <U> Try<U> recoverWith(Function<Exception, Try<U>> f);
public abstract Try<T> orElse(Try<T> defaultValue);
public abstract void forEach(Consumer<T> c);
}
class Success<T> extends Try<T> {
private final T value;
Success(T value) { this.value = value; }
public <U> Try<U> chain(Function<T, Try<U>> f) { … }
public T get() { return value; }
public <U> Try<U> recoverWith(Function<Exception, Try<U>> f) { … }
public Try<T> orElse(Try<T> defaultValue) { return this; }
public void forEach(Consumer<T> c) { c.accept(value); }
}
class Failure<T> extends Try<T> {
private final Exception e;
Failure(Exception e) { this.e = e; }
public <U> Try<U> chain(Function<T, Try<U>> f) { … }
public T get() { … }
public <U> Try<U> recoverWith(Function<Exception, Try<U>> f) { … }
public Try<T> orElse(Try<T> defaultValue) { return defaultValue; }
public void forEach(Consumer<T> c) { }
}
66. Applying forEach
class Test {
public static void main(String[] args) {
final String userid = "softwareartisan";
final String pwd = "1234";
Authenticator authenticator = new Authenticator();
Try.with(() -> authenticator.login(userid, pwd))
.recoverWith(e -> Try.with(() ->
authenticator.gmailLogin(userid, pwd)))
.chain(user -> Try.with(() -> {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
return new URL("http://dashboard");
}))
.orElse(Try.with(() -> new URL("http://login"))
.forEach(target -> Dispatcher.redirect(target));
}
}
67. Finally we are here!
class Test {
public static void main(String[] args) {
final String userid = "softwareartisan";
final String pwd = "1234";
Authenticator authenticator = new Authenticator();
Try.with(() -> authenticator.login(userid, pwd))
.recoverWith(e -> Try.with(() ->
authenticator.gmailLogin(userid, pwd)))
.chain(user -> Try.with(() -> {
long twoFactorPwd = 167840;
authenticator.twoFactor(user, twoFactorPwd);
return new URL("http://dashboard");
}))
.orElse(Try.with(() -> new URL("http://login"))
.forEach(Dispatcher::redirect);
}
}
69. Monad
• One can view a Monad as representing a context and
we want to do computations within that context.
• chain - chains output of one function into the input of
the other, i.e., transform a function into composable
form
• inject - to inject a regular value in to the chain
• For Try<T> the value is a code block and, inject == with.
• chain + inject - these core functions define a
Monad.
Notions of Chaining and
Injection (Getting inside the Chain)
70. Observations
• Monad functions run the code in the context of
the monad itself by unwrapping the result.
• These functions let us use the value from its
monadic wrapper.
• Monad controls what happens inside it and what
gets out.
71. Essence of Try<T>
• Bring catching exceptions (a side-effect) under
control using compositionality.
• Pass the result of computation to a subsequent
computation in the chain.
• By-pass the chain of evaluation in case a
computation fails.
• Recover with other computation or a value and if
everything fails, resort to default handler in the
chain of responsibility.
72. Monad Laws
• Left Unit
• inject(x).chain(f) == f(x)
• Right Unit
• M.chain(inject) == M
• Associative
• (M.chain(f)).chain(g) == M.chain((x -> f(x)).chain(g))
with
Try
73. Practical Interpretation of
Monad Laws
• Left Unit: inject(x).chain(f) == f(x)
• Don’t complicate un-necessarily, if you know you can use f(x)
• The long form is identical in its effect to the short form.
• Right Unit: M.chain(inject) == M
• Again, the long form is identical in its effect to the short form.
• Associative: (M.chain(f)).chain(g) == M.chain((x ->
f(x)).chain(g))
• Refactor or Break up big computations in to smaller ones, and re-group
(compose) to create new computations while preserving their ordering.
74. Monad Laws
• Left Unit fails for Try<T>. Why?
• Try.with(expr).chain(f) != f(expr)
75. Monad Laws
• Left Unit fails for Try<T>. Why?
• Try.with(expr).chain(f) != f(expr)
x
LHS will
never raise
an exception
RHS will raise
an exception
thrown by f
or by expr
76. Monad Laws
• Left Unit fails for Try<T>. Why?
• Try.with(expr).chain(f) != f(expr)
x
LHS will
never raise
an exception
RHS will raise
an exception
thrown by f
or by expr
• Right Unit and Associative laws hold for Try.
• For practical purposes, you can still treat it as a Monad
77. Essence of Monads
• Bring the world of side-effects under control using
compositionality. (Brian Beckman)
• Compositionality is the way to control Complexity.
(Brian Beckman)
• Monads are commonly used to order sequences of
computation, but are not about ordering or sequencing.
(Haskell Wiki)
• Monads capture a -- very specific -- relationship
between values of a specific domain into a common
abstraction. (Haskell Wiki)
• Monadic structures are very common in our day-to-day
programming.
78. Language chain is a.k.a…
Scala flatMap or map and flatten
Java8 flatMap
C# SelectMany
Clojure mapcat
Haskell bind or >>=
Ruby flat_map
Groovy collect and flatten
Different names but are the same
79. References
• Real world Haskell
• Bryan O’ Sullivan, John Goerzen & Don Stewart.
• Don’t fear the Monad! - Channel 9
• Brian Beckman
• Principles of Reactive Programming - Coursera Course.
• Martin Odersky, Eric Meijer and Roland Kuhn
• Integral Education
• Sri Aurobindo’s Work and Sraddhalu Ranade’s talk.