Transcript

WHAT IS PURE FUNCTIONAL PROGRAMMING, AND HOW IT

CAN IMPROVE OUR APPLICATION TESTING?

Voxxed Days Zürich 2016-03-03

Voxxed Days Zürich 2016-03-03 1

About me

Luca Molteni

@volothamp

BitGold (h+ps://bitgold.com)

Voxxed Days Zürich 2016-03-03 2

Pure Func)onal Programming is fun

Voxxed Days Zürich 2016-03-03 3

Tes$ng is also fun

Voxxed Days Zürich 2016-03-03 4

Code quality

Voxxed Days Zürich 2016-03-03 5

Table of Content• What is Pure Func0onal Programming?

• Tes0ng

• Type Systems

• Conclusions

• Q & A

Voxxed Days Zürich 2016-03-03 6

What is Func,onal Programming?• Func&ons are first-class, that is, func&ons are values which can

be used in exactly the same ways as any other sort of value.

• FP languages are focused around evalua&ng expressions rather than execu&ng instruc&ons.

Voxxed Days Zürich 2016-03-03 7

What is "Pure Func0onal Programming?"

Voxxed Days Zürich 2016-03-03 8

What is Referen-al Transparency?

“An expression is said to be referen1ally transparent if it can be replaced with its value without changing the behavior of a program (in other words, yielding a program that has the same effects and

output on the same input).”

Voxxed Days Zürich 2016-03-03 9

Referen&al Transparency in Haskell

sum :: Int -> Int -> Intsum a b = a + b

sum 2 3 == 5sum 3 3 == 6

Voxxed Days Zürich 2016-03-03 10

Referen&al Transparency in Javapublic int sum(int a, int b) { int anotherB = // If the DB is empty, use b as default persistence.readFromSumTableWithDefault(b);

int sum = a + anotherB;

updateSumTable(sum);

return sum;}

sum(2, 3) == 5sum(2, 3) == 7 // ?!

Voxxed Days Zürich 2016-03-03 11

An Impure language can write a pure func2on

/* This is the same method as before */public int sum(int a, int b) { return a + b;}

sum(2, 3) == 5sum(2, 3) == 5

Voxxed Days Zürich 2016-03-03 12

This is not a pure func0on

public int sum(int a, int b) { writeLastExecutedFile(a, b); return a + b;}

Voxxed Days Zürich 2016-03-03 13

What are the "Pure" Func.onal programming languages?

• Haskell

• Clean

• Excel

• Scala?

• Java?

Voxxed Days Zürich 2016-03-03 14

Tes$ng pure code

Voxxed Days Zürich 2016-03-03 15

• By Paul Chiusano and Rúnar Bjarnason (Manning)Voxxed Days Zürich 2016-03-03 16

Untestable Cafeclass CreditCard() { def charge(price: BigDecimal): Unit = if (Random.nextInt(4) == 1) println("I'm calling a webservice") else throw new RuntimeException("crashing")}

case class Coffee(price: BigDecimal = 1)

