+ All Categories
Home > Documents > The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 ·...

The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 ·...

Date post: 01-Aug-2020
Category:
Upload: others
View: 1 times
Download: 1 times
Share this document with a friend
20
Transcript
Page 1: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages
Page 2: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

The Happy LambdaFunctional Programming in Ruby

Arne Brasseur

This book is for sale at http://leanpub.com/happylambda

This version was published on 2016-02-18

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishingprocess. Lean Publishing is the act of publishing an in-progress ebook using lightweight toolsand many iterations to get reader feedback, pivot until you have the right book and buildtraction once you do.

© 2013 - 2016 Arne Brasseur

Page 3: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Tweet This Book!Please help Arne Brasseur by spreading the word about this book on Twitter!

The suggested tweet for this book is:

Looking forward to reading my copy of ”The Happy Lambda”, a book about functionalprogramming in Ruby #happylambda

The suggested hashtag for this book is #happylambda.

Find out what other people are saying about the book by clicking on this link to search for thishashtag on Twitter:

https://twitter.com/search?q=#happylambda

Page 4: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Contents

1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.1 Evocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

2. Functional Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82.1 What vs how . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82.2 Functions as first class citizens . . . . . . . . . . . . . . . . . . . . . . . . . . . 92.3 Higher Order Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92.4 Lack of mutable state . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102.5 Pure functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122.6 Strict and Lazy Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132.7 Pros and Cons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

Page 5: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

1. IntroductionRuby is designed to make programmers happy. Its creators have examined existing languagescarefully, and picked syntax and features to their taste to create a language with a unique flavor.The aroma that dominates the final dish is clearly that of object orientation, owing in no smallpart to Smalltalk. But Ruby has also been influenced by functional languages, notably LISP. Inrecent years interest in the functional way of programming has increased, and so a book thatcovers the intersection between “Ruby” and “Functional” seems long overdue.

People looking to find out what the functional craze is all about might find that much of theexisting materials are littered with jargon that is, quite frankly, baffling. Hopefully this book canavoid that trap.

This book has a dual purpose. It is foremost an introduction to the concepts of functionalprogramming, written for Rubyists. It will assume you are reasonably familiar with Ruby, butassumes little else. Beyond that it will be an experiment in seeing how functional Ruby is, or canbe. We’ll see that certain parts of idiomatic Ruby already have a functional flavor, and will lookat patterns and ideas from the functional side that can enhance your day-to-day programming.We’ll also push the envelope and go a little crazy from time to time. You might not want to useeverything you find here in your day job! But hopefully it will be fun and inspiring.

There are good reasons that interest in FP is rising. Functional code has a certain elegance, apurity that can be very appealing. It prevents you from making certain types of mistakes. Itenables you to solve certain problems in a very concise way. It is also an exciting time for FP,with novel languages like Clojure, Elixir, Scala or F# getting more followers by the day.

But there is another good reason why functional programming is making headway now, despitebeing around for decades. Up until recently processor speed went up year by year, but there isa physical limit to how small transistors, and hence how fast processors can get. With that limiton the horizon chip-makers needed a different tactic. Rather than packing a single processor uniton a chip, they started putting in multiple processor “cores”. This multiplies the performance, intheory, but the software needs to be able to keep all those cores occupied. This is where functionalprogramming comes in.

Writing software that performs multiple tasks in parallel is hard. Multithreaded programmingdoes not only break your brain, it can also be the source of the most subtle and hard to trackdown bugs. Functional programming can make it a lot easier to avoid such bugs. In fact systemswritten in a functional style can often be made to work in parallel with only minimal changes.That’s quite something to get for free! Tasks that can be easily split up and parallelized acrossmultiple cores and processors can also easily be distributed across multiple servers, making itmuch easier to scale popular services. The “map-reduce” paradigm that dominates Google-scaledata processing is essentially functional.

1

Page 6: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Introduction 2

1.1 Evocation

The simplest definition of functional programming would be “programming with values”. Thatdoesn’t sound too daunting, does it? Although it does lead to the question, what are values? ¹

We could try to answer that in depth, and confuse matters before we’ve even started. But let usstart simple, with numbers. Numbers are values, for instance.

