Hands-On Design Patterns and Best Practices with Julia - Tom Kwong - E-Book

Hands-On Design Patterns and Best Practices with Julia E-Book

Tom Kwong

0,0
36,59 €

-100%
Sammeln Sie Punkte in unserem Gutscheinprogramm und kaufen Sie E-Books und Hörbücher mit bis zu 100% Rabatt.

Mehr erfahren.
Beschreibung

Design and develop high-performance, reusable, and maintainable applications using traditional and modern Julia patterns with this comprehensive guide




Key Features



  • Explore useful design patterns along with object-oriented programming in Julia 1.0


  • Implement macros and metaprogramming techniques to make your code faster, concise, and efficient


  • Develop the skills necessary to implement design patterns for creating robust and maintainable applications



Book Description



Design patterns are fundamental techniques for developing reusable and maintainable code. They provide a set of proven solutions that allow developers to solve problems in software development quickly. This book will demonstrate how to leverage design patterns with real-world applications.






Starting with an overview of design patterns and best practices in application design, you'll learn about some of the most fundamental Julia features such as modules, data types, functions/interfaces, and metaprogramming. You'll then get to grips with the modern Julia design patterns for building large-scale applications with a focus on performance, reusability, robustness, and maintainability. The book also covers anti-patterns and how to avoid common mistakes and pitfalls in development. You'll see how traditional object-oriented patterns can be implemented differently and more effectively in Julia. Finally, you'll explore various use cases and examples, such as how expert Julia developers use design patterns in their open source packages.






By the end of this Julia programming book, you'll have learned methods to improve software design, extensibility, and reusability, and be able to use design patterns efficiently to overcome common challenges in software development.




What you will learn



  • Master the Julia language features that are key to developing large-scale software applications


  • Discover design patterns to improve overall application architecture and design


  • Develop reusable programs that are modular, extendable, performant, and easy to maintain


  • Weigh up the pros and cons of using different design patterns for use cases


  • Explore methods for transitioning from object-oriented programming to using equivalent or more advanced Julia techniques



Who this book is for



This book is for beginner to intermediate-level Julia programmers who want to enhance their skills in designing and developing large-scale applications.

Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:

EPUB

Seitenzahl: 569

Veröffentlichungsjahr: 2020

Bewertungen
0,0
0
0
0
0
0
Mehr Informationen
Mehr Informationen
Legimi prüft nicht, ob Rezensionen von Nutzern stammen, die den betreffenden Titel tatsächlich gekauft oder gelesen/gehört haben. Wir entfernen aber gefälschte Rezensionen.



Hands-On Design Patterns and Best Practices with Julia

 

 

 

 

 

Proven solutions to common problems in software design for Julia 1.x

 

 

 

 

 

 

 

 

Tom Kwong, CFA

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BIRMINGHAM - MUMBAI

Hands-On Design Patterns and Best Practices with Julia

Copyright © 2020 Packt Publishing

All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.

Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.

Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.

Commissioning Editor: Richa TripathiAcquisition Editor: Karan GuptaContent Development Editor: Tiksha SarangSenior Editor: Afshaan KhanTechnical Editor: Ketan KambleCopy Editor: Safis EditingProject Coordinator: Francy PuthiryProofreader: Safis EditingIndexer: Rekha NairProduction Designer: Nilesh Mohite

First published: January 2020

Production reference: 1170120

Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK.

ISBN 978-1-83864-881-7

www.packt.com

 
 
 
 
 
 
 
 
 
 
 
 
 
To my lovely family, Mei, Keith, and Karen.
 

Packt.com

Subscribe to our online digital library for full access to over 7,000 books and videos, as well as industry leading tools to help you plan your personal development and advance your career. For more information, please visit our website.

Why subscribe?

Spend less time learning and more time coding with practical eBooks and Videos from over 4,000 industry professionals

Improve your learning with Skill Plans built especially for you

Get a free eBook or video every month

Fully searchable for easy access to vital information

Copy and paste, print, and bookmark content

Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.packt.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at [email protected] for more details.

At www.packt.com, you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on Packt books and eBooks. 

Foreword

Design patterns are the negative space of a programming language. They are the techniques that programmers come up with to effectively leverage a language's strengths and compensate for its weaknesses. We all use them whether we mean to or not. The classic Gang of Four Design Patterns book took existing patterns that were already being used in the wild and catalogued and classified them. Perhaps even more importantly, it gave them names so that programmers could refer to common patterns easily and immediately understand each other. It gave programmers a lingua franca for the tools of their trade.

Whereas classic design pattern books have focused almost exclusively on patterns themselves, assuming language proficiency as a given, Hands-on Design Patterns and Best Practices with Julia weaves together both the positive and negative space images of Julia. It introduces the language features that patterns depend on as they are used, making the book accessible even for readers who are not already fluent Julia programmers. This approach provides a comprehensive introduction of the language, while also covering advanced subjects as the book progresses. The later chapters delve into the kinds of sophisticated design patterns used by Julia wizards, so by the time you get to the end, you will truly have mastered the language. Be forewarned, however, as with most of the best programming books, it may require more than one read through before you've fully digested the content.

One of the more interesting aspects of creating a widely used programming language is seeing the remarkable and surprising things that people do with it. This includes incredible and sometimes world-changing applications that people have built in Julia—from specifying the FAA's next generation air collision avoidance system, to mapping all the visible universe's celestial bodies, to modeling climate change with unprecedented accuracy and resolution. But it also includes the clever programming tricks that people come up with to make it do their bidding. One of my favorites is the (Tim) Holy Trait Trick, discussed in Chapter 5, Reusability Patterns, which leverages the fact that Julia can efficiently dispatch on as many arguments as we want, to work around the language's lack of multiple inheritance. Not only does this technique get the job done, it goes well beyond: traits can depend on computed properties of types, allowing them to express relationships that multiple inheritance cannot. It turns out that the language already had the expressive power that was needed, it just took a clever design pattern to unlock it.

