0% found this document useful (0 votes)
178 views

Declarative Style in CPP Ben Deane Cppcon 2018

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
178 views

Declarative Style in CPP Ben Deane Cppcon 2018

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 129

EASY TO USE, HARD TO MISUSE

DECLARATIVE STYLE IN C++

BEN DEANE / @ben_deane


CPPCON / FRIDAY SEPTEMBER 28TH, 2018

1
IN THIS TALK
1. Definitions & motivation
2. Where we came from
3. Where we are
4. Where we could be headed

2.1
WHAT DO WE MEAN?

"A programming paradigm … that expresses the logic of a


computation without describing its control ow."

– Declarative programming page on Wikipedia

"Often it involves the separation of 'facts' from operations on the


facts." "… generalizes the pure functional model."

– wiki.c2.com

3.1
DECLARATIVE STYLE INDICATORS

3.2
DECLARATIVE STYLE INDICATORS

referential transparency

3.2
DECLARATIVE STYLE INDICATORS

referential transparency
say WHAT in preference to HOW

3.2
DECLARATIVE STYLE INDICATORS

referential transparency
say WHAT in preference to HOW
minimize imperative style

3.2
DECLARATIVE STYLE INDICATORS

referential transparency
say WHAT in preference to HOW
minimize imperative style
declaring things

3.2
DECLARATIVE STYLE INDICATORS

referential transparency
say WHAT in preference to HOW
minimize imperative style
declaring things
expressions over statements

3.2
EXPRESSIONS VS STATEMENTS

4.1
EXPRESSIONS

"An expression is a sequence of operators and operands that specifies a computation.


An expression can result in a value and can cause side effects." [expr.pre] § 1

Properties of expressions:

4.2
EXPRESSIONS

"An expression is a sequence of operators and operands that specifies a computation.


An expression can result in a value and can cause side effects." [expr.pre] § 1

Properties of expressions:
value category

4.2
EXPRESSIONS

"An expression is a sequence of operators and operands that specifies a computation.


An expression can result in a value and can cause side effects." [expr.pre] § 1

Properties of expressions:
value category
type

4.2
EXPRESSIONS COMPOSE ON MULTIPLE AXES

auto expr = a @ b @ c;

Consider this snippet.


(@ stands for any operator)

4.3
STATEMENTS

"Except as indicated, statements are executed in sequence." [stmt.stmt] § 1

Properties of statements:

4.4
STATEMENTS

"Except as indicated, statements are executed in sequence." [stmt.stmt] § 1

Properties of statements:
er…

4.4
STATEMENTS "COMPOSE" ONLY BY SEQUENCING

x;
y;
z;

no type checking
value checking is manual, intrusive
implicit constraints
temporal reasoning is poor

4.5
IMPERATIVE SAFETY GEAR

Many of our guidelines, best practices, idioms, and much of our tooling, analysis, and
brainpower work in service of checking the implicit constraints around statement
"composition".

4.6
DECLARATIVE STYLE: AVOID STATEMENTS!

expression statement
selection statement (if, switch)
iteration statement (for, while, do)
jump statement (break, continue, return, goto)
declaration statement

4.7
LET'S EXAMINE HISTORY…

Let's look at where we've come from, and see how it informs moving to declarative
style.

5.1
WORLD'S LAST BUG

while (true)
{
status = GetRadarInfo();
if (status = 1)
LaunchMissiles();
}

Ancient history you say?

5.2
ODD THING #1: ASSIGNMENTS ARE EXPRESSIONS

Assignment as an expression is a historical choice.


It's doing us no favours today.
Assignment should be a statement.

5.3
ODD THING #1: ASSIGNMENTS ARE EXPRESSIONS

/* The following function will print a non-negative number, n, to


the base b, where 2<=b<=10. This routine uses the fact that
in the ASCII character set, the digits 0 to 9 have sequential
code values. */
printn(n, b) {
extrn putchar;
auto a;

if (a = n / b)
printn(a, b); /* recursive */
putchar(n % b + '0');
}