4

12

20

Merely writing down some numbers is, to be honest, not the most exciting thing we can comeup with. So what else can we do? We can assign names to these values, i.e. numbers, and nowanywhere we use the value we can just as well use its name².

x = 4

twelve = 12

the_answer = 42

four = x

But just having those values around, named or not, still doesn’t really do much, does it? This isthe point where functions come in. Let’s start with some simple arithmetic functions since youprobably already know those.

Take a function, and feed it some values. The function does its magic, and will get back to youwith a result. This is called applying the function to one or more values. What we get back is thefunction’s result or return value. Which is, necessarily, again a value.

1 + 1

# => 2

7 * four

# => 28

result = (3 * 4) / twelve

# => 1

What we have demonstrated so far is actually just (admittedly very) plain Ruby code.If we want to show the return value of an expression, we add it in a comment startingwith # =>, as you can see here.

Isn’t this great? Turns out you’ve been doing functional programming since primary school!

¹It also leads to the question, what is programming?, which turns out to be surprisingly hard to answer well. We’ll assume that if you’rereading this book you have some sense of what programming means to you, which should be enough to cover, however loosely, the stuff we’llbe doing here.

²These are variables, but we will not stress that point, since if there is one thing we will try to avoid it is varying them. In other words, wewill avoid assigning a new value to a variable that was already previously assigned.

Page 7: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Introduction 3

So far so good, we’re getting warmed up. But so far we only know one type of values: numbers.Well, actually we have already come across another type of values. Can you guess? That’s right,functions!

Let’s try this, if functions are just values we can assign names to them.

plus = +

And we can apply functions to values, so let’s apply a function to other functions.

sum = * + /

Did you try that? Did it work?

Oh.

Turns out arithmetic operators like + and - and Ruby methods like puts and each aren’t reallyvalues. This is something that sets Ruby apart from more “pure” functional languages.

We could “valuefy” them but that’s a different story. Keep reading and we’ll eventually get tothat. But Ruby does have functions that are simply values, take this function here. It takes a valueof type String and returns a different value of type String. (Strings are values, I hope you’renoticing a trend here ³)

fn = ->(name) { "Hello, #{name}!" }

We can apply that function to a value, in other words: call the function with an argument, andit will return another value

fn.("World")

# => "Hello, World!"

We can even create and apply the function in one go.

->(name) { "Hello, #{name}!" }.("World")

# => "Hello, World!"

Unlike + or puts, this function does not have a name, it’s an anonymous function, also known asa lambda.

Lambdas will feature prominently in this text, as you might have inferred from thetitle. If you find the name confusing, try substituting it with anonymous function orfunction value. I can’t guarantee that it will make more sense that way, but it might.

³In Ruby (almost) everything is an object, and objects are values. So it seems we are generally covered.

Page 8: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Introduction 4

We can store a lambda in a variable, so we can refer to it, but it’s still just a lambda. The differencewith a traditional Ruby function⁴ is that the latter is defined by its name, it can not exist withoutit. In contrast, when we assign a lambda to a variable, it is the variable that has a name. Thevalue it holds, the lambda, is still nameless.

def jack(x)

# A Ruby method, this is not a value

end

If functions are values, and functions take values, then it follows that we can pass a function toa function, let’s see how that works.

hello_maker = ->(name) { "Hello, #{name}!" }

apply = ->(fn, value) { fn.(value) }

apply.(hello_maker, "Alonzo")

# => "Hello, Alonzo!"

So what’s going on here? Our function apply takes two values as arguments. The first value mustbe a function, the second value can be anything, as long as it can be safely passed to the firstfunction. apply will then, as its name suggest, apply the given function to the second argument.

Obviously we could have written hello_maker.("Alonzo"), bypassing the need for an apply

function altogether. Here’s a first glimpse as to why passing functions to other functions mightbe useful.

to_upper = ->(str) { str.upcase }

first_letter = ->(str) { str.chars.first }

equal_by = ->(fn, a, b) { fn.(a) == fn.(b) }

equal_by.(to_upper, "Almanac", "aLmAnAc")