Tom's background gives him an expertly nuanced and balanced perspective on programming languages and their design patterns. He started programming in BASIC. But since those early days, he's used a broad variety of languages in professional settings, including: C++, Java, Python, TypeScript, Scheme and—of course—Julia. The set of technological sectors he's applied these languages in are equally diverse: finance, search engine, e-commerce, content management, and currently asset management. Perhaps not coincidentally, Julia is gaining significant traction in many of these sectors, especially those which are computationally demanding. Our backgrounds shape how we see the world and sometimes you find a new tool that feels like it was made for you. Sometimes you encounter a new programming language and think This is how I've always wanted to write programs! Julia has been that language for Tom and for many others. Hopefully it will be for you as well. Whether you are just trying Julia for the first time, or have used it for years and want to level up with more advanced techniques, you will find what you're looking for in this book. Enjoy and happy coding!

Stefan Karpinski

Co-creator of the Julia programming language

Co-founder of Julia Computing, Inc.

Contributors

About the author

Tom Kwong, CFA, is an experienced software engineer with over 25 years of industry programming experience. He has spent the majority of his career in the financial services industry. His expertise includes software architecture, design, and the development of trading/risk systems. Since 2017, he has uncovered the Julia language and has worked on several open source packages, including SASLib.jl. He currently works at Western Asset Management Company, a prestige asset management company that specializes in fixed income investment services. He holds an MS degree in computer science from the University of California, Santa Barbara (from 1993), and he holds the Chartered Financial Analyst® designation since 2009.

I would like to thank my colleagues at Western Asset, especially Team Nebula, the DevOps Team, and some of the risk managers, for their encouragement and support in my enthusiasm for the Julia language. This includes Sal Kadam, Chandra Subramani, Rony Chen, Kevin Yen, Khairil Iqbal, Michael Li, Anila Kothapally, Porntawee Nantamanasikarn, Ramesh Pandey, Louie Liu, Patrick Colony, John Quan, and many others. I also want to thank Jacqueline Farrington for teaching me valuable life lessons, such as having a growth mindset and keeping myself challenged. I would like to thank the community members from the JuliaLang Slack and Discourse forum for being so kind in teaching me and enlightening me about various programming techniques in Julia. There are too many to mention here, but the first ones who come to mind are Tamas Papp, David Anthoff, Scott P. Jones, David P. Sanders, Mohamed Terek, Chris Elrod, Lyndon White (oxinabox), Cédric St. Jean, Twan Koolen, Milan Bouchet-Valat, Chris Rackauckas, Stefan Karpinski, Kristoffer Carlsson, Fredrik Ekre, and Yichao Yu.

About the reviewer

Zhuo Qingliang (also known as KDR 2 online) is presently working at paodingai.com, which is a start up FinTech company in China that is dedicated to improving the financial industry by using artificial intelligence technologies. He has over 10 years of experience in Linux, C, C++, Python, Perl, and Java development. He is interested in programming, doing consulting work, and participating in, and contributing to, the open source community (including the Julia community, of course).

 

 

 

 

 

 

 

Packt is searching for authors like you

If you're interested in becoming an author for Packt, please visit authors.packtpub.com and apply today. We have worked with thousands of developers and tech professionals, just like you, to help them share their insight with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.

Table of Contents

Title Page

Copyright and Credits

Hands-On Design Patterns and Best Practices with Julia

Dedication

About Packt

Why subscribe?

Foreword

Contributors

About the author

About the reviewer

Packt is searching for authors like you

Preface

Who this book is for

What this book covers

To get the most out of this book

Download the example code files

Code in Action

Conventions used

Get in touch

Reviews

Section 1: Getting Started with Design Patterns

Design Patterns and Related Principles

The history of design patterns

The rise of design patterns

More thoughts about GoF patterns

How do we describe patterns in this book?

Software design principles

SOLID

Single Responsibility Principle

Open/Closed Principle

Liskov Substitution Principle

Interface Segregation Principle

Dependency Inversion Principle

DRY

KISS

POLA

YAGNI

POLP

Software quality objectives

Reusability

Characteristics of reusable components

Performance

Characteristics of high-performance code

Maintainability

Characteristics of maintainable code

Safety

Characteristics of safe applications

Summary

Questions

Section 2: Julia Fundamentals

Modules, Packages, and Data Type Concepts

Technical requirements

The growing pains of developing applications

Data science projects

Enterprise applications

Adapting to growth

Working with namespaces, modules, and packages

Understanding namespaces

Creating modules and packages

Defining functional behavior

Exporting functions

Resolving conflicts

Creating submodules

Organizing files in a module

Managing package dependencies

Understanding the semantic versioning scheme

Specifying dependencies for Julia packages

Avoiding circular dependencies

What's the problem?

How do we fix this?

Designing abstract and concrete types

Designing abstract types

A personal asset type hierarchy example

Navigating the type hierarchy

Defining functions for abstract types

Descriptive functions

Functional behavior

Interaction between objects

Designing concrete types

Designing composite types

Immutability

Mutability

Mutable or immutable?

Supporting multiple types using Union types

Working with type operators

The isa operator

The <: operator

Differences between abstract and concrete types

Working with parametric types

Working with parametric composite types

Working with parametric abstract types

Conversion between data types

Performing simple data type conversion

Beware of lossy conversions

Understanding numeric type conversions

Reviewing the rules for automatic conversion

Case 1: Assigning a value to an array

Case 2: Assigning a value to a field of an object

Case 3: Constructing an object with the new function 

Case 4: Assigning to a variable that has a declared type

Case 5: Function has a declared return type

Case 6: Passing a value to ccall

Understanding the rules for function dispatches

Summary

Questions

Designing Functions and Interfaces

Technical requirements

Designing functions

Our use case – a space war game

Defining functions

Annotating function arguments

Untyped arguments

Typed arguments

Working with optional arguments

Utilizing keyword arguments

Accepting variable numbers of arguments

Splatting arguments 

Understanding first-class functions

Developing anonymous functions

Using do-syntax

Understanding Multiple Dispatch

What is a dispatch?

Matching to the narrowest types

Dispatching with multiple arguments