5.4
ODD THING #1: ASSIGNMENTS ARE EXPRESSIONS

We've learned to deal with this. But we don't really like it.

5.5
ODD THING #1: ASSIGNMENTS ARE EXPRESSIONS

We've learned to deal with this. But we don't really like it.
yoda conditions

5.5
ODD THING #1: ASSIGNMENTS ARE EXPRESSIONS

We've learned to deal with this. But we don't really like it.
yoda conditions
compiler warnings

5.5
ODD THING #1: ASSIGNMENTS ARE EXPRESSIONS

We've learned to deal with this. But we don't really like it.
yoda conditions
compiler warnings
P0963: discouraged

5.5
ODD THING #2: = MEANS ASSIGNMENT

/* The following function will print a non-negative number, n, to


the base b, where 2<=b<=10. This routine uses the fact that
in the ASCII character set, the digits 0 to 9 have sequential
code values. */
printn(n, b) {
extrn putchar;
auto a;

if (a = n / b) /* assignment, not test for equality */


printn(a, b); /* recursive */
putchar(n % b + '0');
}

5.6
ODD THING #2: = MEANS ASSIGNMENT

"A notorious example for a bad idea was the choice of the equal
sign to denote assignment."

– Niklaus Wirth

5.7
ODD THING #2: = MEANS ASSIGNMENT

5.8
ODD THING #2: = MEANS ASSIGNMENT

Superplan (1951) introduced = for assignment

5.8
ODD THING #2: = MEANS ASSIGNMENT

Superplan (1951) introduced = for assignment


FORTRAN (1957) used = (because .GT. .LT. .EQ. etc)

5.8
ODD THING #2: = MEANS ASSIGNMENT

Superplan (1951) introduced = for assignment


FORTRAN (1957) used = (because .GT. .LT. .EQ. etc)
ALGOL-58 introduced := (assignment) distinct from = (equality)
Subsequently many languages went this way

5.8
ODD THING #2: = MEANS ASSIGNMENT

Superplan (1951) introduced = for assignment


FORTRAN (1957) used = (because .GT. .LT. .EQ. etc)
ALGOL-58 introduced := (assignment) distinct from = (equality)
Subsequently many languages went this way
BCPL (1967) used :=

5.8
ODD THING #2: = MEANS ASSIGNMENT

Superplan (1951) introduced = for assignment


FORTRAN (1957) used = (because .GT. .LT. .EQ. etc)
ALGOL-58 introduced := (assignment) distinct from = (equality)
Subsequently many languages went this way
BCPL (1967) used :=
B (1969) simplified a lot of BCPL syntax, went with =
Followed by C (1972) and many other languages

5.8
ODD THING #2: = MEANS ASSIGNMENT

"Since assignment is about twice as frequent as equality testing in


typical programs, it’s appropriate that the operator be half as
long."

– Ken Thompson

5.9
DECLARATION VS (RE-)ASSIGNMENT

In moving from BCPL to B, the distinction between


declaration and reassignment was blurred.
int a = 42; // declaration/initialization

a = 1729; // assignment

"It cannot be overemphasized that assignment and


initialization are di erent operations."

– Bjarne Stroustrup, The C++ Programming Language

5 . 10
<END OF HISTORICAL DIVERSION>

5 . 11
<END OF HISTORICAL DIVERSION>

Declaring things is – has always been – fine.

5 . 11
<END OF HISTORICAL DIVERSION>

Declaring things is – has always been – fine.


Declaration and assignment are different things that look the same.

5 . 11
<END OF HISTORICAL DIVERSION>

Declaring things is – has always been – fine.


Declaration and assignment are different things that look the same.
Assignment as an expression statement is best avoided.
Chained assignments are a syntactic laziness.

5 . 11
DECLARATIVE STYLE: AVOIDING STATEMENTS

Statement Status
assignment
selection
iteration
jump
declaration