# => true

equal_by.(first_letter, "Almanac", "aLmAnAc")

# => false

Now we’re talking! We’ll walk you through this code line by line.

The first function, to_upper takes a string, and it returns the same string, but all in uppercase.So to_upper.("Calamitous") would return "CALAMITOUS".

The second function simply returns the first letter of a given string, so "Fortuitous" becomes"F", "gratuitous" becomes "g".

⁴Or to be pedantic, a method, we’ll talk about the difference later in the book.

Page 9: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Introduction 5

Next up is equal_by, which is a bit different in that the first argument it takes, fn, should be afunction. For the rest equal_by is very generic. Its other two arguments, a and b, can be of anytype, as long as applying fn to them makes sense.

In our case both to_upper and first_letter expect a string, so we’ll go with Strings.

Now to find the value of equal_by.(to_upper, "Almanac", "aLmAnAc") goes something likethis, step by step

equal_by.(to_upper, "Almanac", "aLmAnAc")

# evaluate equal_by

to_upper.("Almanac") == to_upper.("aLmAnAc")

# evaluate to_upper (x2)

"Almanac".upcase == "aLmAnAc".upcase

# call upcase on each string

"ALMANAC" == "ALMANAC"

# check if the two values are equal

# => true

Functions that take other functions as their arguments, or return functions as their return value,are called higher order functions or HOFs. equal_by is the first HOF we’ve seen, since it takes afunction as one of its arguments.

The Other type of higher-order functions are those that return a function.

Let’s try that. Here’s a function that we’ll assign to the name create_adder. It takes a number,and returns a function.

create_adder = ->(n) {

->(x) { x + n }

}

add3 = create_adder.(3)

add3.(5)

# => 8

In Ruby, the result of the last expression in a function body is what is returned from the function.In this case that last statement is the lambda expression ->(x) { x + n }

When we call create_adder.(3), then inside create_adder n has the value 3, so the functionwe get back is equivalent to ->(x) { x + 3 }.

Page 10: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Introduction 6

Notice that n (with the value 3) only exists during the execution of create_adder. Once create_-adder.(3) returns n is no longer available, but somehow that 3 got captured inside the functionthat was returned. We’ll dive deeper into how that works exactly when talking about closures.

To round it up let’s look at one more higher order function. This one both takes and returnsa function. In fact it returns a function that is a lot like the one we pass to it, but with somebehavior added on top. This type of HOF is called a function decorator.

maybe = ->(fn) {

->(x) {

fn.(x) unless x.nil?

}

}

maybe_add3 = maybe.(create_adder.(3))

maybe_add3.(3)

# => 6

add3.(nil)

# => NoMethodError: undefined method `+' for nil:NilClass

maybe_add3.(nil)

# => nil

Here we create a maybe decorator that makes a function more resilient. Many functions don’tplay well with nil. They want something to work with, and will throw a hissy fit when passedthat most non-value of values, the nil.

maybe can make such a function slightly more well-behaved. It will still function like before forproper inputs, but will silently ignore any nils.

We have already covered a lot of ground in this introduction. Don’t worry if not everything hasclicked instantly. The main goal was to give you a taste of what’s to come.

Hopefully this introduction has given you an idea of the flavor of functional programming. Wewill explore in much greater depth both the general principles of functional programming, andthe mechanics that are available to us to program in a functional way in Ruby.

Page 11: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Introduction 7

The Lambda Calculus, A Historical AsideBack in the 1930’s at Princeton University, the American mathematician and logician AlonzoChurch was trying to give mathematics more rigorous underpinnings. In the process he cameup with a system called the Lambda-calculus. This later would form the basis for the first“functional” languages.

The Lambda-calculus is a formal system for reasoning about computation. It’s a tool foranswering the question “Which mathematical functions can be computed?”. In lambda calculusthere are no numbers, no strings, there is nothing except “lambdas”, which is the name Churchgave to anonymous functions. Lambdas are merely defined by the arguments they take, andthe function body.

This is what a lambda expression looks like

λx.xyz

This would be equivalent to the Ruby code

->(x) { x.(y.(z)) }

