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

DRYing to Monad in Java8

Jan 15, 2017

Download

Technology

Dhaval Dalal
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: DRYing to Monad in Java8

DRYing to Monads in

[email protected]

@softwareartisan

Page 2: 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…

Page 3: DRYing to Monad in Java8

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

Page 4: DRYing to Monad in Java8

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

Page 5: DRYing to Monad in Java8

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

Page 6: DRYing to Monad in Java8

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

Page 7: DRYing to Monad in Java8

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

Page 8: DRYing to Monad in Java8

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

Page 9: DRYing to Monad in Java8

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

Page 10: DRYing to Monad in Java8

Can we chain the two try blocks and pass

data around implicitly?

Page 11: DRYing to Monad in Java8

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”

Page 12: DRYing to Monad in Java8

• 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

Page 13: DRYing to Monad in Java8

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

Page 14: DRYing to Monad in Java8

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

Page 15: DRYing to Monad in Java8

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

Page 16: DRYing to Monad in Java8

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

Page 17: DRYing to Monad in Java8

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.

Page 18: DRYing to Monad in Java8

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 { … } }

Page 19: DRYing to Monad in Java8

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

Page 20: DRYing to Monad in Java8

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

Page 21: DRYing to Monad in Java8

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.

Page 22: DRYing to Monad in Java8

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.

Page 23: DRYing to Monad in Java8

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>

Page 24: DRYing to Monad in Java8

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

Page 25: DRYing to Monad in Java8

Introduce Polymorphic variants of Try<T>

• Success<T> - holds successful result of computation.

• Failure<T> - holds exception from computation.

Page 26: DRYing to Monad in Java8

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

Page 27: DRYing to Monad in Java8

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

Page 28: DRYing to Monad in Java8

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>

Page 29: DRYing to Monad in Java8

Time to address the question:

How can we chain the next try block?

Page 30: DRYing to Monad in Java8

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

Page 31: DRYing to Monad in Java8

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>

Page 32: DRYing to Monad in Java8

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

Page 33: DRYing to Monad in Java8

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

Page 34: DRYing to Monad in Java8

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

Page 35: DRYing to Monad in Java8

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

Page 36: DRYing to Monad in Java8

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

Page 37: DRYing to Monad in Java8

Visualizing the pipes so far…

Try.with (system login)

chain (two factor authentication)

URL

Unhandled Errors

Dashboard

Page 38: DRYing to Monad in Java8

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

Page 39: DRYing to Monad in Java8

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

Page 40: DRYing to Monad in Java8

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

Page 41: DRYing to Monad in Java8

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

Page 42: DRYing to Monad in Java8

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

Page 43: DRYing to Monad in Java8

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

Page 44: DRYing to Monad in Java8

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>

Page 45: DRYing to Monad in Java8

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

Page 46: DRYing to Monad in Java8

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

Page 47: DRYing to Monad in Java8

Visualizing the pipes again…

Try.with (System login)

recoverWith (gmail login)

URL

Unhandled Errors

chain (two factor authentication)

Dashboard

Page 48: DRYing to Monad in Java8

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.

Page 49: DRYing to Monad in Java8

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

Page 50: DRYing to Monad in Java8

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

Page 51: DRYing to Monad in Java8

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

Page 52: DRYing to Monad in Java8

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

Page 53: DRYing to Monad in Java8

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…

Page 54: DRYing to Monad in Java8

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…

Page 55: DRYing to Monad in Java8

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

Page 56: DRYing to Monad in Java8

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>

Page 57: DRYing to Monad in Java8

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

Page 58: DRYing to Monad in Java8

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

Page 59: DRYing to Monad in Java8

Visualizing the pipes again…

Try.with (System login)

recoverWith (gmail login)

URL

Unhandled Errors

chain (two factor authentication)

Dashboard

orElse (login URL)

Page 60: DRYing to Monad in Java8

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.

Page 61: DRYing to Monad in Java8

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.

Page 62: DRYing to Monad in Java8

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

Page 63: DRYing to Monad in Java8

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

Page 64: DRYing to Monad in Java8

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

Page 65: DRYing to Monad in Java8

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) { }}

Page 66: DRYing to Monad in Java8

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

Page 67: DRYing to Monad in Java8

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

Page 68: DRYing to Monad in Java8

Implementation available on

http://dhavaldalal.github.io/Java8-Try

Page 69: DRYing to Monad in Java8

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)

Page 70: DRYing to Monad in Java8

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.

Page 71: DRYing to Monad in Java8

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.

Page 72: DRYing to Monad in Java8

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

Page 73: DRYing to Monad in Java8

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.

Page 74: DRYing to Monad in Java8

Monad Laws• Left Unit fails for Try<T>. Why?

• Try.with(expr).chain(f) != f(expr)

Page 75: DRYing to Monad in Java8

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

Page 76: DRYing to Monad in Java8

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

Page 77: DRYing to Monad in Java8

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.

Page 78: DRYing to Monad in Java8

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

Page 79: DRYing to Monad in Java8

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.

Page 80: DRYing to Monad in Java8

Thank-you!