class UntestableCafe { def buyCoffee(cc: CreditCard): Coffee = {

val cup = new Coffee() cc.charge(cup.price) // Side effect cup }}

Voxxed Days Zürich 2016-03-03 17

Untestable Cafe Test

class UntestableCafeTest extends Specification { "Cafe should be able to charge an amount" >> {

val cafe = new UntestableCafe()

val coffee = cafe.buyCoffee(new CreditCard())

success("hopefully charged") }}

Voxxed Days Zürich 2016-03-03 18

Testable Cafecase class CreditCard()case class Coffee(price: BigDecimal = 1)

trait Payments { def charge(cc: CreditCard, amount: BigDecimal) }

class TestableCafe { def buyCoffee(cc: CreditCard, p: Payments): Coffee = {

val cup = new Coffee()

p.charge(cc, cup.price) cup }}

Voxxed Days Zürich 2016-03-03 19

Testable Cafe Testclass TestableCafeTest extends Specification with Mockito { "Cafe should be able to charge an amount" >> {

val cafe = new TestableCafe()

val mockedPayment = mock[Payments] val cc = new CreditCard()

val coffee = cafe.buyCoffee(cc, mockedPayment)

there was one(mockedPayment).charge(cc, 1) }}

Voxxed Days Zürich 2016-03-03 20

Pure Cafecase class CreditCard()

case class Coffee(price: BigDecimal = 1)

case class Charge(cc: CreditCard, amount: BigDecimal)

class PureCafe { def buyCoffee(cc: CreditCard): (Coffee, Charge) = {

val cup = new Coffee()

(cup, Charge(cc, cup.price)) }}

Voxxed Days Zürich 2016-03-03 21

Pure Cafe Test

class PureCafeTest extends Specification with Mockito { "Cafe should be able to charge an amount" >> {

val cafe = new PureCafe()

val result = cafe.buyCoffee(new CreditCard())

result._2.amount === 1 }}

Voxxed Days Zürich 2016-03-03 22

Pure FP Helps Unit Tes0ng

Voxxed Days Zürich 2016-03-03 23

Do we have something be0er than unit tes3ng?

Voxxed Days Zürich 2016-03-03 24

Increase the entropy of the inputs"insert element in database" in { val username = randomString

val numberOfRowInserted = database.insert(1, username).futureAwait

val usernameFromDB = database.findById(1)

numberOfRowInserted === 1 usernameFromDB === username}

Voxxed Days Zürich 2016-03-03 25

Beware of pseudo-random tests!

Voxxed Days Zürich 2016-03-03 26

Property based tes-ng

val modelGenerator = for { tag <- arbitrary[String] elem <- arbitrary[String] } yield Model(tag, elem)

property("String is always 891 characters") = forAll(modelGenerator) { record => processString(record).length == 891}

Voxxed Days Zürich 2016-03-03 27

Proper&es instead of Asser&ons

Voxxed Days Zürich 2016-03-03 28

How can Pure FP helps Property based tes5ng?

Voxxed Days Zürich 2016-03-03 29

Beware of readability 1

take5 :: [Char] -> [Char]take5 = take 5 . filter (`elem` ['a'..'e'])

quickCheck ((\s -> (reverse.reverse) s == s) :: [Char] -> Bool)

quickCheck (\s -> all (`elem` ['a'..'e']) (take5 s))

1 h$ps://wiki.haskell.org/Introduc9ontoQuickCheck1

Voxxed Days Zürich 2016-03-03 30

TDD by Kent Beck and property base tes3ng 2

2 h$p://natpryce.com/ar2cles/000807.html

Voxxed Days Zürich 2016-03-03 31

Side Effects and Mocks

Voxxed Days Zürich 2016-03-03 32

Code Example with a lot of mocking public void mocking() { final File dir = Mockito.mock(File.class); BDDMockito.given(dir.getAbsolutePath()).willReturn("/some/otherdir/" + FILE); Mockito.doReturn(dir).when(extensionAccessor).getExtensionDir(FILE);

final String remotePath = "/some/dir/" + NAME; Mockito.doReturn(remotePath).when(request).getRequestURI();

Mockito.doReturn(remotePath).when(filter).getFullPathNameFromRequest(request); Mockito.doReturn("/some/dir/" + NAME).when(filter).getFullPathNameFromRequest(request);

filter.doFilter(request, response, filterChain);

Mockito.verify(filter).copyFileInternalIfNeeded("/some/dir/" + NAME); }

Voxxed Days Zürich 2016-03-03 33

Mockable classtrait DbThing { def getDbStuff(params): Future[DbStuff] }

trait HttpThing { def getHttpStuff(params): Future[HttpStuff] }

class BizThingImpl(db: DbThing, http: HttpThing) { def doComplicatedStuff(params): () = db.getDbStuff(params).zip(http.getHttpStuff(params)).map { case (dbStuff, httpStuff) => if(dbStuff.value > 0) doSomething else doSomethingElse case _ => throw new Exception("something went wrong") }}

Voxxed Days Zürich 2016-03-03 34

Can we avoid stubbing?def bizThingImpl(getDBStuff: Param => Future[DBStuff]) (httpStuff: Param => Future[HTTPStuff]): () = {

getDbStuff.zip(httpStuff(params)).map { case (dbStuff, httpStuff) => if(dbStuff.value > 0) doSomething else doSomethingElse case _ => throw new Exception("something went wrong") }}

Voxxed Days Zürich 2016-03-03 35

How can we avoid mocking?def bizThingImpl(getDBStuff: Param => Future[DBStuff]) (httpStuff: Param => Future[HTTPStuff]): Future[Result] = {

getDbStuff.zip(httpStuff(params)).map { case (dbStuff, httpStuff) => if(dbStuff.value > 0) Ok(httpValue.result) else NotFound case _ => InternalServerError }}

Voxxed Days Zürich 2016-03-03 36

Separate the pure from the impure part

Voxxed Days Zürich 2016-03-03 37

Applica'on with li.le logicpublic void complexInteraction(User user) {

httpClient.call(user.firstName , user.lastName, user.address, user.capCode, user.phoneNumber)

db.insertUser(user.firstName , user.lastName, user.address, user.capCode, user.phoneNumber)}

Voxxed Days Zürich 2016-03-03 38

Tes$ng applica$on with li1le logicpublic void testComplexInteraction(User user) {

User user = aUser("firstName", "lastName", "address", "capCode", "phoneNumber")

service.complexInteraction(user)

verify(httpClient) .call(user.firstName, user.lastName, user.address , user.capCode, user.phoneNumber) verify(db) .insertUser(user.firstName , user.lastName, user.address , user.capCode, user.phoneNumber)

}

Voxxed Days Zürich 2016-03-03 39

What should we test?

Voxxed Days Zürich 2016-03-03 40

It is hard to write tests.

Voxxed Days Zürich 2016-03-03 41

Tests vs Type Systems

Voxxed Days Zürich 2016-03-03 42

Referen&al Transparency again 3

function sum(a, b) = { ??? }

num = sum(2,3)

test.assert(typeof num === 'number');

3 h$p://unitjs.com/

Voxxed Days Zürich 2016-03-03 43

Same test using Javapublic String sayHello(String name) { return "Hello " + name + "!";}

public void testGreetingIsString() { assertEquals(String.class, sayHello("luca").getClass());}

Voxxed Days Zürich 2016-03-03 44

Excep&ons break referen&al transparency

Voxxed Days Zürich 2016-03-03 45

Feedback

Voxxed Days Zürich 2016-03-03 46

Compile )me vs Execu)on )me