A lambda starts with the Greek letter lambda, λ, (hence the name), the arguments it takes, adot, and then the body of the function.

The lambda calculus is a very austere system. All it has are variables and anonymous functions,literally nothing else. Keep in mind that this predates actual computers. It was a theoreticaldevice, used with pen and paper to explore logic and computation. But despite that it had atremendous influence on computer science as it developed. You could say that Church, andhis contemporary Alan Turing, have created the standard model on which computer science isfounded. The equivalent of discovering atoms and molecules in physics.

Two decades later John McCarthy came up with the programming language LISP. It was thefirst functional programming language, and takes several key ideas from the lambda calculus,especially the concept of functions that can be passed around like other values.

Page 12: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

2. Functional ProgrammingFunctional programming is a style of writing software that can be practiced in many differentprogramming languages. Some language features are necessary to fully practice, and reap thebenefits of, functional programming. Certain concepts however can be applied in virtually anylanguage.

There are languages that are considered functional because they give you the tools to programin a functional way comfortably, and in such a way that the end result performs well. Languagesthat make it impossible to program in any other way are called purely functional.

This chapter will go through the features and concepts that you will typically find in a functionalprogramming language. There is a lot of non-obvious terminology associated with functionalprogramming, which hampers its adoption. Hopefully after this chapter you will be able to walkinto a room of functional enthusiasts confidently and hold your own.

2.1 What vs how

If a programming language is not “functional”, then what is it instead? Most non-functionallanguages, including object-oriented ones like Ruby, are imperative.

The word “imperative” comes from the Latin for “to command”. In grammar, sentences like“Wait for me!”, or “Make me a sandwich!” are in the “imperative mood”. It’s what you use to tellsomeone what to do.

Similarly in imperative programming we tell the computer, step by step, what to do. Here are afew lines from a once popular Ruby library.

if names.empty?

traverse_all_element(&block)

else

name_set = {}

names.each {|n| name_set[n] = true }

traverse_some_element(name_set, &block)

end

Notice how it’s saying: “Computer, traverse those elements!”. It’s imperative code, commandingthe computer step by step.

Functional programming is more about declaring what you want, and how it’s computed, andlet the compiler and run-time system figure out the exact step by step instructions. It’s more likesaying, “Computer, given these elements, please return me a list of the ones that have a nameset, applying a certain operation to each along the way.” The fact that they need to be traversedone by one is a detail you don’t care about, as long as you get the list you asked for.

8

Page 13: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Functional Programming 9

2.2 Functions as first class citizens

Let’s recap the definition we tentatively gave at the start of the book, “Functional programmingis programming with values (and functions are values)”.

We then went on to introduce values by example. Our implicit requirements for values were thefollowing:

• they can be created by the running programming (as opposed to having to be specifiedbeforehand)

• they can be assigned to variables• they can be passed into functions• they can be returned by functions

When you can do these things with functions themselves, as easily as you can with numbers orstrings, then functions are first class citizens.

Not all programming languages are created equal. In many languages there is a strict borderbetween “program code land” and “value land”. You define functions in code, and call them fromother functions, passing values in and out. But it isn’t possible to pass functions around, or createnew functions at run-time.

In languages with lambdas this distinction blurs. A lambda is effectively a little chunk of programcode that has yet to be executed, and that behaves as any other value. Ruby lies in this camp.

Ruby does still make a distinction between lambdas and regular methods. The former are first-class citizens, the latter aren’t.

A language that takes it all the way is Javascript, all functions are as value-like as any other,whether declared with a name or anonymous. This is also how things work in purely functionallanguages like Haskell.

Having lambdas as first-class citizens is a requirement to be able to do functional programming.

2.3 Higher Order Functions

We already met some higher order functions, functions that either accept or return otherfunctions. When functions are first class citizens, it follows that you can have higher orderfunctions.

Higher order functions come in various kinds. We already mentioned function decorators, whichtake a function and turn it into a slightly different function.

Another kind are those used for iterating over collections. If you have done Ruby programmingat any length you have already used these, you just might not realize it.

Page 14: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Functional Programming 10

[3, 9 ,7].map do |num|

num * num

end