Possible ambiguities during dispatch 

Detecting ambiguities

Understanding dynamic dispatch

Leveraging parametric methods

Using type parameters

Replacing abstract types with type parameters

Enforcing type consistency in using parameters 

Extracting type information from the method signature

Working with interfaces

Designing and developing interfaces

Defining the Vehicle interface

Implementing FighterJet 

Handling soft contracts

Using interface traits

Summary

Questions

Macros and Metaprogramming Techniques

Technical requirements

Understanding the need for metaprogramming

Measuring performance with the @time macro

Unrolling loops

Working with expressions

Experimenting with the parser

Single-variable expressions

Function calls with keyword arguments

Nested functions

Constructing expression objects manually

Playing with more complex expressions

Assignment

Code blocks

Conditional

Loop

Function definition

Evaluating expressions

Interpolating variables in expressions

Using QuoteNode for symbols

Interpolating in nested expressions

Developing macros

What are macros?

Writing our first macro

Passing literal arguments

Passing expression arguments

Understanding the macro expansion process

Timing of macro expansion

Manipulating expressions

Example 1 – Making a new expression

Example 2 - Tweaking the abstract syntax tree

Understanding macro hygiene

Developing nonstandard string literals

Using generated functions

Defining generated functions

Examining generated function arguments

Summary

Questions

Section 3: Implementing Design Patterns

Reusability Patterns

Technical requirements

The delegation pattern

Applying the delegation pattern to a banking use case

Composing a new type that contains an existing type

Reducing boilerplate code for forwarding methods

Reviewing some real-life examples

Example 1 – the OffsetArrays.jl package

Example 2 – the ScikitLearn.jl package

Considerations

The holy traits pattern

Revisiting the personal asset management use case

Implementing the holy traits pattern

Defining the trait type

Identifying traits

Implementing trait behavior

Using traits with a different type of hierarchy

Reviewing some common usages

Example 1 – Base.IteratorSize

Example 2 – AbstractPlotting.jl ConversionTrait

Using the SimpleTraits.jl package

The parametric type pattern

Utilizing remove text parametric type for the stock trading app

Designing parametric types

Designing parametric methods

Using multiple parametric type arguments

Real-life examples

Example 1 – the ColorTypes.jl package

Example 2 – the NamedDims.jl package

Summary

Questions

Performance Patterns

Technical requirements

The global constant pattern

Benchmarking performance with global variables

Enjoying the speed of global constants

Annotating variables with type information

Understanding why constants help performance

Passing global variables as function arguments

Hiding a variable inside a global constant 

Turning to some real-life examples

Example 1 – SASLib.jl package

Example 2 – PyCall.jl package

Considerations

The struct of arrays pattern

Working with a business domain model

Improving performance using a different data layout

Constructing a struct of arrays

Using the StructArrays package

Understanding the space versus time trade-off

Handling nested object structures

Considerations

The shared array pattern

Introducing a risk management use case

Preparing data for the example

Overview of a high-performance solution

Populating data in the shared array

Analyzing data directly on a shared array

Understanding the overhead of parallel processing

Configuring system settings for shared memory usage

Adjusting system kernel parameters 

Configuring a shared memory device

Debugging the shared memory size issue 

Ensuring worker processes have access to code and data 

Avoiding race conditions among parallel processes

Working with the constraints of shared arrays

The memoization pattern

Introducing the Fibonacci function

Improving the performance of the Fibonacci function

Automating the construction of a memoization cache

Understanding the constraint with generic functions

Supporting functions that take multiple arguments

Handling mutable data types in the arguments

Memoizing generic functions with macros

Turning to real-life examples

Symata.jl

Omega.jl

Considerations

Utilizing the Caching.jl package

The barrier function pattern

Identifying type-unstable functions

Understanding performance impact

Developing barrier functions

Dealing with a type-unstable output variable

Using the @inferred macro

Summary

Questions

Maintainability Patterns

Technical requirements

Sub-module pattern

Understanding when sub-module is needed

Understanding afferent and efferent coupling

Organizing sub-modules

Referencing symbols and functions between modules and sub-modules

Referencing symbols defined in sub-modules

Referencing symbols from the parent module

Removing bidirectional coupling

Passing data as function arguments

Factoring common code as another sub-module

Considering splitting into top-level modules

Understanding the counterarguments of using sub-modules

Keyword definition pattern

Revisiting struct definitions and constructors

Using keyword arguments in constructors

Simplifying code with the @kwdef macro

Code generation pattern

Introducing the file logger use case

Code generation for function definitions

Debugging code generation

Considering options other than code generation

Domain-specific language pattern

Introducing the L-System

Designing DSL for L-System

Reviewing the L-System core logic

Developing the LModel object

Developing the state object

Implementing a DSL for L-System

Using the @capture macro 

Matching axiom and rule statements

Using the postwalk function

Developing the macro for a DSL

Summary

Questions

Robustness Patterns

Technical requirements

Accessor patterns

Recognizing the implicit interface of an object

Implementing getter functions

Implementing setter functions

Discouraging direct field access

Property patterns

Introducing the lazy file loader

Understanding the dot notation for field access

Implementing read access and lazy loading

Controlling write access to object fields

Reporting accessible fields

Let block patterns

Introducing the web crawler use case

Using closure to hide private variables and functions away

Limiting the variable scope for long scripts or functions

Exception handling patterns

Catching and handling exceptions

Dealing with various types of exceptions

Handling exceptions at the top level

Walking along the stack frames

Understanding the performance impact of exception handling

Retrying operations

Choosing nothing over exceptions

Summary

 Questions

Miscellaneous Patterns

Technical requirements

Singleton type dispatch pattern

Developing a command processor

Understanding singleton types

Using the Val parametric data type

Using singleton types with dynamic dispatch

Understanding the performance benefits of dispatch

Stubbing/Mocking pattern

What are testing doubles?

Introducing the credit approval use case

Performing state verification using stubs

Implementing stubs with the Mocking package

Applying multiple stubs to the same function

Performing behavior verification using mocks