5 . 12
A QUICK DECLARATIVE STUDY

6.1
EXAMPLE

Given:
weak_ptr<Foo> wp;

How to write:
Bar b;
{
auto sp = wp.lock();
if (sp) b = sp->bar();
}

In a (more) declarative way.

6.2
C++17 IF-INITIALIZER?

Bar b;
if (auto sp = wp.lock(); sp)
b = sp->bar();

This still has the declaration/initialization split. Still has mutable state.

6.3
CONDITIONAL OPERATOR?

Bar b = wp.lock() ? wp.lock()->bar() : Bar{};

Hm…

6.4
C++?? CONDITIONAL-OPERATOR-INITIALIZER?

// this isn't real syntax...


Bar b = [auto sp = wp.lock(); sp] ? sp->bar() : Bar{};

Might be nice… but not today.

6.5
GCC EXTENSION?

Bar b =
({
auto sp = wp.lock();
sp ? sp->bar() : Bar{};
});

Not ISO C++.

6.6
I+LE?

Bar b = [&] () {
if (auto sp = wp.lock(); sp) return sp->bar();
return Bar{};
}();

Immediately-invoked, inline, initializing, …

6.7
OPTIONAL-LIKE?

Bar b = get_bar_or(wp.lock(), Bar{});

Not really generic enough.

6.8
FUNCTORIAL/MONADIC INTERFACE?

shared_ptr<Bar> b = fmap(wp.lock(),
[] (auto foo) { return foo.bar(); });

template <typename T, typename F>


[[nodiscard]] auto fmap(const shared_ptr<T>& p, F f)
-> shared_ptr<invoke_result_t<F, T>>
{
...
}

6.9
STUDY CONCLUSIONS

"Total" declarative style is not always achievable in C++.


A more declarative style is a reasonable goal.
Some features of C++ help us get there.
Different domains lean towards different approaches.

6 . 10
EXISTING DECLARATIVE PRACTICE

We are surrounded by guidelines, goals and idioms.


Looking through a declarative lens, we can tie it together.

7.1
CORE GUIDELINES

Con.1 By default, make objects immutable.


Con.4 Use const to define objects with values that do not change after construction.
ES.21 Don't introduce a variable (or constant) before you need to use it.
ES.22 Don't declare a variable until you have a value to initialize it with.
ES.28 Use lambdas for complex initialization.

7.2
DECLARATIVE STYLE: AVOIDING STATEMENTS

Statement Status Killed by


assignment guidelines
selection
iteration
jump
declaration

7.3
FUNCTIONS IN GENERAL

We like to put things in functions.


shorter is more expressive, understandable
encapsulation of variable scopes, lifetimes, concerns
functions give things names

7.4
ANOTHER REASON TO LIKE FUNCTIONS

Functions turn statements into expressions.


return is the socially acceptable goto
way better than break
and if that wasn't enough, RVO

7.5
<ALGORITHM>

"No Raw Loops"


What does that mean?
encapsulate iteration statements
encapsulate remaining assignments
encapsulate break and continue

7.6
#include "my_algorithms.h"

min_unused
is_prefix_of
join
transform_if
set_differences (aka before and after)
push_back_unique

7.7
DECLARATIVE STYLE: AVOIDING STATEMENTS

Statement Status Killed by


assignment guidelines
selection
iteration "no raw loops"
jump "no raw loops"
declaration

7.8
DECLARATIVE DOMAINS AND
PATTERNS

8.1
TESTING

TEST_CASE( "Factorials are computed", "[factorial]" ) {


REQUIRE( Factorial(1) == 1 );
REQUIRE( Factorial(2) == 2 );
REQUIRE( Factorial(3) == 6 );
}

Conditions are encapsulated; nothing is dependent.

8.2
TESTING

TEST_CASE( "Factorials are computed", "[factorial]" ) {


REQUIRE( Factorial(1) == 1 );
REQUIRE( Factorial(2) == 2 );
REQUIRE( Factorial(3) == 6 );
}