# => [9, 81, 49]

Can you spot the lambda in there? It’s ->(num) { num * num }.

The “block” syntax in Ruby is just a handy shorthand for the case where you want to pass asingle lambda to a function. We will come across many more higher order functions as our storyprogresses.

2.4 Lack of mutable state

Back in maths class you might have come across equations like these,

x = 5

y = x + 2

But the following lines would presumably not get the approval of your maths teacher:

x = 5

x = x + 2

Either x equals 5, or it equals 7. It can’t be both! But in imperative languages, from C and C++all the way to Ruby and Python, we have been writing such things in our code without blinking.

In functional programming this is a big no-no!

The effect is that x has a certain value at one point, and a different value at another. Any part ofthe program that has access to x can change it. When you read out the value of x, do you know(proverbially) who touched it? Be mindful of your code’s hygiene!

We don’t necessarily have to reassign a variable to change it. We might be able to simply changethe object it points to. An object that allows changes to be made to its internals is calledmutable.Objects that, once created, can never be changed, are called immutable. Going forward, we willadd this to our list of requirements for things to be considered a value:

Values * can be created by the running programming * can be assigned to variables * can bepassed into functions or returned by functions * are immutable

In the olden days, back when forests were still enchanted and computer screens showed orangetext on a black background, people would program with global variables and gotos. At the top oftheir programs they would declare a long list of all the variables that would be used throughoutthe program. Then the program began executing line by line, top to bottom, until an occasionalgoto would cause the execution to continue on some other line later or earlier in the program.

This lead to what is known as “spaghetti code”. A messy hodgepodge of control flow that wasimpossible to disentangle.

Page 15: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Functional Programming 11

What makes this programming style so hard to reason about is that different parts of the programcommunicate by altering shared state. Imagine a factory with a big blackboard in the middle.No-one is allowed to talk, their only method of communication is writing to or reading fromthe blackboard. You could devise an intricate system to make this work, but it’s easy to see howmessy things would get. This is how programming with shared state works. One part of theprogram will change some variables to influence what another part of the program does.

This is the danger of using mutable state, taken to its most extreme. But even in clean, object-oriented Ruby code wemight have manymicro-violations, small gourmet portions of handmadespaghetti that all add up until your code comes crashing down with indigestion.

Completely abandoning mutable state might seem an extreme stance to hold. The world changesconstantly after all, isn’t it vital that your application can keep track of change? What about userinput, network traffic, scheduled jobs?

Obviously we need to keep track of application state. What we try to avoid however, is changingthings in place. Since people seem to be capable of writing complex applications in languagesthat don’t have the concept of changeable variables we will assume for now that this is not justcrazy talk. Much of this book will be figuring out how to program given these constraints.

To recap, here are some of the things when it comes to programming “in the small” that fromhere on will be frowned upon,

array << :wen

array.concat([:lobsang, :sweeper])

str << "hello"

x += 1

str.upcase!

options[:verbose] = true

object.attribute = new_value

Purely functional languages do not allow mutable state. Once a value is set, it stays set. You canderive a new value from the old one, but you can’t change it in place. So these are all allowed,

array + [:lobsang, :sweeper]

str + "hello"

x + 1

str.upcase

options.merge(verbose: true)

Ruby’s strings deserve an honorablemention here. They behavemore like string buffersin other languages, where you can append to, shorten or replace content inside thebuffer. Some of the methods on String come in two variants, a regular one anda “destructive” variant that changes the string in place. They are called destructivebecause after they complete, the original string value is gone. You can recognize thedestructive versions because their names end in an exclamation mark, signaling like awarning flag their dangerous character. For example upcase!, downcase!, delete! andgsub!

Page 16: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Functional Programming 12

2.5 Pure functions

Once we’ve decided to stick to immutable values, the concept of pure functions follows quitenaturally. A function is pure when its output (return value) is completely determined by itsinputs (arguments), and it doesn’t do anything “on the side” besides coming up with a returnvalue.

For example ->(x,y) { x * y } is a pure function. If we call it with the arguments 3 and 4, itwill always return 12. The only way we can get a different output is by giving different inputs.To contrast, take this function

