DRYing to Monad in Java8
-
Upload
dhaval-dalal -
Category
Technology
-
view
12.740 -
download
2
Transcript of DRYing to Monad in Java8
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…
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); }}
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); }}
The
Hap
py P
ath
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
The
Hap
py P
ath
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
The
Hap
py P
ath
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 ThirdThe
Hap
py P
ath
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
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);
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”
• 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
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); }}
The
Hap
py P
ath
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
The
Hap
py P
ath
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
The
Hap
py P
ath
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 EventuallyTh
e H
appy
Pat
h
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.
Tiding up…@FunctionalInterfaceinterface 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 { … } }
Tidi
ng u
p…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; } Dispatcher.redirect(target); }
Tidi
ng u
p…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);
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.
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.
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 { … } }
Intro
duci
ng Try<T>
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 { … } }
Intro
duci
ng Try<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); } }}
Introduce Polymorphic variants of Try<T>
• Success<T> - holds successful result of computation.
• Failure<T> - holds exception from computation.
Varia
nts
of Try<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; }}
App
lyin
g Try<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); }}
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);
App
lyin
g Try<T>
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;});
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>
Addi
ng chain
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; }}
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
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 chainCompilation Error
“Incompatible types: Try<URL> cannot be converted to
URL” Dispatcher.redirect(target);
Addi
ng get()
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); }}
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
Visualizing the pipes so far…
Try.with (system login)
chain (two factor authentication)
URL
Unhandled Errors
Dashboard
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).
Pend
ing
Item
s
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); }}
Failu
re R
ecov
ery
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 firstFa
ilure
Rec
over
y
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 firstFa
ilure
Rec
over
y
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 firstFa
ilure
Rec
over
y
In case of failure, recover with
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
Failu
re R
ecov
ery
In case of failure, recover with
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>
Addi
ng recoverWith 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); } }}
Applying recoverWithclass 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()); }}
Visualizing the pipes again…
Try.with (System login)
recoverWith (gmail login)
URL
Unhandled Errors
chain (two factor authentication)
Dashboard
Pend
ing
Item
sclass 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.
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); }}If
Reco
very
fails
…
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); }}If
Reco
very
fails
…
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); }}If
Reco
very
fails
…
If recovery fails, then do this and
go back
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); }}If
Reco
very
fails
…
If recovery fails, then do this and
go back
Don’t execute this at all
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); }}If
twoF
acto
r fai
ls…
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); }}If
twoF
acto
r fai
ls…
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); }}If
twoF
acto
r fai
ls…
If twoFactor fails, set target
to loginPage
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>
Addi
ng orElse
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; }}
Applying orElseclass 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()); }}
Visualizing the pipes again…
Try.with (System login)
recoverWith (gmail login)
URL
Unhandled Errors
chain (two factor authentication)
Dashboard
orElse (login URL)
Pend
ing
Item
sclass 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.
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.
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); }}
Afte
r get
ting
valu
e…w
e re
-dire
ct
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
Afte
r get
ting
valu
e…w
e re
-dire
ct
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
Addi
ng forEach
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) { }}
Applying forEachclass 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)); }}
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); }}
Implementation available on
http://dhavaldalal.github.io/Java8-Try
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)
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.
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.
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
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.
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
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
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.
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
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.