Conditions are encapsulated; nothing is dependent.


idempotent

8.2
TESTING

TEST_CASE( "Factorials are computed", "[factorial]" ) {


REQUIRE( Factorial(1) == 1 );
REQUIRE( Factorial(2) == 2 );
REQUIRE( Factorial(3) == 6 );
}

Conditions are encapsulated; nothing is dependent.


idempotent
minimal temporal dependency between statements

8.2
TESTING

TEST_CASE( "Factorials are computed", "[factorial]" ) {


REQUIRE( Factorial(1) == 1 );
REQUIRE( Factorial(2) == 2 );
REQUIRE( Factorial(3) == 6 );
}

Conditions are encapsulated; nothing is dependent.


idempotent
minimal temporal dependency between statements
leverage constructors/RAII

8.2
TESTING

TEST_CASE( "Factorials are computed", "[factorial]" ) {


REQUIRE( Factorial(1) == 1 );
REQUIRE( Factorial(2) == 2 );
REQUIRE( Factorial(3) == 6 );
}

Conditions are encapsulated; nothing is dependent.


idempotent
minimal temporal dependency between statements
leverage constructors/RAII
popularity of sections over fixture management

8.2
LOGGING : IMPERATIVE TURNED DECLARATIVE

fprintf(g_debugLogFilep, "R Tape loading error, %d:%d", line, stmt);

vs
LOG("R Tape loading error, " << line << ':' << stmt);

8.3
WHERE DID THE GLOBAL GO?

// g_debugLogFilep is a global variable


fprintf(g_debugLogFilep, "R Tape loading error, %d:%d", line, stmt);

// somewhere, a "global" variable lurks? where does the log go to?


LOG("R Tape loading error, " << line << ':' << stmt);

8.4
C-STYLE LOG SINK

fprintf(g_debugLogFilep, "R Tape loading error, %d:%d", line, stmt);

What would we do if we wanted to change where the log went?

8.5
LOG SINKS: OO TURNED DECLARATIVE

A study in compositional design.


class Sink
{
...
virtual bool Push(const Entry& e);
...
};

8.6
SINK VARIATIONS

class FileSink : Sink


{
...
FileSink(string_view pathname);
...
};

class DebugSink : Sink { ... };

8.7
SINK VARIATIONS

class FilterSink : Sink


{
...
template <typename Pred>
FilterSink(Pred p);
...
using Predicate = std::function<bool(const Entry&)>;
Predicate pred;
};

8.8
SINK VARIATIONS

// Exercise for the reader: ExecutionPolicy Concept


template <typename ExecutionPolicy>
class ExecSink : Sink { ... };

8.9
SINK VARIATIONS

class MultiSink : Sink


{
...
vector<unique_ptr<Sink>> sinks;
};

8 . 10
SINK VARIATIONS

class NullSink : Sink


{
...
virtual bool Push(const Entry&) override { return true; }
...
};

8 . 11
DECLARATIVE SINK CONSTRUCTION

auto fileSink = [&] () -> std::unique_ptr<Sink> {


if (logToFile) {
return std::make_unique<FileSink>(generate_filename());
} else {
return std::make_unique<NullSink>();
}
}();

Conditions are encapsulated at the point of construction.


The point of use is condition-free and declarative.

8 . 12
DECLARATIVE STYLE: AVOIDING STATEMENTS

Statement Status Killed by


assignment guidelines
selection paradigm shift
iteration "no raw loops"
jump "no raw loops"
declaration

8 . 13
DESIGN PATTERNS

9.1
OO PATTERNS

Several patterns lean towards declarative style.


Many patterns are about replacing conditions with polymorphism.
Null object
Command
Composite

9.2
THE "BUILDER PATTERN"

AKA "Fluent Style" (not the original GoF pattern)


