I hope that you’ve found this book to be a helpful stepping-stone toward functional programming. Most important, I hope that it has demystified some of the concepts and shown how you might implement them without switching to a purely functional language.
I also hope that you can take some of the early concepts that might actually provide you the greatest benefit and apply those to your everyday job, helping you write less code while implementing more functionality.
At this point, you should have the knowledge and the understanding of how to transform your current imperative code into functional code. As you start your transition, you will want to break it down into steps. Let’s look at the transitional steps and recap how to implement those concepts:
The first step to take is to introduce some higher-order functions, as you saw in Chapter 2. To accomplish this in Java, you either use pre-existing libraries such as Guava or create your own interface like the Function1
interface so that you can encapsulate functionality to be passed to another function.
As demonstrated in Chapter 2, you can also begin to convert to a language such as Groovy, which enables you to use higher-order functions while still keeping the basic Java syntax. The advantage is that you don’t need to rewrite your entire code base in order to convert.
If you cannot integrate with a non-type-safe language such as Groovy, I would suggest starting to integrate with another language, such as Scala, to keep your type safety but still begin integrating functional concepts.
This has the largest benefit, because you’ll be able to immediately start taking advantage of code reuse. You’ll start seeing loops where you can abstract the looping logic and just start passing a higher-order function into the looping logic.
The next step is to start converting your normal methods into pure functions, as I showed in Chapter 3. These should actually be fairly straightforward to convert—and as you begin converting them, you’ll find how much easier it is to write tests for those functions.
This is, again, one step that will have a lot of benefit right up front. You’ll begin to reduce your functions, which will make them even more testable and more understandable. This is really one of the first times that the concept of expressiveness will come up. As your decompose your functions into smaller, more pure functions, they will inherently become more expressive. Expressiveness reflects how much meaning each line of code has; the more that can be understood per one line of code, the more expressiveness it has.
You don’t want to jump directly into immutable variables, so the first thing to do is convert your looping structures over to recursive—specifically, tail-recursive if you can—methods. The caveat here is that some languages, such as Java, don’t support tail recursion. If you can use it without worrying about buffer overflow, try it.
Recursion is something that many people find unfamiliar at first, but after you get into a functional mindset and practice with it, you’ll get much better at seeing the conceptual framework. You’ll see iteration via recursion instead of just normal iterative loops. The nice thing about recursion is that you’ll be able to truly test your loops to make sure that you’re looping over what you want. You’ll also be able to more fully test them.
One of the last things you can do—without switching to a fully functional language—is move your normal mutable variables into immutable variables. This has many different implications, including the ability to do highly concurrent applications without locking variables.
Immutable variables are one of the more difficult things to get used to working with because we, as developers, are so accustomed to changing variables over time. The positive for not changing the variable is that you have no concern that a variable has “changed out from under you.”
After you’ve done these things, generally you will have exhausted most of your functional abilities in the language you’re using. The next step, then, is to move to a more functional language, such as Scala or Groovy, or to a fully functional language, such as Erlang or Clojure. Whatever you choose, the concepts and ways to program in the functional paradigm will remain the same.
There are a few different design patterns based on some of the concepts we’ve seen throughout this book:
Let’s look at some of these design patterns and how we can actually use them in our day-to-day jobs.
When we think about concurrency, we think about a thread that starts, processes an amount of work, and then exits. Sometimes we think of a thread pool to which we submit jobs to be executed. However, with message passing we can actually send a message, which is then interpreted by the running thread to do processing.
The big difference between message passing and thread pools is that with the latter, an individual job must be created and executed, whereas in message passing you can have a thread that exists for a long period of time, and send messages to that thread to tell it what operations to perform. This allows the threads to communicate without blocking.
The Null Object pattern in Java is a way you can provide an object to be executed, but the object does nothing when executed and thus removes the need for null
. An extension of that pattern is the Option pattern. The concept, as we saw in earlier chapters, is that you have an Option
interface that is implemented by a Some
and a None
case; then with pattern matching, you are able to extract when the Option
has Some
or do an else
if there is None
.
Even if we don’t have pattern matching, we can still use the Option pattern by creating a getOrElse(T obj)
method on the Option
interface. This way, you can actually pass something to use in the event that you have None
. All of the classes are listed in Example 10-1.
public
interface
Option
<
T
>
{
public
T
getOrElse
(
T
defObj
);
}
public
class
Some
<
T
>
implements
Option
<
T
>
{
private
final
T
obj
;
public
Some
(
T
obj
)
{
this
.
obj
=
obj
;
}
public
T
getOrElse
(
T
defObj
)
{
return
(
this
.
obj
==
null
)
?
defObj
:
this
.
obj
;
}
}
public
class
None
<
T
>
implements
Option
<
T
>
{
public
T
getOrElse
(
T
defObj
)
{
return
obj
;
}
}
Now we can use this by passing a new Some<String>("Foo")
or a new None<String>()
, which then forces the method accepting the Option<String>
to do a getOrElse
. This means that it will always check for nullity so long as the underlying object is not null
.
Generally in object-oriented programming (OOP), our functions will contain lots of functional logic. In many instances, this functionality may bleed throughout an entire class. This means that you will eventually need the class to be instantiated to test even the most basic functionality.
Instead, when we want to have a set of functionality, we should create our static
methods and then have our instance methods call those directly. This allows us to maintain function purity while being able to have OOP expressiveness. Example 10-2 shows a method on the instance that only calls a static
method.
Throughout this book, I’ve tried to convey the principles of functional programming by showing you how to start with an imperative paradigm and move to the functional one. Instead of refactoring, we’re going to put all of these principles into action by building an example from the ground up.
Over the next few pages, we’re going to work through an example of a very simplistic database. Here is a very basic overview of the functionality that we’ll be implementing; it will be a menu-driven system, so it’s not meant to be a full-fledged database. It will have the following capabilities:
We’re going to use Scala, and we’ll get started by working with the initial application object shown in Example 10-3.
import
scala.annotation.tailrec
object
fDB
{
val
options
=
Map
[
String
,CommandLineOption
](
"create"
->
new
CommandLineOption
(
"Create Table"
,
Database
.
createTable
),
"describe"
->
new
CommandLineOption
(
"Describe Table"
,
Database
.
describeTable
),
"insert"
->
new
CommandLineOption
(
"Insert Record"
,
Database
.
insert
),
"delete"
->
new
CommandLineOption
(
"Delete Record"
,
Database
.
delete
),
"select"
->
new
CommandLineOption
(
"Select Record"
,
Database
.
select
),
"exit"
->
new
CommandLineOption
(
"Exit"
,
db
=>
sys
.
exit
)
)
@tailrec
def
mainLoop
(
database
:
Database
)
:
Unit
=
mainLoop
(
CommandLine
.
optionPrompt
(
options
)
match
{
case
Some
(
opt
)
=>
opt
.
exec
(
database
)
case
_
=>
{
println
(
"Invalid option"
);
database
}
}
)
def
main
(
args
:
Array
[
String
])
=
{
mainLoop
(
new
Database
(
Map
()))
}
}
The variable options
is a mapping of all the possible options that a user can enter.
These options are create
, describe
, insert
, delete
, select
, and exit
. For each one, we’ll create a CommandLineOption
object, shown in Example 10-4, that contains a description and an executable function.
We’ll also create a mainLoop
that will execute the CommandLine
(Example 10-5), which will prompt a user based on the options available and try to grab that CommandLine
Option
.
If the option doesn’t exist, we make no changes and let the user know that she selected an invalid option. Otherwise, we will execute the CommandLineOption
executable function, which will perform the necessary changes to the Database
object, and which we will continue using in our tail-recursive function.
Finally, we have our main
function, which will call into our mainLoop
with a new blank Database
object.
In Example 10-4, you can see the basic CommandLineOption
object. It contains a name
to be used for display purposes so that the user knows what the option does, and the exec
, which will take a Database
object, perform some operation based on that object, and then return either that Database
or a new one.
class
CommandLineOption
(
val
name
:
String
,
val
exec
:
Database
=>
Database
)
The CommandLine
object in Example 10-5 performs all of our prompting and printing functionality. We have a wrapOutput
function, which will take some wrapping
and some output
that we want to display. We will then print the wrapping
, followed by the output
and the wrapping
again, so that we have a nice separation of data.
The next function is our optionPrompt
function, which takes a mapping of input to CommandLineOption
; this allows us to print out the mapping and then ask the user for the input. We will print in the format option) CommandLineOption.name
and then ask the user to give us an input.
Our last function is a generic prompt
function, which will print a message to the user and wait for her to input a line of data.
object
CommandLine
{
def
wrapOutput
(
wrapper
:
String
,
output
:
String
)
:
Unit
=
{
println
(
wrapper
)
(
output
)
println
(
wrapper
)
}
def
optionPrompt
(
options
:
Map
[
String
,CommandLineOption
])
:
Option
[
CommandLineOption
]
=
{
println
()
println
(
"----[Options]----"
)
options
.
foreach
(
option
=>
println
(
option
.
_1
+
") "
+
option
.
_2
.
name
))
options
.
get
(
prompt
(
"Action"
).
toLowerCase
)
}
def
prompt
(
msg
:
String
)
:
String
=
{
(
msg
+
": "
)
readLine
()
}
}
Our Database
class in Example 10-6 is really straightforward—it has a set of tables, which will be a map of table name
to Table
. We’ll discuss the Table
object in Example 10-7; for now, get to the Database
object (singleton).
object
Database
{
def
createTable
(
database
:
Database
)
:
Database
=
{
new
Database
(
database
.
tables
+
(
CommandLine
.
prompt
(
"Table Name"
)
->
Table
.
create
()))
}
def
describeTable
(
database
:
Database
)
:
Database
=
{
database
.
tables
.
get
(
CommandLine
.
prompt
(
"Table Name"
))
match
{
case
Some
(
table
)
=>
table
.
describe
()
case
_
=>
println
(
"Table does not exist"
)
}
database
}
def
insert
(
database
:
Database
)
:
Database
=
{
val
tableName
=
CommandLine
.
prompt
(
"Table Name"
)
database
.
tables
.
get
(
tableName
)
match
{
case
Some
(
table
)
=>
{
new
Database
(
database
.
tables
+
(
tableName
->
table
.
insert
()))
}
case
_
=>
{
println
(
"Table does not exist"
);
database
}
}
}
def
select
(
database
:
Database
)
:
Database
=
{
database
.
tables
.
get
(
CommandLine
.
prompt
(
"Table Name"
))
match
{
case
Some
(
table
)
=>
table
.
select
()
case
_
=>
println
(
"Table does not exist"
)
}
database
}
def
delete
(
database
:
Database
)
:
Database
=
{
val
tableName
=
CommandLine
.
prompt
(
"Table Name"
)
database
.
tables
.
get
(
tableName
)
match
{
case
Some
(
table
)
=>
new
Database
(
database
.
tables
+
(
tableName
->
table
.
delete
()))
case
_
=>
{
println
(
"Table does not exist"
);
database
}
}
}
}
case
class
Database
(
tables
:
Map
[
String
,Table
])
{}
Our first method, create
, will do just that—create a new table. We implement this by prompting for a table name that we will then use in our Table Name
-to-Table
mapping. We then use the function create
to create a new table. You’ll notice that we’re adding this new association to our existing table mapping and creating a new Database
object with this new table mapping.
Next, we have a describeTable
method, which allows us to print out all of the fields from a specific table. Notice that we use the pattern match with the Option pattern to get the table by name and print an error if the table doesn’t exist.
In the insert
method, we get the table and create a new database with the table replaced in the map. The important thing here is that the table we’re replacing it with will be the Table
object’s insert
return in Example 10-7.
The select
method gets the table from which the user wants to select; it then uses a pattern match on the Option pattern again to perform the select on that table or print an error if the table doesn’t exist.
Finally, the delete
method gets the table from which the user wants to delete, uses the pattern match on the Option pattern, and then passes to the table for the delete.
Our Table
class in Example 10-7 has quite a few pieces to it. Let’s look at the class first, and notice the use of the static
method calls from the instance methods.
object
Table
{
def
createFields
(
count
:
Int
,
fields
:
List
[
String
])
:
List
[
String
]
=
if
(
count
<=
0
)
{
fields
}
else
{
createFields
(
count
-
1
,
fields
:::
List
(
CommandLine
.
prompt
(
"Field"
)))
}
def
create
()
:
Table
=
new
Table
(
createFields
(
CommandLine
.
prompt
(
"Number of fields"
).
toInt
,
List
()
),
Map
(),
1
)
def
insert
(
table
:
Table
)
:
Table
=
new
Table
(
table
.
fields
,
table
.
records
+
(
table
.
id
->
Record
.
create
(
table
.
fields
,
Map
())),
table
.
id
+
1
)
def
describe
(
table
:
Table
)
:
Table
=
{
println
(
"(implied) id"
)
table
.
fields
.
foreach
(
field
=>
println
(
field
))
table
}
def
select
(
table
:
Table
)
:
Table
=
{
CommandLine
.
prompt
(
"Filter By Field? (y/n)"
).
toLowerCase
match
{
case
"y"
=>
selectWithFilter
(
table
)
case
"n"
=>
selectAll
(
table
)
case
_
=>
{
println
(
"Invalid selection"
);
select
(
table
);
}
}
}
def
selectAll
(
table
:
Table
)
:
Table
=
{
table
.
records
.
foreach
(
record
=>
record
.
_2
.
(
table
.
fields
,
record
.
_1
))
table
}
def
selectWithFilter
(
table
:
Table
)
:
Table
=
{
performFilter
(
table
,
CommandLine
.
prompt
(
"Filter Field"
),
CommandLine
.
prompt
(
"Field Value"
)
).
foreach
(
record
=>
record
.
_2
.
(
table
.
fields
,
record
.
_1
)
)
table
}
def
performFilter
(
table
:
Table
,
fieldName
:
String
,
fieldValue
:
String
)
:
Map
[
Long
,Record
]
=
{
if
(
fieldName
==
"id"
)
{
table
.
records
.
get
(
fieldValue
.
toLong
)
match
{
case
Some
(
record
)
=>
Map
(
fieldValue
.
toLong
->
record
)
case
_
=>
Map
()
}
}
else
{
table
.
records
.
filter
(
record
=>
record
.
_2
.
fieldValues
.
get
(
fieldName
)
match
{
case
Some
(
value
)
=>
value
==
fieldValue
case
_
=>
false
}
)
}
}
def
delete
(
table
:
Table
)
:
Table
=
{
new
Table
(
table
.
fields
,
table
.
records
-
CommandLine
.
prompt
(
"ID"
).
toLong
,
table
.
id
)
}
}
case
class
Table
(
fields
:
List
[
String
],
records
:
Map
[
Long
,Record
],
id
:
Long
)
{
def
delete
()
:
Table
=
{
Table
.
delete
(
this
)
}
def
select
()
:
Table
=
{
Table
.
select
(
this
)
}
def
insert
()
:
Table
=
{
Table
.
insert
(
this
)
}
def
describe
()
:
Table
=
{
Table
.
describe
(
this
)
}
}
We have a createFields
method that will create all of our fields in a tail-recursive manner. Notice that we don’t actually need to use a pattern match to do tail recursion. We also have the create
method, which asks for the number of fields and then calls into createFields
to create the list of fields.
We also have an insert
method, which will call into Record
(shown in Example 10-8) to ask for each individual field value. We then add the record to our map with the id
from the Table
and create a new table with the new map and increment the id
for the new table.
Our describe
method iterates over each field and prints it out so that we know the table structure.
Next, we have our select
method, which asks the user if she wants to filter the records for which she’s looking. Depending on which option she selects, we will go into either selectAll
or selectWithFilter
.
The selectAll
iterates over each record and calls print
. The selectWithFilter
asks the user which field she wants to filter on and the value for which she’s looking. We then call into the performFilter
method, which will return a map of only matching records and print out those records.
Our performFilter
method splits on the field. If the user asks for the id
field, we can directly access it based on the map’s key; otherwise, we will perform a filter
on the records map to find the records that match. Notice that we can pattern-match in the case that the field is missing, and instead of blowing up, we’re just not going to match.
Finally, we have the delete
method, which asks the user what id
she wants to remove, and we remove it from the records map.
Our last Record
class in Example 10-8 is fairly simple, but again let’s look at the class first.
object
Record
{
def
create
(
fields
:
List
[
String
],
fieldValues
:
Map
[
String
,String
])
:
Record
=
fields
match
{
case
List
()
=>
new
Record
(
fieldValues
)
case
f
::
fs
=>
create
(
fs
,
fieldValues
+
(
f
->
CommandLine
.
prompt
(
"Value ["
+
f
+
"]"
))
)
}
def
(
fields
:
List
[
String
],
id
:
Long
,
record
:
Record
)
:
Unit
=
{
def
_print
(
fieldList
:
List
[
String
],
output
:
String
)
:
String
=
fieldList
match
{
case
List
()
=>
output
case
f
::
fs
=>
_print
(
fs
,
output
+
f
+
": "
+
record
.
fieldValues
(
f
)
+
" "
)
}
CommandLine
.
wrapOutput
(
"------------"
,
"id: "
+
id
+
" "
+
_print
(
fields
,
""
))
}
}
case
class
Record
(
fieldValues
:
Map
[
String
,String
])
{
def
(
fields
:
List
[
String
],
id
:
Long
)
:
Unit
=
{
Record
.
(
fields
,
id
,
this
)
}
}
We have a print
method on the class itself that calls into the static print
method, passing the list and id
of the object along with itself.
In the singleton, we have two primary methods. The first is the create
method, which uses tail recursion to iterate over the field list, asking for each field input. After all fields have been asked for, we create a new Record
with the map we’ve been building through our recursive function.
The second method, print
, takes the list of fields, the id
of the record, and the record itself. We then create a nested function, _print
, which does a tail-recursive iteration to create an output string that will contain each field and value. The print
method uses the inner _print
method and passes the output to our CommandLine
object’s wrapOutput
method, which then nicely prints out the object.
We now have a small functional database that we can use for simple queries. Our database utilizes all of the concepts of functional programming—from higher-order functions to immutability.
Throughout this chapter, we covered how you can start making the transition from a legacy imperative style to a functional style of programming. We also looked at both new design patterns and extensions of existing design patterns.
Finally, we wrote a simple, functional database in Scala. In doing so, we used first-class functions and functional OOP through the use of our CommandLineOption
object. We also used pattern matching to determine whether the input option was valid. In addition, we used pure functions, recursion, and immutability throughout the application. Even when creating/updating/deleting records, we were able to apply immutability using recursion.