DRYing to Monad in Java8

80
DRYing to Monads in Java8 [email protected] @softwareartisan

Transcript of DRYing to Monad in Java8

DRYing to Monads in

[email protected]

@softwareartisan

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);

Can we chain the two try blocks and pass

data around implicitly?

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>

Time to address the question:

How can we chain the next try block?

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)

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.

Thank-you!