FluentGlutApp(argc, argv)
.withDoubleBuffer().withRGBA().withAlpha().withDepth()
.at(200, 200).across(500, 500)
.named("My OpenGL/GLUT App")
.create();

"In which the author turns what should be 5 lines of glut calls at
the start of main into 100 lines of buggy OOP."

– Nicolas Guillemot (via Twitter)

9.3
BUILDER PATTERN: A BETTER EXAMPLE

// Schedule& Schedule::then(interval_t);

auto s = Schedule(interval::fixed{1s})
.then(repeat::n_times{5, interval::random_exponential{2s, 2.0}})
.then(repeat::forever{interval::fixed{30s}});

// template <typename Timer, typename Task>


// void Schedule::run(Timer, Task);
s.run(timer, task);

9.4
BUILDER PATTERN: HELP FROM C++17

P0145: Refining Expression Evaluation Order for Idiomatic C++


void f()
{
std::string s = "but I have heard it works even if you don't believe in it";
s.replace(0, 4, "")
.replace(s.find("even"), 4, "only")
.replace(s.find(" don't"), 6, "");
assert(s == "I have heard it works only if you believe in it");
}

9.5
PUTTING TYPES TO WORK

This "builder pattern" is an ideal place to put strong types to work.


// Build a request object
request_t req = make_request()
.set_req_field_1(...)
.set_req_field_2(...)
.set_opt_field(...)
.set_opt_field(...)
.set_opt_field(...);

// Use it
send_request(req);

9.6
PUTTING TYPES TO WORK

The "normal" construct for this behaviour.


struct request_t {
request_t& set_req_field_1(field_t f) {
f1 = f;
return *this;
}
request_t& set_req_field_2(field_t f);
request_t& set_opt_field(field_t f);

field_t f1;
// etc ...
};

request_t make_request() { ... }

9.7
BEHAVIOUR IN THE TYPE

One way: use a bitfield.


constexpr static uint8_t OPT_FIELDS = 1 << 0;
constexpr static uint8_t REQ_FIELD1 = 1 << 1;
constexpr static uint8_t REQ_FIELD2 = 1 << 2;
constexpr static uint8_t ALL_FIELDS = OPT_FIELDS | REQ_FIELD1 | REQ_FIELD2;

9.8
BEHAVIOUR IN THE TYPE
template <uint8_t N>
struct request_t;

template <>
struct request_t<0>
{
field_t f1;
// etc ...
};

template <uint8_t N>


struct request_t : request_t<N-1>
{
request_t<N & ~REQ_FIELD1>& set_req_field1(field_t f) {
this->f1 = f;
return *this;
}
request_t<N & ~REQ_FIELD2>& set_req_field2(field_t f);
request_t& set_opt_field(field_t f);
};

9.9
BEHAVIOUR IN THE TYPE

Use =delete to enable the send_request function only for a correctly-filled-in request.
request_t<ALL_FIELDS> make_request();

template <uint8_t N>


void send_request(const request_t<N>& req) = delete;

void send_request(const request_t<OPT_FIELDS>& req);

See an implementation of this idea by @timtro:


https://gist.github.com/timtro/c6efbfa0c3fb9d0de1d50647ed341026

9 . 10
BUILDER PATTERN GUIDELINES

Fluent style is more suitable when:


you have a single verb (then, set_field)
you'll be building objects a lot
you can make types work for you
rvalues aren't too verbose

9 . 11
WHERE CAN WE GO FROM HERE?

Where is C++ giving declarative code good support?


Where can it be improved?

10 . 1
RAII, INITIALIZATION

RAII is the bread-and-butter of C++ programming. It's a natural fit for a declarative


style.
Initialization is complex, but getting easier.
aggregate initialization
rule of zero
UDLs for extra expressiveness
class template deduction
C++20 designators

10 . 2
FUNCTIONS & LAMBDAS

Functions:
turn statements into expressions
give expressions names
encapsulate conditions
are the optimizer's bread and butter (RVO, inlining)
Structured bindings work around single-return-value limitation.