x = 3

fn = ->(y) { x * y }

fn.(4)

# => 12

x = 5

fn.(4)

# => 20

Here we have executed fn.(4) twice, but we did not get the same result back. fn is clearly nota pure function.

Side effects

A function has side effects if it interacts in some way with the world outside the function. Forexample, apart from calculating its return value it could also write to some output to the screen.

fn = ->(x,y) do

puts "#{x}, #{y}"

x * y

end

We still get identical output for identical input, but there is also the side effect of writing to thescreen. Similarly interacting with files, the network or connected devices are all considered sideeffects, and thus not allowed in pure functions.

There is another type of side effects, which comes from having mutable state. Take this function

Page 17: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Functional Programming 13

suffixify = ->(str) { str << "icious" }

w = "awesome"

suffixify.(w)

# => "awesomeicious"

w

# => "awesomeicious"

Here again our function does more then is necessary to come up with its output. It also changesthe string we pass to it. Perhaps this wasn’t even the programmer’s intention. Luckily making itpure again requires only a small change

suffixify = ->(str) { str + "icious" }

Where before we were changing an object, now we create a new object and return that, leavingthe old one unchanged.

Here are some more examples of non-pure functions, can you say why they aren’t pure?.

counter = 0

->(x,y) { counter += 1 ; x * y }

->(x,y) { counter + x * y }

->(x) { x * readline.to_i }

Referential Transparency

The great thing with pure functions is that it doesn’t really matter whether we execute them onceor a million times. It also doesn’t matter so much in what order we execute them. All that mattersis that we can calculate the values we need from the values we have available by combining theright functions. We have the freedom to decide which intermediate calculations we do first. Withside-effecting functions reordering the execution will almost certainly change how the programbehaves.

Anywhere a pure function is used, we can replace the function call with its result, and theprogram still works the same as before. This property is called referential transparency. Thefunction in itself is not so important. If we can get the same return value through any othermeans that’s fine as well, because the function doesn’t have side effects that we would skip.

So this is great, we can forego calling the function at all! Simply replace the function call with itsreturn value. Or to phrase it in a less chicken-and-egg way, we only have to calculate the resultonce and can then reuse it everywhere. This pattern is called memoization.

2.6 Strict and Lazy Evaluation

Given a program that computes a certain result, a computer will have to evaluate this program,one way or another, so that in the end it finds the correct computed result.

Page 18: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Functional Programming 14

There are several ways to go about this, but it’s likely something you never consciously thoughtabout. All common imperative programming languages approach this the same way, includingRuby, so it likely just seems the logical thing to do.

The evaluation strategy of Ruby is called call-by-value, and it is considered a strict evaluationstrategy. Let’s spell out exactly how that works.

Given this function call

def add_if(condition, list, v1, v2)

if condition

list + [v1]

else

list + [v2, v2]

end

end

add_if(true, [], 7*7, 2**99 / 131)

Before entering add_if, Ruby will need the value of all the arguments. It will go over theargument list from left to right, evaluating each to produce its value. When all the argumentvalues have been computed, in this case true, [], 49 and 4838361069573394662201157272, theprogram enters add_if, checking condition and adding one of the two elements to the list.

The fact that all expressions given as arguments are fully evaluated to values before calling thefunction makes this a strict evaluation strategy.

What is not so cool about this is that the value of the second expression 2**99 / 131 is actuallynever used. We have computed that large number, and then simply discarded it! Granted,arithmetic is pretty fast, but this could have been any kind of complex calculation taking foreverto finish, all for nothing.

So is it possible to do it any other way? Turns out there is! We could substitute the argumentexpressions as they are right in the function body. This would yield something like this

if condition

list + [7*7]

else

list + [2**99 / 131, 2**99 / 131]

end

What we have accomplished now is non-strict evaluation. We have postponed the evaluation ofthe arguments until we actually need them. Now only one of the two expressions will actuallybe calculated. This is also called “call-by-need”. Only when we actually need the value of anexpression before we can continue, we evaluate it.

Notice though that with the above substitution we risk calculating 2**99 / 131 twice. Thatcertainly defeats the purpose. Languages with support for lazy evaluation will be smart about

