Hands-On Design Patterns and Best Practices with Julia

Book description

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.

Table of contents

  1. Title Page
  2. Copyright and Credits
    1. Hands-On Design Patterns and Best Practices with Julia
  3. Dedication
  4. About Packt
    1. Why subscribe?
  5. Foreword
  6. Contributors
    1. About the author
    2. About the reviewer
    3. Packt is searching for authors like you
  7. Preface
    1. Who this book is for
    2. What this book covers
    3. To get the most out of this book
      1. Download the example code files
      2. Code in Action
      3. Conventions used
    4. Get in touch
      1. Reviews
  8. Section 1: Getting Started with Design Patterns
  9. Design Patterns and Related Principles
    1. The history of design patterns
      1. The rise of design patterns
      2. More thoughts about GoF patterns
      3. How do we describe patterns in this book?
    2. Software design principles
      1. SOLID
        1. Single Responsibility Principle
        2. Open/Closed Principle
        3. Liskov Substitution Principle
        4. Interface Segregation Principle
        5. Dependency Inversion Principle
      2. DRY
      3. KISS
      4. POLA
      5. YAGNI
      6. POLP
    3. Software quality objectives
      1. Reusability
        1. Characteristics of reusable components
      2. Performance
        1. Characteristics of high-performance code
      3. Maintainability
        1. Characteristics of maintainable code
      4. Safety
        1. Characteristics of safe applications
    4. Summary
    5. Questions
  10. Section 2: Julia Fundamentals
  11. Modules, Packages, and Data Type Concepts
    1. Technical requirements
    2. The growing pains of developing applications
      1. Data science projects
      2. Enterprise applications
      3. Adapting to growth
    3. Working with namespaces, modules, and packages
      1. Understanding namespaces
      2. Creating modules and packages
        1. Defining functional behavior
        2. Exporting functions
        3. Resolving conflicts
      3. Creating submodules
      4. Organizing files in a module
    4. Managing package dependencies
      1. Understanding the semantic versioning scheme
      2. Specifying dependencies for Julia packages
      3. Avoiding circular dependencies
        1. What's the problem?
        2. How do we fix this?
    5. Designing abstract and concrete types
      1. Designing abstract types
        1. A personal asset type hierarchy example
        2. Navigating the type hierarchy
        3. Defining functions for abstract types
          1. Descriptive functions
          2. Functional behavior
          3. Interaction between objects
      2. Designing concrete types
        1. Designing composite types
        2. Immutability
        3. Mutability
        4. Mutable or immutable?
        5. Supporting multiple types using Union types
      3. Working with type operators
        1. The isa operator
        2. The <: operator
      4. Differences between abstract and concrete types
    6. Working with parametric types
      1. Working with parametric composite types
      2. Working with parametric abstract types
    7. Conversion between data types
      1. Performing simple data type conversion
      2. Beware of lossy conversions
      3. Understanding numeric type conversions
      4. Reviewing the rules for automatic conversion
        1. Case 1: Assigning a value to an array
        2. Case 2: Assigning a value to a field of an object
        3. Case 3: Constructing an object with the new function 
        4. Case 4: Assigning to a variable that has a declared type
        5. Case 5: Function has a declared return type
        6. Case 6: Passing a value to ccall
      5. Understanding the rules for function dispatches
    8. Summary
    9. Questions
  12. Designing Functions and Interfaces
    1. Technical requirements
    2. Designing functions
      1. Our use case – a space war game
      2. Defining functions
      3. Annotating function arguments
        1. Untyped arguments
        2. Typed arguments
      4. Working with optional arguments
      5. Utilizing keyword arguments
      6. Accepting variable numbers of arguments
      7. Splatting arguments 
      8. Understanding first-class functions
      9. Developing anonymous functions
      10. Using do-syntax
    3. Understanding Multiple Dispatch
      1. What is a dispatch?
      2. Matching to the narrowest types
      3. Dispatching with multiple arguments
      4. Possible ambiguities during dispatch 
      5. Detecting ambiguities
      6. Understanding dynamic dispatch
    4. Leveraging parametric methods
      1. Using type parameters
      2. Replacing abstract types with type parameters
      3. Enforcing type consistency in using parameters 
      4. Extracting type information from the method signature
    5. Working with interfaces
      1. Designing and developing interfaces
        1. Defining the Vehicle interface
        2. Implementing FighterJet 
      2. Handling soft contracts
      3. Using interface traits
    6. Summary
    7. Questions
  13. Macros and Metaprogramming Techniques
    1. Technical requirements
    2. Understanding the need for metaprogramming
      1. Measuring performance with the @time macro
      2. Unrolling loops
    3. Working with expressions
      1. Experimenting with the parser
        1. Single-variable expressions
        2. Function calls with keyword arguments
        3. Nested functions
      2. Constructing expression objects manually
      3. Playing with more complex expressions
        1. Assignment
        2. Code blocks
        3. Conditional
        4. Loop
        5. Function definition
      4. Evaluating expressions
      5. Interpolating variables in expressions
      6. Using QuoteNode for symbols
      7. Interpolating in nested expressions
    4. Developing macros
      1. What are macros?
      2. Writing our first macro
      3. Passing literal arguments
      4. Passing expression arguments
      5. Understanding the macro expansion process
        1. Timing of macro expansion
      6. Manipulating expressions
        1. Example 1 – Making a new expression
        2. Example 2 - Tweaking the abstract syntax tree
      7. Understanding macro hygiene
      8. Developing nonstandard string literals
    5. Using generated functions
      1. Defining generated functions
      2. Examining generated function arguments
    6. Summary
    7. Questions
  14. Section 3: Implementing Design Patterns
  15. Reusability Patterns
    1. Technical requirements
    2. The delegation pattern
      1. Applying the delegation pattern to a banking use case
        1. Composing a new type that contains an existing type
        2. Reducing boilerplate code for forwarding methods
      2. Reviewing some real-life examples
        1. Example 1 – the OffsetArrays.jl package
        2. Example 2 – the ScikitLearn.jl package
      3. Considerations
    3. The holy traits pattern
      1. Revisiting the personal asset management use case
      2. Implementing the holy traits pattern
        1. Defining the trait type
        2. Identifying traits
        3. Implementing trait behavior
        4. Using traits with a different type of hierarchy
      3. Reviewing some common usages
        1. Example 1 – Base.IteratorSize
        2. Example 2 – AbstractPlotting.jl ConversionTrait
      4. Using the SimpleTraits.jl package
    4. The parametric type pattern
      1. Utilizing remove text parametric type for the stock trading app
        1. Designing parametric types
        2. Designing parametric methods
        3. Using multiple parametric type arguments
      2. Real-life examples
        1. Example 1 – the ColorTypes.jl package
        2. Example 2 – the NamedDims.jl package
    5. Summary
    6. Questions
  16. Performance Patterns
    1. Technical requirements
    2. The global constant pattern
      1. Benchmarking performance with global variables
      2. Enjoying the speed of global constants
      3. Annotating variables with type information
      4. Understanding why constants help performance
      5. Passing global variables as function arguments
      6. Hiding a variable inside a global constant 
      7. Turning to some real-life examples
        1. Example 1 – SASLib.jl package
        2. Example 2 – PyCall.jl package
      8. Considerations
    3. The struct of arrays pattern
      1. Working with a business domain model
      2. Improving performance using a different data layout
        1. Constructing a struct of arrays
        2. Using the StructArrays package
        3. Understanding the space versus time trade-off
        4. Handling nested object structures
      3. Considerations
    4. The shared array pattern
      1. Introducing a risk management use case
      2. Preparing data for the example
      3. Overview of a high-performance solution
      4. Populating data in the shared array
      5. Analyzing data directly on a shared array
      6. Understanding the overhead of parallel processing
      7. Configuring system settings for shared memory usage
        1. Adjusting system kernel parameters 
        2. Configuring a shared memory device
        3. Debugging the shared memory size issue 
      8. Ensuring worker processes have access to code and data 
      9. Avoiding race conditions among parallel processes
      10. Working with the constraints of shared arrays
    5. The memoization pattern
      1. Introducing the Fibonacci function
      2. Improving the performance of the Fibonacci function
      3. Automating the construction of a memoization cache
      4. Understanding the constraint with generic functions
      5. Supporting functions that take multiple arguments
      6. Handling mutable data types in the arguments
      7. Memoizing generic functions with macros
      8. Turning to real-life examples
        1. Symata.jl
        2. Omega.jl
      9. Considerations
      10. Utilizing the Caching.jl package
    6. The barrier function pattern
      1. Identifying type-unstable functions
      2. Understanding performance impact
      3. Developing barrier functions
      4. Dealing with a type-unstable output variable
      5. Using the @inferred macro
    7. Summary
    8. Questions
  17. Maintainability Patterns
    1. Technical requirements
    2. Sub-module pattern
      1. Understanding when sub-module is needed
      2. Understanding afferent and efferent coupling
      3. Organizing sub-modules
      4. Referencing symbols and functions between modules and sub-modules
        1. Referencing symbols defined in sub-modules
        2. Referencing symbols from the parent module
      5. Removing bidirectional coupling
        1. Passing data as function arguments
        2. Factoring common code as another sub-module
      6. Considering splitting into top-level modules
      7. Understanding the counterarguments of using sub-modules
    3. Keyword definition pattern
      1. Revisiting struct definitions and constructors
      2. Using keyword arguments in constructors
      3. Simplifying code with the @kwdef macro
    4. Code generation pattern
      1. Introducing the file logger use case
      2. Code generation for function definitions
      3. Debugging code generation
      4. Considering options other than code generation
    5. Domain-specific language pattern
      1. Introducing the L-System
      2. Designing DSL for L-System
      3. Reviewing the L-System core logic
        1. Developing the LModel object
        2. Developing the state object
      4. Implementing a DSL for L-System
        1. Using the @capture macro 
        2. Matching axiom and rule statements
        3. Using the postwalk function
        4. Developing the macro for a DSL
    6. Summary
    7. Questions
  18. Robustness Patterns
    1. Technical requirements
    2. Accessor patterns
      1. Recognizing the implicit interface of an object
      2. Implementing getter functions
      3. Implementing setter functions
      4. Discouraging direct field access
    3. Property patterns
      1. Introducing the lazy file loader
      2. Understanding the dot notation for field access
      3. Implementing read access and lazy loading
      4. Controlling write access to object fields
      5. Reporting accessible fields
    4. Let block patterns
      1. Introducing the web crawler use case
      2. Using closure to hide private variables and functions away
      3. Limiting the variable scope for long scripts or functions
    5. Exception handling patterns
      1. Catching and handling exceptions
      2. Dealing with various types of exceptions
      3. Handling exceptions at the top level
      4. Walking along the stack frames
      5. Understanding the performance impact of exception handling
      6. Retrying operations
      7. Choosing nothing over exceptions
    6. Summary
    7.  Questions
  19. Miscellaneous Patterns
    1. Technical requirements
    2. Singleton type dispatch pattern
      1. Developing a command processor
      2. Understanding singleton types
      3. Using the Val parametric data type
      4. Using singleton types with dynamic dispatch
      5. Understanding the performance benefits of dispatch
    3. Stubbing/Mocking pattern
      1. What are testing doubles?
      2. Introducing the credit approval use case
      3. Performing state verification using stubs
      4. Implementing stubs with the Mocking package
      5. Applying multiple stubs to the same function
      6. Performing behavior verification using mocks
    4. Functional pipes pattern
      1. Introducing the Hacker News analysis use case
        1. Fetching top story IDs on Hacker News
        2. Fetching details about a story
        3. Calculating the average score for the top N stories
      2. Understanding functional pipes
      3. Designing composable functions
      4. Developing a functional pipe for the average score function
      5. Implementing conditional logic in functional pipes
      6. Broadcasting along functional pipes
      7. Considerations about using functional pipes
    5. Summary
    6. Questions
  20. Anti-Patterns
    1. Technical requirements
    2. Piracy anti-pattern
      1. Type I – Redefining a function
      2. Type II piracy – Extending without your own types
        1. Conflicting with another pirate
        2. Future-proofing your code
        3. Avoiding type piracy
      3. Type III piracy – Extending with your own type, but for a different purpose
    3. Narrow argument types anti-pattern
      1. Considering various options for argument types
        1. Option 1 – Vectors of Float64 values
        2. Option 2 – Vectors of instances of Number
        3. Option 3 – Vectors of type T where T is a subtype of Number
        4. Option 4 – Vectors of type S and T where S and T are subtypes of Number
        5. Option 5 – Arrays of type S and type T where S and T are subtypes of Number
        6. Option 6 – Abstract arrays
        7. Option 7 – Duck typing
        8. Summarizing all options
      2. Evaluating performance
    4. Nonconcrete field types anti-pattern
      1. Understanding the memory layout of composite data types
      2. Designing composite types with concrete types in mind
      3. Comparing performance between concrete versus nonconcrete field types
    5. Summary
    6. Questions
  21. Traditional Object-Oriented Patterns
    1. Technical requirements
    2. Creational patterns
      1. The factory method pattern
      2. The abstract factory pattern
      3. The singleton pattern
      4. The builder pattern
      5. The prototype pattern
    3. Behavioral patterns
      1. The chain-of-responsibility pattern
      2. The mediator pattern
      3. The memento pattern
      4. The observer pattern
      5. The state pattern
      6. The strategy pattern
      7. The template method pattern
      8. Command, interpreter, iterator, and visitor patterns
    4. Structural patterns
      1. The adapter pattern
      2. The composite pattern
      3. The flyweight pattern
      4. Bridge, decorator, and facade patterns
    5. Summary
    6. Questions
  22. Section 4: Advanced Topics
  23. Inheritance and Variance
    1. Technical requirements
    2. Implementing inheritance and behavior subtyping
      1. Understanding implementation inheritance
      2. Understanding behavior subtyping
      3. The square-rectangle problem
      4. The fragile base class problem
      5. Revisiting duck typing
    3. Covariance, invariance, and contravariance
      1. Understanding different kinds of variance
      2. Parametric types are invariant
      3. Method arguments are covariant
      4. Dissecting function types 
      5. Determining the variance of the function type 
      6. Implementing our own function type dispatch
    4. Parametric methods revisited
      1. Specifying type variables
      2. Matching type variables
      3. Understanding the diagonal rule
      4. An exception to the diagonal rule
      5. The availability of type variables
    5. Summary
    6. Questions
  24. Assessments
    1. Chapter 1 
    2. Chapter 2
    3. Chapter 3
    4. Chapter 4
    5. Chapter 5
    6. Chapter 6
    7. Chapter 7
    8. Chapter 8
    9. Chapter 9
    10. Chapter 10
    11. Chapter 11
    12. Chapter 12
  25. Other Books You May Enjoy
    1. Leave a review - let other readers know what you think

Product information

  • Title: Hands-On Design Patterns and Best Practices with Julia
  • Author(s): Tom Kwong
  • Release date: January 2020
  • Publisher(s): Packt Publishing
  • ISBN: 9781838648817