10 . 3
OVERLOADS & TEMPLATES

Parametric polymorphism: enable use of functions without conditionals.


Let the compiler do the right thing.
template <typename A, typename B = A,
typename C = std::common_type_t<A, B>,
typename D = std::uniform_int_distribution<C>>
inline auto make_uniform_distribution(const A& a,
const B& b = std::numeric_limits<B>::max())
-> std::enable_if_t<std::is_integral_v<C>, D>
{
return D(a, b);
}

Andy Bond: AAAARGH!? (CppCon 2016)


https://www.youtube.com/watch?v=ZCGyvPDM0YY

10 . 4
OVERLOADS & TEMPLATES

template <typename A, typename B = A,


typename C = std::common_type_t<A, B>,
typename D = std::uniform_real_distribution<C>>
inline auto make_uniform_distribution(const A& a,
const B& b = B{1})
-> std::enable_if_t<std::is_floating_point_v<C>, D>;

class uniform_duration_distribution;

template <typename A, typename B = A,


typename C = std::common_type_t<A, B>,
typename D = uniform_duration_distribution<C>>
inline auto make_uniform_distribution(const A& a,
const B& b = B::max()) -> D;

10 . 5
INCONSISTENCIES

In C++17, we gained if- and switch-initializers.


if (auto [it, inserted] = m.emplace("Jenny", 8675309); inserted)
{
...
}

But no love for the expression equivalent of if.


auto result =
(auto [it, inserted] = m.emplace("Jenny", 8675309); inserted)
? // some expression ...
: // some other expression ...

10 . 6
HERITAGE: OPERATORS

C++ inherits pretty much all of its operators from C (or even earlier).
We also inherit some fixed semantics (despite operator overloading).
Operators can be amazing for expressivity of code and declarative constructs.

10 . 7
"IMPERATIVE SAFETY GEAR"

10 . 8
"IMPERATIVE SAFETY GEAR"

better warnings

10 . 8
"IMPERATIVE SAFETY GEAR"

better warnings
static analysis

10 . 8
"IMPERATIVE SAFETY GEAR"

better warnings
static analysis
[[nodiscard]] attribute (another default?)

10 . 8
"IMPERATIVE SAFETY GEAR"

better warnings
static analysis
[[nodiscard]] attribute (another default?)
[[fallthrough]] attribute

10 . 8
"IMPERATIVE SAFETY GEAR"

better warnings
static analysis
[[nodiscard]] attribute (another default?)
[[fallthrough]] attribute
if-initializer

10 . 8
RICHNESS OF LIBRARY HELP

Seemingly-unimportant helper functions (or metafunctions) can be very important in


avoiding conditionals.

10 . 9
RICHNESS OF LIBRARY HELP

Seemingly-unimportant helper functions (or metafunctions) can be very important in


avoiding conditionals.
std::exchange

10 . 9
RICHNESS OF LIBRARY HELP

Seemingly-unimportant helper functions (or metafunctions) can be very important in


avoiding conditionals.
std::exchange
std::as_const

10 . 9
RICHNESS OF LIBRARY HELP

Seemingly-unimportant helper functions (or metafunctions) can be very important in


avoiding conditionals.
std::exchange
std::as_const
std::apply

10 . 9
RICHNESS OF LIBRARY HELP

Seemingly-unimportant helper functions (or metafunctions) can be very important in


avoiding conditionals.
std::exchange
std::as_const
std::apply
expanding type_traits

10 . 9
RICHNESS OF LIBRARY HELP

Seemingly-unimportant helper functions (or metafunctions) can be very important in


avoiding conditionals.
std::exchange
std::as_const
std::apply
expanding type_traits
monadic interface to std::optional

10 . 9
RICHNESS OF LIBRARY HELP

template <typename T>


decltype(auto) identity(T&& t) {
return std::forward<T>(t);
}

template <typename T>


auto always(T&& t) {
return [x = std::forward<T>(t)](auto...) { return x; };
};