Functional pipes pattern

Introducing the Hacker News analysis use case

Fetching top story IDs on Hacker News

Fetching details about a story

Calculating the average score for the top N stories

Understanding functional pipes

Designing composable functions

Developing a functional pipe for the average score function

Implementing conditional logic in functional pipes

Broadcasting along functional pipes

Considerations about using functional pipes

Summary

Questions

Anti-Patterns

Technical requirements

Piracy anti-pattern

Type I – Redefining a function

Type II piracy – Extending without your own types

Conflicting with another pirate

Future-proofing your code

Avoiding type piracy

Type III piracy – Extending with your own type, but for a different purpose

Narrow argument types anti-pattern

Considering various options for argument types

Option 1 – Vectors of Float64 values

Option 2 – Vectors of instances of Number

Option 3 – Vectors of type T where T is a subtype of Number

Option 4 – Vectors of type S and T where S and T are subtypes of Number

Option 5 – Arrays of type S and type T where S and T are subtypes of Number

Option 6 – Abstract arrays

Option 7 – Duck typing

Summarizing all options

Evaluating performance

Nonconcrete field types anti-pattern

Understanding the memory layout of composite data types

Designing composite types with concrete types in mind

Comparing performance between concrete versus nonconcrete field types

Summary

Questions

Traditional Object-Oriented Patterns

Technical requirements

Creational patterns

The factory method pattern

The abstract factory pattern

The singleton pattern

The builder pattern

The prototype pattern

Behavioral patterns

The chain-of-responsibility pattern

The mediator pattern

The memento pattern

The observer pattern

The state pattern

The strategy pattern

The template method pattern

Command, interpreter, iterator, and visitor patterns

Structural patterns

The adapter pattern

The composite pattern

The flyweight pattern

Bridge, decorator, and facade patterns

Summary

Questions

Section 4: Advanced Topics

Inheritance and Variance

Technical requirements

Implementing inheritance and behavior subtyping

Understanding implementation inheritance

Understanding behavior subtyping

The square-rectangle problem

The fragile base class problem

Revisiting duck typing

Covariance, invariance, and contravariance

Understanding different kinds of variance

Parametric types are invariant

Method arguments are covariant

Dissecting function types 

Determining the variance of the function type 

Implementing our own function type dispatch

Parametric methods revisited

Specifying type variables

Matching type variables

Understanding the diagonal rule

An exception to the diagonal rule

The availability of type variables

Summary

Questions

Assessments

Chapter 1 

Chapter 2

Chapter 3

Chapter 4

Chapter 5

Chapter 6

Chapter 7

Chapter 8

Chapter 9

Chapter 10

Chapter 11

Chapter 12

Other Books You May Enjoy

Leave a review - let other readers know what you think

Preface

Julia is a powerful programming language that is designed to enable high-performance applications with developer productivity in mind. Its dynamic nature allows you to quickly perform small-scale experiments and then migrate to larger applications. Its introspection tools allow us to optimize performance by analyzing how high-level code is translated into lower-level instructions and machine code. Its metaprogramming facility helps more advanced programmers to build custom syntax for their specific domain usage. Its multiple dispatch and generic function features make it easy to build new capabilities by extending existing functions. For these and many more reasons, Julia is an excellent tool for developing applications across a wide spectrum of industries.

This book fulfills several demands from Julia developers. A desire to write better code. A desire to improve system performance. A desire to design software that is easy to maintain. From the time that the Julia language was born to its magnificent milestone of version 1.0 in August 2018, many design patterns have already emerged from the brightest minds, ranging from the core developers of the language to heavy users of the language. At times, these patterns were presented in blog posts and conferences. Sometimes, they showed up in random discussion threads on the Julia Discourse forum. At other times, they come up in casual talks between community members on various Julia Slack channels. This book is a collection of patterns, documenting the best approaches to designing high-quality Julia applications.

The primary objective of this book is to organize these well-proven patterns into a format that is easily consumable by the Julia developer community. There are several benefits to organizing and naming these patterns:

It allows developers to communicate with each other more easily.

It allows developers to better understand code that uses these patterns.

It allows developers to articulate when a pattern should be applied.

The goal of this book is simple but powerful – after reading this book, you should be more knowledgeable about how to design and develop software in Julia. In addition, the material presented in this book can serve as a reference for any future discussions regarding design patterns in Julia. As we know from history, new design patterns will continue to emerge alongside the continuous evolution of the Julia language.

I hope you enjoy this book. Happy reading!

Who this book is for

This book is for beginner-to-intermediate Julia developers who want to get better at writing idiomatic Julia code for larger applications. It is not an introductory book, so you are expected to have some basic programming knowledge. If you are familiar with the object-oriented programming paradigm, then you may find this book helpful where it shows how the same problem can be solved differently, and often in a better way, in Julia.

Many of the patterns described in this book are applicable to any industry domain and use cases. Whether you are a data scientist, researcher, system programmer, or enterprise application developer, you should be able to benefit from using these patterns in your projects.

What this book covers

Chapter 1, Design Patterns and Related Principles, introduces the history of design patterns and how they are useful for developing applications. It covers several industry-standard software design principles that are applicable across any programming language and paradigm.

Chapter 2, Modules, Packages, and Data Type Concepts, discusses how larger programs can be organized and how dependencies can be managed. Then, it explains how to develop new data types and express their relationship in a custom type hierarchy.

Chapter 3, Designing Functions and Interfaces, explains how functions are defined and how multiple dispatch come into play. It also discusses parametric method and interfaces, for which different functions can work with each other properly based on a pre-determined contract.

Chapter 4, Macros and Metaprogramming Techniques, introduces macro programming facility and how it can be used to transform source code into a different form. It describes several techniques for developing and debugging macros more effectively.

Chapter 5, Reusability Patterns, covers design patterns that relate to code reuse. This includes the Delegation pattern for reusing code via composition, the Holy Traits pattern for a more formal interface contract, and the Parametric Type pattern for creating new types from a parameterized data structure.