• Type System ⟶ Compile /me

• Tests ⟶ Run Time

Voxxed Days Zürich 2016-03-03 47

Reading asser+on vs reading type errors

Voxxed Days Zürich 2016-03-03 48

Compiler error messages shouldn't be hard (Elm) 4

4 h$p://elm-lang.org/blog/compiler-errors-for-humans

Voxxed Days Zürich 2016-03-03 49

"The problem with the type checker is that it should never be smarter

than the developer"— Unknown

Voxxed Days Zürich 2016-03-03 50

Readability• Use unit tes*ng to give example of your API

• Use property based tes*ng when you want to verify proper*es

• Use Type Systems when having li?le logic

Voxxed Days Zürich 2016-03-03 51

Be careful using type inference

Voxxed Days Zürich 2016-03-03 52

BONUS

Voxxed Days Zürich 2016-03-03 53

Dependent Types

Voxxed Days Zürich 2016-03-03 54

Test the concatena+ons of two lists

def testConcatenationOfList() { List(1, 2) ++ List(3,4) === List(1,2,3,4)}

def testConcatenationOfListWithSize() { val sum = List(1, 2) ++ List(3,4)

sum.length === 4}

Voxxed Days Zürich 2016-03-03 55

Concatena(on using dependent types 5

concatenate : Vect n a -> Vect m a -> Vect (n + m) aconcatenate Nil ys = ysconcatenate (x :: xs) ys = x :: app xs ys

5 h$ps://eb.host.cs.st-andrews.ac.uk/wri8ngs/idris-tutorial.pdf

Voxxed Days Zürich 2016-03-03 56

Idris error messages

Can’t unify

Vect (n + n) awith

Vect (plus n m) aSpecifically:

Can’t unify plus n n with plus n m

Voxxed Days Zürich 2016-03-03 57

Conclusion• Pure Func*onal Programming leads to a more testable code

• If you're going to use pure FP, use a strong type system too (but don't forget tests)

• Modern type systems help us finding bugs in a more efficient way while designing our applica*on.

• If the logic is limited, rely on the Type System

Voxxed Days Zürich 2016-03-03 58

Q&A

Voxxed Days Zürich 2016-03-03 59

Thank you for your ,meFeedback please!

@volothamp

mailto:[email protected]

Voxxed Days Zürich 2016-03-03 60

BONUS 2

Voxxed Days Zürich 2016-03-03 61

Stability Problem• h#ps://github.com/mikeizbicki/HerbiePlugin

Voxxed Days Zürich 2016-03-03 62

The problem with the IO Type / Monad in Haskell

Voxxed Days Zürich 2016-03-03 63


Recommended