10 . 10
GUIDELINES FOR DECLARATIVE CODE

Meta-guideline reductio: avoid writing statements.


(Principally control-flow and assignment.)

11 . 1
REPLACING CONDITIONALS

Style Signature Element Elimination Strategy


Imperative
Object-Oriented
Functional
Generic

11 . 2
REPLACING CONDITIONALS

Style Signature Element Elimination Strategy


Imperative Statement
Object-Oriented
Functional
Generic

11 . 2
REPLACING CONDITIONALS

Style Signature Element Elimination Strategy


Imperative Statement multi-computation
Object-Oriented
Functional
Generic

11 . 2
REPLACING CONDITIONALS

Style Signature Element Elimination Strategy


Imperative Statement multi-computation
Object-Oriented Object construction
Functional
Generic

11 . 2
REPLACING CONDITIONALS

Style Signature Element Elimination Strategy


Imperative Statement multi-computation
Object-Oriented Object construction polymorphism
Functional
Generic

11 . 2
REPLACING CONDITIONALS

Style Signature Element Elimination Strategy


Imperative Statement multi-computation
Object-Oriented Object construction polymorphism
Functional Function call
Generic

11 . 2
REPLACING CONDITIONALS

Style Signature Element Elimination Strategy


Imperative Statement multi-computation
Object-Oriented Object construction polymorphism
Functional Function call higher order function
Generic

11 . 2
REPLACING CONDITIONALS

Style Signature Element Elimination Strategy


Imperative Statement multi-computation
Object-Oriented Object construction polymorphism
Functional Function call higher order function
Generic Type instantiation

11 . 2
REPLACING CONDITIONALS

Style Signature Element Elimination Strategy


Imperative Statement multi-computation
Object-Oriented Object construction polymorphism
Functional Function call higher order function
Generic Type instantiation traits class

11 . 2
REPLACING CONDITIONALS

Style Signature Element Elimination Strategy


Imperative Statement multi-computation
Object-Oriented Object construction polymorphism
Functional Function call higher order function
Generic Type instantiation traits class

The Conditional-Replacement Meta-Pattern.

11 . 2
REPLACING CONDITIONALS

11 . 3
REPLACING CONDITIONALS

Push conditionals down the callstack


intrinsic to data structures
optional/monadic interface
handle at leaf, don't leak

11 . 3
REPLACING CONDITIONALS

Push conditionals down the callstack


intrinsic to data structures
optional/monadic interface
handle at leaf, don't leak
Push conditionals up the callstack
dependency injection
higher-order functions
power to the caller
lifted to root, abstracted

11 . 3
REPLACING CONDITIONALS

Push conditionals down the callstack


intrinsic to data structures
optional/monadic interface
handle at leaf, don't leak
Push conditionals up the callstack
dependency injection
higher-order functions
power to the caller
lifted to root, abstracted
Goal: total functions

11 . 3
REPLACING CONDITIONALS => FEWER STATEMENTS

When you replace/encapsulate conditionals:


less call-site logic (obviously)
simpler, total functions
simpler loops (no break/continue without conditions)
more reason-ability

11 . 4
REPLACING LOOPS => FEWER STATEMENTS

No Raw Loops: encapsulate and replace iteration and jumps


less call-site logic
simpler, total functions
more reason-ability
vocabulary grows

11 . 5
REPLACING ASSIGNMENTS

Declare-at-use
use I+LEs
leverage const
use AAA-style if you like
Overload operators for declaration power

11 . 6
LET THE LANGUAGE HELP

Where you can't avoid statements, use "imperative safety gear"


nodiscard attribute
if-initializer
static analysis

11 . 7
DECLARATIVE INTERFACES

dependency injection
higher-order functions
builder pattern / fluent style
identify monoids
start with composition

11 . 8
DECLARATIVE GOALS

Expressions over statements.


Declarations over assignments.
Unconditional code.

11 . 9

You might also like