Chapter 6, Performance Patterns, covers design patterns that relate to improving system performance. This includes the Global Constant pattern for better type stability, the Memoization pattern for caching prior computation results, the Struct of Arrays pattern for rearranging data for a more optimal layout, the Shared Array pattern for optimizing memory usage with parallel computing, and the Barrier Function pattern for improving performance via function specialization.

Chapter 7, Maintainability Patterns, covers design patterns about code maintainability. This includes the Sub Module pattern for better organization of larger code bases, the Keyword Definition pattern for creating data types that can be constructed more easily, the Code Generation pattern for defining many similar functions with less code, and the Domain-Specific Language pattern for creating a new syntax for a specific domain.

Chapter 8, Robustness Patterns, covers design patterns that help you write safer code. This include the Accessor pattern for providing standard access to fields, the Property pattern for controlling access to fields, the Let-Block pattern for limiting the variable scope, and the Exception Handling pattern for handling errors.

Chapter 9, Miscellaneous Patterns, covers several design patterns that do not fit into the preceding categories. It includes the Singleton Type pattern for use with dynamic dispatch, the Mocking pattern for building isolated tests, and the Functional Pipe pattern for building linear data processing pipelines.

Chapter 10, Anti-Patterns, covers patterns that should be avoided. The main anti-pattern is Piracy, which involves defining or extending functions for data types that you do not own. Then, it covers the Narrow Arguments and Non-Concrete Type Fields patterns, which hinder system performance.

Chapter 11, Traditional Object-Oriented Patterns, covers the traditional object-oriented patterns described in the Design Patterns book by Gang-of-Four. It discusses how those patterns can be simplified or implemented differently in Julia.

Chapter 12, Inheritance and Variance, discusses how Julia supports inheritance and why it is designed as such since its approach is quite different from mainstream object-oriented programming languages. Then, it covers the topic of type variance, an important concept in terms of subtyping relationships between data types used by multiple dispatch.

To get the most out of this book

You should download the latest version of Julia from the Julia Language website (https://julialang.org/).

The code samples are available on GitHub, as described in the Technical requirements section of each chapter. At the time of writing, the code has been tested with Julia version 1.3.0. To download the code samples, clone the project from GitHub as follows:

You are encouraged to run and experiment with the code samples accompanying this book. The code samples are typically stored in one of the following formats:

Code snippets in a Julia source file. These snippets can be copied and pasted into the REPL.

Code residing in a package directory. The package can be instantiated as follows:

For example, in Chapter 5, Reusability Patterns, the content is listed as follows:

To use the code for DelegationPattern, just start a Julia REPL in that folder with the --project=. command-line argument:

Then, go to the package mode and instantiate the package by entering the ] instantiate command:

After that, you can use the package as usual:

If there is a test directory, then you can read and run the test scripts provided.

Download the example code files

You can download the example code files for this book from your account at www.packt.com. If you purchased this book elsewhere, you can visit www.packtpub.com/support and register to have the files emailed directly to you.

You can download the code files by following these steps:

Log in or register at

www.packt.com

.

Select the

Support

tab.

Click on

Code Downloads

.

Enter the name of the book in the

Search

box and follow the onscreen instructions.

Once the file is downloaded, please make sure that you unzip or extract the folder using the latest version of:

WinRAR/7-Zip for Windows

Zipeg/iZip/UnRarX for Mac

7-Zip/PeaZip for Linux

The code bundle for the book is also hosted on GitHub at https://github.com/PacktPublishing/Hands-on-Design-Patterns-and-Best-Practices-with-Julia. In case there's an update to the code, it will be updated on the existing GitHub repository.

We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!

Code in Action

Visit the following link for the Code in Action videos:

http://bit.ly/36Z4oXs

Conventions used

There are a number of text conventions used throughout this book.

CodeInText: Indicates code words in text such as variable names, function names, data types, etc. For example, "The format function takes a formatter and a numeric value, x, and returns a formatted string."

A block of code is set as follows:

abstract type Formatter endstruct IntegerFormatter <: Formatter endstruct FloatFormatter <: Formatter end

Any experiment or output from the REPL is presented as screenshots:

Bold: Indicates an important word or concept. For example, "The Bridge pattern is used to decouple an abstraction from its implementation so that it can evolve independently."

Italics: Emphasizes a new concept that will be explained later in the text. For example, "The cases presented in previous chapters include various situations that we can solve by writing idiomatic Julia code."

Important notes appear like this.
Tips and tricks appear like this.

Get in touch

Feedback from our readers is always welcome.

General feedback: If you have questions about any aspect of this book, mention the book title in the subject of your message and email us at [email protected].

Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/support/errata, selecting your book, clicking on the Errata Submission Form link, and entering the details.

Piracy: If you come across any illegal copies of our works in any form on the Internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material.

If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.

Reviews

Please leave a review. Once you have read and used this book, why not leave a review on the site that you purchased it from? Potential readers can then see and use your unbiased opinion to make purchase decisions, we at Packt can understand what you think about our products, and our authors can see your feedback on their book. Thank you!

For more information about Packt, please visit packt.com.

Section 1: Getting Started with Design Patterns

The aim of this section is to introduce you to how design patterns are used in general and how Julia is different from the object-oriented programming paradigm.

This section contains the following chapter:

Chapter 1

Design Patterns and Related Principles

Design Patterns and Related Principles

Nowadays, learning and applying design patterns is an important aspect of software engineering. Design patterns are like water – you can't live without them. Don't believe me? Just ask hiring managers, and you will find that many of them have design patterns in their job postings as well as related questions in job interviews. It is a common belief that design patterns are important ingredients for software development and everyone should know them.

In this chapter, we will provide some context about why design patterns are useful and how they have served us well in the past few decades. By understanding the motivation behind design patterns, we will be able to set forth a set of guiding principles for developing software. The following topics will be discussed in this chapter:

The history of design patterns 

Software design principles

Software quality objectives 

Let's get started!

The history of design patterns

Design patterns is not a new concept to computer programmers. Since personal computers became more affordable and popular in the 1980s, the programming profession flourished and a lot of code was written for a variety of applications.