Page 19: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Functional Programming 15

this. They will evaluate each expression only if it’s needed, and if it is, it is evaluated only once,and the result is memoized.

Ruby’s evaluation strategy is strict, so we normally won’t be able to rely on laziness much. Butthere are some ways to trick ruby in postponing evaluation, so we have a kind of “optionallaziness”. We’ll explore that later when we dive deep into what Ruby can and can’t do.

An important requirement for lazy evaluation is that the functions involved are pure. Take thisexample

add_if(true, [], increase_counter(foo), log_and_return(bar))

Here the two expressions that might or might not be evaluated are increase_counter(foo),which presumably changes the value of a counter, and log_and_return(bar), which appendssomething to a log file. In these cases we don’t call these methods just for their return values,but also for the side effects they cause. Having a system that decides for us if these need to beevaluated or not can really come back to bite us. If all functions involved are pure however, thenit’s really not important if they are actually evaluated or not, so the system can optimize themaway as it sees fit.

Laziness isn’t just about optimization though. A common pattern in languages with lazyevaluation is to construct infinite sequences, for instance the list of all prime numbers. Eachelement is only actually computed as it is used, so as long as you only end up needing a finiteamount of them, you’re all good. The implementation of the algorithm doesn’t care. It just knowshow to compute the next prime, whether you need a few, or a few million.

Ruby 2.0 added lazy enumerators, which provides similar functionality. We’ll talk about themlater in the book.

2.7 Pros and Cons

Nouns vs Verbs

When I learned how to computer, we had a course called “Object Oriented Analysis”. We weretaught to approach programming by creating diagrams of the various “things” that your programwill need to deal with. It was called “modeling the domain”.

For instance, suppose you need to make a system for scheduling hospital visitor hours. The“things” in your domain would be the Hospital, Patients, Time Slots, Rooms, Doctors, etc. Theserelate to each other in certain ways. A hospital would be associated with one or more doctors,for instance.

Finally you added behavior to these “objects”. A patient can register, or pay. Perhaps a roomcan find a time slot in which it is free. (You end up making surreal statements about inanimateobjects when modeling reality through the lens of Object Orientation.)

While this technique certainly has some merit, it also has a very big drawback. You only startthinking about what the system has to do until very late in the process. The reason is that the

Page 20: The Happy Lambda - Leanpubsamples.leanpub.com/happylambda-sample.pdf · 2016-02-18 · 1.Introduction Rubyisdesignedtomakeprogrammershappy.Itscreatorshaveexaminedexistinglanguages

Functional Programming 16

most prominent items in your modeling, classes and objects, all tend to be nouns. They are thethings that make up this world, they are not what those things do.

Luckily times have changed, and smart people will tell you that it’s not about the objectsthemselves, it’s about how they interact. The dynamic behavior of a system is more importantthan its static structure.

Functional programming takes this one step further, by focusing on the verbs first. It allows theprogrammer to concisely declare what needs to happen. In the extreme this leads to what iscalled “point-free” style, in which functions are combined to form composed behavior, which isonly at the end applied to actual values.

Moving parts

To indicate that a software system is complex and difficult to manage, it is sometimes describedas having “too many moving parts”. This metaphore is apt. When building software we layerand compose various abstractions. As more of these start interacting it becomes harder to keepa clear mental image how the system as a whole functions.

When parts start interacting the complexity does not go up linearly, every part added has amultiplying effect on the number of relations that play a part in the final behavior.

When different parts of your program read and write to shared data you need to keep track ofwhich part is altering what, at what time. The ordering in which things happen is vital, yet thisordering is never specified explicitly. It comes about from carefully constructing an algorithm.

Contrast this with programming in a functional style. When all your code is data-in data-out,dealing with immutable values, then every context, every function, is completely localized. Youcan forget about the rest of the system for a moment, just make sure that each function doeswhat it is supposed to do, and then compose those into bigger, more complex functions.

I have found that every single time when my code is becoming complex and hard to reasonabout, taking a step back and rephrasing things in a functional way cleared up the confusing andled to more clear, simple and maintainable code.


Recommended