I remember that, when I was 14 years old, learning the GOTO statement for a BASIC program was one of the coolest things. It literally allowed me to take a control flow to a different part of the code at any time. Perhaps not too surprisingly, when I learned about structured programming and the Pascal language in college, I started to realize how GOTO statements produce messy spaghetti code. Using GOTO for branching purposes is a pattern. It's just a bad one because it makes code difficult to understand, follow, and debug. In today's lingua franca, we call them anti-patterns. When it comes to structured programming techniques, organizing code in small functions is a pattern as well, one that has been taught as a mainstream subject in programming courses. 

When I graduated from college, I started my programming career and spent plenty of time hacking away. I had the opportunity to do various kinds of research and find out how systems are designed. For example, I realized that the Unix operating system has a beautiful design. That is because it consists of many small programs, which individually do not have a ton of functionality, but you can compose them in any number of ways to solve more complex problems. I was also fond of the Scheme programming language, which came out of MIT's AI Lab. The simplicity and versatility of the language still amazes me today. Scheme's heritage can be traced to Lisp, which had some influence on how the Julia language was designed.

The rise of design patterns

In 1994, while I was diving deep into C++ and distributed computing for a financial application, four software professionals, also known as the Gang of Four or GoF, came together and published a book about design patterns, and it took the object-oriented programming community by storm. The group collected and classified 23 design patterns that were commonly utilized when developing large-scale systems. They also chose to explain the concepts using Unified Modeling Language (UML) and C++ and Smalltalk.

For the first time, a set of design patterns had been collected, organized, explained, and widely distributed to software developers. Perhaps one of the most significant decisions by the group was to organize these patterns in a highly structured and easily consumable format. Since then, programmers have been able to communicate with each other easily about how they design their software. In addition, they can visually present software design with a universal notation. When one person talks about the Singleton pattern, another person can immediately understand and even visualize in his/her mind how that component works. Isn't that convenient?

Even more surprisingly, design patterns suddenly became the gospel when it come to building good software. In some ways, using them was even perceived as the only way to write good software. GoF patterns were so widely preached across the development community that many people abused them and used them everywhere without good reason. The problem is – When all you have is a hammer, everything looks like a nail! Not everything can be solved or should be solved by the same patterns. When design patterns are overused or misused, the code becomes more abstract, more complicated, and more difficult to manage.

So, what have we learned from the past? We recognize that every abstraction comes with a cost. Every design pattern comes with its own pros and cons. One of the main objectives of this book is to discuss not just the how but also the why and why not, and under what circumstances a pattern should be used or not used. We, as software professionals, will then be equipped with the information we need to make good judgment calls about when to apply these patterns.

More thoughts about GoF patterns

GoF design patterns are classified into three main categories:

Creational patterns

: These cover how to construct objects in various ways. Since object-oriented programming brings together data and behavior, and a class may inherit the structure and behavior of an ancestor class, there are some complexities involved when building a large application. Creational patterns help standardize object creation methods in various situations.

Structural patterns

:

 These cover how objects can be extended or composed to make bigger thing. The purpose of these patterns is to allow software components to be reused or replaced more easily.

Behavioral patterns

:

 This cover how objects can be designed to perform separate tasks and communicate with each other. A large application can be decomposed into independent components and the code becomes easier to maintain. The o

bject-oriented programming paradigm requires solid interaction between objects. The purpose of these patterns is to make software components more flexible and more convenient for collaboration with each other. 

One school of thought is that design patterns are created to address limitations in their respective programming language. Two years after the GoF book was published, Peter Norvig published research showing that 16 of the 23 design patterns are either unnecessary or can be simplified in a dynamic programming language such as Lisp.

This is not an unimportant observation. In the context of object-oriented programming, additional abstraction from a class hierarchy requires the software designer to think about how objects are instantiated and interact with each other. In a strong, statically typed language such as Java, it is even more necessary to reason about the behavior and interaction of objects. In Chapter 11, Traditional Object-Oriented Patterns, we will circle back to this topic and discuss how Julia works differently compared to object-oriented programming.

For now, we will start with the basics and review some software design principles. These principles are like the North star, guiding us as we build applications.

How do we describe patterns in this book?

If you are new to Julia programming, this book will help you understand how to write more idiomatic Julia code. We will also focus on describing some of the most useful patterns that are already used in the existing open source Julia ecosystem. That includes Julia's own Base andstdlibpackages as the Julia runtime is largely written in Julia itself. We will also reference other packages that are used for numerical computing and web programming. 

For ease of reference, we will organize our patterns by name. For example, the Holy Traits pattern refers to a specific method for implementing traits. The Domain-Specific Language pattern talks about how to build new syntax to represent specific domain concepts. The sole purpose of having a name is just ease of reference.

When we discuss these design patterns in this book, we will try to understand the motivation behind them. What specific problem are we trying to solve? What would be a real-world situation where such a pattern would be useful? Then, we will get into the details of how to solve these problems. Sometimes, there may be several ways to solve the same problem, in which case we will look into each possible solution and discuss the pros and cons.

Having said that, it is important for us to understand the ultimate goal of using design patterns. Why do we want to use design patterns in the first place? To answer this question, it would be useful for us to first understand some key software design principles.

Software design principles

While this book does not cover object-oriented programming, some object-oriented design principles are universal and could be applied to any programming language and paradigm. Here, we will take a look at some of the most well-known design principles. In particular, we will cover the following:

SOLID

: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion

DRY

: Don't Repeat Yourself

KISS

: Keep It Simple, Stupid!

POLA

: Principle of Least Astonishment

YAGNI

: You Aren't Gonna Need It

POLP

: Principle of Least Privilege

Let's start with SOLID.

SOLID

The SOLID principle consists of the following:

S

: Single Responsibility Principle

O

: Open/Closed Principle

L

: Liskov Substitution Principle

I

: Interface Segregation Principle

D

: Dependency Inversion Principle

Let's understand each concept in detail.

Single Responsibility Principle

The Single Responsibility Principle states that every module, class, and function should be responsible for a single functional objective. There should be only one reason to make any changes.

The benefits of this principle are listed here: 

The programmer can focus on a single context during development.

The size of each component is smaller.

The code is easier to understand.

The code can be tested more easily.

Open/Closed Principle

The Open/Closed Principle states that every module should be open for extension but closed for modification. It is necessary to distinguish between enhancement and extension—enhancement refers to a core improvement of the existing module, while an extension is considered an add-on that provides additional functionality.

The following are the benefits of this principle: 

Existing components can be easily reused to derive new functionalities.

Components are loosely coupled so it is easier to replace them without affecting the existing functionality.

Liskov Substitution Principle

The Liskov Substitution Principle states that a program that accepts type T can also accept type S (which is a subtype of T), without any change in behavior or intended outcome.

The following are the benefits of this principle: 

A function can be reused for any subtype passed in the arguments.

Interface Segregation Principle

The Interface Segregation Principle states that a client should not be forced to implement interfaces that it does not need to use.

The following are the benefits of this principle: 

Software components are more modular and reusable.

New implementations can be created more easily.

Dependency Inversion Principle

The Dependency Inversion Principle states that high-level classes should not depend on low-level classes; instead, high-level classes should depend on an abstraction that low-level classes implement.

The following are the benefits of this principle: 

Components are more decoupled.

The system becomes more flexible and can adapt to changes more easily. 

Low-level components can be replaced without affecting high-level components.

DRY

We'll now cover the DRY principle:

D

: Don't

R

: Repeat

Y

: Yourself

This acronym is a good way of reminding programmers that duplicate code is bad. It is obvious that duplicate code can be difficult to maintain—whenever logic is changed, multiple places in the code are affected.

What do we do when duplicate code is found? Eliminate it and create a common function that is reusable from multiple source files. 

In addition, sometimes code is not 100% duplicated but instead is 90% similar. That is not an uncommon scenario. In that case, consider redesigning the relevant components, possibly refactoring code to a common interface.

KISS

Let's talk about the KISS principle:

K

: Keep

I

: It

S

: Simple

S

: Stupid!

Often, when we design software, we like to think ahead and try to deal with all kinds of future scenarios. The trouble with building such future-proof software is that it takes exponentially more effort to design and code properly. Practically speaking, it's a conundrum—there is no 100% future-proof solution because technology changes, business changes, and people change. Also, over-engineering could lead to excessive abstraction and indirection, making a system more difficult to test and maintain.

In addition, when using Agile software development methods, we value faster and high-quality delivery over perfection or excess engineering. Keeping the design and code simple is a virtue that every programmer should keep in mind.

POLA

Let's look at the POLA principle:

P

: Principle

O

: Of

L

: Least

A

: Astonishment

POLA states that a software component should be easy to understand and its behavior should never be a surprise (or, more accurately, astonishing) to the client. How do we do that?

The following are some things to keep in mind:

Make sure that the names of the module, function, or function arguments are clear and unambiguous.

Ensure that modules are right-sized and well maintained.

Ensure that interfaces are small and easy to understand.

Ensure that functions have few positional arguments. 

YAGNI

Let's move on to the YAGNI principle:

Y

: You

A

: Aren't

G

: Gonna

N

: Need

I

: It

YAGNI says you should only develop software that is needed today. This principle came from Extreme Programming (XP). See what Ron Jeffries, co-founder of XP, wrote in his blog:

"Always implement things when you actually need them, never when you just foresee that you need them."

Software engineers are sometimes tempted to develop functionality that they feel the customer will need in the future. It's been proven time and time again that this is not the most effective way to develop software. Consider the following scenarios:

The functionality is never needed by the customer and so the code is never used.

The business environment changes and the system has to be redesigned or replaced.

The technology changes and the system has to be upgraded to use a new library, a new framework, or a new language. 

The cheapest software is the one that you didn't write. You aren't gonna need it!

POLP

Now, for POLP:

P

: Principle

O

: Of

L

: Least 

P

: Privilege

POLP states that a client must be given access only to the information or functions that they need. POLP is one of the most important pillars for building secure applications, and it is widely adopted by cloud infrastructure vendors such as Amazon, Microsoft, and Google.

There are quite a few benefits when POLP is applied:

Sensitive data is protected and not exposed to non-privileged users.

The system can be tested more easily since the number of use cases is limited.

The system becomes less prone to misuse because only limited access is given and the interface is simpler.

The software design principles that we have learned about so far are great tools. Although SOLID, DRY, KISS, POLA, YAGNI, and POLP seem to be just a bunch of acronyms, they are useful in designing better software. While SOLID principles came from the object-oriented programming paradigm, SOLID's concepts can still be applied to other languages and environments. As we work through the rest of the chapters in this book, I would encourage you to keep them in mind.

In the next section, we will go over several software quality objectives when designing software.

Software quality objectives

Everyone likes beautiful design. I do, too. But, the use of design patterns is not just to make something look good. Everything we do should have a purpose.

The GoF classified object-oriented design patterns as creational, structural, and behavioral. For Julia, let's take a different perspective and classify our patterns by their respective software quality objectives as follows:

Reusability

Performance

Maintenance

Safety

Let's understand each of these in the following sections.

Reusability

People often talk about top-down and bottom-up approaches when designing software.

The top-down approach starts with a large problem and breaks it down into a set of smaller problems. Then, if the problems are not small enough, as discussed when we looked at the Single Responsibility Principle, we further break down the problem into even smaller ones. The process repeats and eventually the problem is small enough to design and code.

The bottom-up approach works in the opposite direction. Given domain knowledge, you can start creating building blocks, and then create more complex ones by composing from these building blocks.

Regardless of how it is done, eventually there will be a set of components that work with each other, thereby forming the basis of the application. 

I like the metaphor. Even a 5-year old child can build a variety of structures using just several kinds of Lego block. Imagination is the limit. Do you ever wonder why it is so powerful? Well, if you recall, every Lego block has a standard set of connectors: one, two, four, six, eight, or more. Using these connectors, each block can plug into another block easily. When you create a new structure, you can combine it with other structures to create even larger, more complex structures.

When building applications, the key design principle is to create pluggable interfaces so every component can be reused easily.

Characteristics of reusable components

The following are important characteristics of reusable components:

Each component serves a single purpose (the S in SOLID).

Each component is well defined and ready for reuse (the O in SOLID).

An abstract type hierarchy is designed for parent-child relationships (the L in SOLID).

Interfaces are defined as a small set of functions (the I in SOLID).

Interfaces are used to bridge between components (the D in SOLID).

Modules and functions are designed with simplicity in mind (KISS).

Reusability is important because it means we can avoid duplicated code and wasted effort. The less code we write, the less work we need to do to maintain software. That includes not just the development effort but also the time testing, packaging, and upgrading. Reusability is also one of the reasons why open source software is so successful. In particular, the Julia ecosystem contains many open source packages and they tend to borrow functionalities from each other.

Next, we will discuss another software quality objective—performance.

Performance

The Julia language is designed for high-performance computing. It does not come for free, however. When it comes to performance, it takes practice to write code that is more compiler-friendly, thus making it more likely to translate the program into optimized machine code.

For the past few decades, computers have seemed to become faster and faster every year. What used to be performance bottlenecks are more easily solved using today's hardware. At the same time, we are also facing more challenges due to the explosion of data. A good example is the field of big data and data science. As the amount of data grows, we need even more computing power to handle these new use cases.

Unfortunately, the speed of computers has not grown as rapidly as it did in the past. Moore's Law states that the number of transistors on a microchip doubles roughly every 18 months, and since 1960 it has been correlated with the growth in CPU speed. However, it is well known that Moore's Law will no longer be applicable soon due to a physical limitation: the number of transistors that can be fitted to a chip and the precision of the fabrication process.

In order to address today's computational needs, especially in the world of artificial intelligence, machine learning, and data science, practitioners have been gearing toward a scale-out strategy that utilizes multiple CPU cores across many servers, and looking at exploiting the efficiency of GPUs and TPUs.

Characteristics of high-performance code

The following are characteristics of high-performance code:

Functions are small and can be optimized easily (S in SOLID).

Functions contains simple logic rather than complex logic (KISS).

Numeric data is laid out in contiguous memory space so the compiler can fully utilize CPU hardware.

Memory allocation should be kept to a minimum to avoid excessive garbage collection.

Performance is an important aspect of any software project. It is particularly important for data science, machine learning, and scientific computing use cases. A small design change can make a big difference—depending on the situation, it could possibly turn a 24-hour process into a 30-minute process. It could also give users real-time experience when using a web application rather than a please wait... dialog.

 

Next, we will discuss software maintainability as another software quality objective.

Maintainability

Software can be maintained more easily when it is designed properly. Generally speaking, if you are able to effectively use the design principles listed previously (SOLID, KISS, DRY, POLA, YAGNI, and POLP), then your application is more likely to be well architected and designed for long-term maintenance.

Maintainability is an important ingredient for large-scale applications. A research project from graduate school may not last long. On the contrary, an enterprise application may last for decades. Recently, I heard from a colleague that COBOL is still in use and COBOL programmers are still making a good living.

We often hear about technical debt. Similar to monetary debt in real life, technical debt is something that you must pay for whenever code is changed. And the longer the technical debt stays in place, the more effort you have to spend. 

To understand why, consider a module that is bloated with duplicate code or unnecessary dependencies. Whenever a new functionality is added, you have to update multiple parts of the source code, and you have to perform regression testing for a larger area of the system. So, you end up paying (in terms of programming time and effort) for the debt every time the code is changed until the debt is fully repaid (that is, until the code is fully refactored).

Characteristics of maintainable code

The following are characteristics of maintainable code:

No unused code (YAGNI).

No duplicate code (DRY).

Code is concise and short (KISS).

Code is clear and easy to understand (KISS).

Every function has a single purpose (the S in SOLID).

Every module contains functions that relate to and work with each other (the S in SOLID).

Maintainability is an important aspect of any application. When designed properly, even large applications can be changed frequently and easily without fear. Applications can also last a long time, reducing the cost of the software.

Next, we will discuss software safety as another quality objective.

Safety

"Safety—the condition of being safe from undergoing or causing hurt, injury, or loss."
– Merriam-Webster Dictionary

Applications are expected to function correctly. When an application malfunctions, there could be undesired consequences and some of those could be fatal. Consider a mission-critical rocket-launch subsystem used by NASA. A single defect could cause the launch to be delayed; or, in the worst-case scenario, it could cause the rocket to explode in mid-air.

Programming languages are designed to allow flexibility but at the same time provide safety features so software engineers can make fewer mistakes. For example, the compiler's static type checking ensures that the correct types are passed to functions that expect those types. In addition, most computer programs operate on data, and as we know, data is not always clean or available. Hence, the ability to handle bad or missing data is an important software quality. 

Characteristics of safe applications

Some characteristics of safe applications follow:

Each module exposes a minimum set of types, functions, and variables.

Each function is called with arguments such that the respective types implement the expected behavior of the function (the L in SOLID; POLA).

The return value of a function is clear and documented (POLA).

Missing data is handled properly (POLA).

Variables are limited to the smallest scope.

Exceptions are caught and handled accordingly.

Safety is one of the most important objectives here. An erroneous application can cause major disasters. It can even cost a company millions of dollars. In 2010, Toyota recalled over 400,000 of its Prius hybrid cars due to a software defect with the Anti-lock Braking System (ABS). In 1996, the Ariane 5 rocket launched by the European Space Agency exploded just 40 seconds after launch. Of course, these are only a few more extreme examples. By utilizing best practices, we can avoid getting into these kinds of embarrassing and costly incidents.

Now, we understand the importance of software design principles and software quality objectives.

Summary

In this chapter, we started by going back in time and reviewing the history of design patterns. We discussed why design patterns can be useful for software professionals and how we would like to organize design patterns in this book given what we have learned in the past.