Optimizing AI for immediate response in Smart CCTV
Pseudo dynamic immutable records in C++
1. If one cannot get one’s own way, one must bow to the inevitable...
Pseudo-dynamic
immutable records in C++
Anton Tcholakov
Software Engineer,
Pico Technology
2. About
❖ Software engineer
working on drivers at
Pico Technology
❖ Physics graduate with
experience in
experimental physics
❖ Amateur
photographer
3. About
❖ Software engineer
working on drivers at
Pico Technology
❖ Physics graduate with
experience in
experimental physics
❖ Amateur
photographer
4. About
❖ Software engineer
working on drivers at
Pico Technology
❖ Physics graduate with
experience in
experimental physics
❖ Amateur
photographer
5. Pseudo-dynamic immutable records
“If it walks like a duck and quacks like a duck then
it must be a duck.”
class Duck:
def fly(self):
print("Duck flying")
class Airplane:
def fly(self):
print("Airplane flying")
for thing in Duck(), Airplane():
thing.fly()
6. Pseudo-dynamic immutable records
immutable (adj.)
unchanging over time or unable to be changed
-module(lists).
-export(sum/1).
sum([], acc) -> acc;
sum([First|Rest], acc) -> sum(Rest, First + acc).
sum(List) -> sum(List, 0).
7. Pseudo-dynamic immutable records
composite data type (n.)
… composed from primitives and other composites
type Account =
{ AccountNumber : int
FirstName : string
LastName : string
Balance : float }
8. … in C++. Why?!
❖ Having different mental models for how to deconstruct
problems is incredibly valuable
❖ Functional techniques are very well-suited to
programming concurrent systems
❖ C++ is a very malleable programming language
❖ Intellectual curiosity…
9. Building on immer
https://github.com/arximboldi/immer
immutable, persistent data structures for C++
#include <immer/vector.hpp>
int main() {
const auto v0 = immer::vector<int>{};
const auto v1 = v0.push_back(13);
assert(v0.size() == 0 && v1.size() == 1 && v1[0] == 13);
const auto v2 = v1.set(0, 42);
assert(v1[0] == 13 && v2[0] == 42);
}
11. aeternum
❖ Name is Latin for “forever”…
❖ Currently just a playground
❖ https://github.com/anton-pt/aeternum
12. atoms
❖ Literal types represented by C-style strings
❖ Name is hashed using CRC-32 at compile time
❖ Useful as “tags” for dynamic types and key-value pairs
constexpr aeternum::atom apples("apples");
constexpr aeternum::atom oranges("oranges");
std::cout << std::boolalpha << (apples == oranges);
// output: false
13. tagged_untyped and tagged<T>
❖ tagged<T> inherits from tagged_untyped
❖ Supports std::hash and operator== provided that T does
const auto four_apples = aeternum::make_tagged(apples, 4);
const auto four_oranges = aeternum::make_tagged(oranges, 4);
const auto five_apples = aeternum::make_tagged(apples, 5);
const auto four_more_oranges = aeternum::make_tagged(oranges, 4);
std::cout << std::boolalpha
<< (four_apples == four_oranges) << std::endl // false
<< (four_apples == five_apples) << std::endl // false
<< (four_oranges == four_more_oranges) << std::endl; // true
“It’s less important how these operations are defined and more
important that they are.” - Joe Armstrong
14. tagged_untyped::match<…>
int fruit_count(const aeternum::tagged_untyped& x) {
if (auto apples = x.match<fruit::apples, int>()) {
return *apples;
}
if (auto oranges = x.match<fruit::oranges, int>()) {
return *oranges;
}
return 0;
}
❖ Allows you to convert to tagged<T> provided that the
tag matches
❖ Allows you to define “polymorphic” functions without
complex inheritance hierarchies
15. tagged_untyped in containers
immer::set<aeternum::tagged_untyped> fruit;
fruit = fruit.insert(four_apples);
fruit = fruit.insert(four_oranges);
fruit = fruit.insert(five_apples);
fruit = fruit.insert(four_more_oranges);
for (auto& f : fruit) {
std::cout << "Set contains " << fruit_count(f)
<< " " << f.get_tag().name << std::endl;
}
❖ Containers requiring std::hash are supported
❖ Implementing std::less is future work, but possible
16. untyped_record
class untyped_record {
public:
using data = immer::map<atom, std::shared_ptr<void>>;
using hasher = std::function<std::size_t(...)>;
using equality_comparer = std::function<bool(...)>;
protected:
data _data;
hasher _hasher;
equality_comparer _equality_comparer;
};
❖ Based on a persistent map from atom to
std::shared_ptr<void>
❖ Also implements std::hash and operator==
17. fields<…>::record<…>
template<typename ...FieldTypes>
class fields {
public:
template<const atom& tag,
const field_name<FieldTypes>& ...names>
class record : public untyped_record {
public:
using tagged = tagged<record<tag, names...>>;
};
};
❖ Inherits from untyped_record and automatically supplies
structural equality comparison and hashing arguments
❖ Nested class because of variadic template restrictions (?)
18. field_name<T>
template<typename T>
class field_name : public lens<tagged<untyped_record>, T> {
private:
const atom _field_key;
};
❖ Inherits from lens<tagged<untyped_record>, T> and
contains an atom which is the key in the record’s data
map
❖ A lens is essentially a pair of getter and (persistent)
setter functions
19. Type-aliasing custom record<…>s
namespace contact {
constexpr aeternum::atom tag("contact");
const aeternum::field_name<std::string> telephone_("telephone");
const aeternum::field_name<std::string> email_("email");
using record =
aeternum::fields<std::string, std::string>
::record<tag, telephone_, email_>;
}
❖ Domain model definitions are done by type-aliasing
record in a namespace…
20. Manipulating custom record<…>s
auto const jane_contact =
contact::record::make("12345", “jane_bond@007.com");
std::cout << "Jane's telephone is "
<< jane_contact[contact::telephone_]
<< " and her email is "
<< jane_contact[contact::email_]
<< std::endl;
❖ field_name<T>s act as strongly-typed accessors via an
operator[] overload
21. Nesting custom record<…>s
namespace person {
constexpr aeternum::atom tag("person");
const aeternum::field_name<std::string> name_("name");
const aeternum::field_name<uint8_t> age_("age");
const aeternum::field_name<
contact::record::tagged> contact_("contact");
using record =
aeternum::fields<
std::string, uint8_t, contact::record::tagged>
::record<tag, name_, age_, contact_>;
}
❖ record<…>s can be composed into larger objects, and
equality comparison and hashing will take nested
records into account
22. Manipulating nested record<…>s
auto const john = person::record::make(
"John Smith", 42,
contact::record::make("67890", "j.smith@email.com"));
auto const junior = john
| person::name_.set("Johnny Junior”)
| person::age_.set(12);
auto const person_email_ = person::contact_ >> contact::email_;
auto const junior_new_email = junior
| person_email_.set("junior@email.com");
❖ field_name<T>::set creates a setter which acts on the
record and can be applied via operator|
❖ lens<A, B> and lens<B, C> can be stacked via operator>>
resulting in a lens<A, C>
23. Metadata fields and dynamism
❖ The underlying representation of a weakly-typed map
allows us to augment records with additional metadata
fields
❖ This can lead to less brittle system design: metadata
comes along for the ride and parts of the system that
don’t care about it just propagate it automatically
❖ This case is made well by Rich Hickey in “Maybe Not”:
https://www.youtube.com/watch?v=YR5WdGrpoug
❖ Dynamic data, polymorphic behaviour
24. Metadata: example
namespace music { namespace song {
constexpr aeternum::atom tag("song");
const aeternum::field_name<std::string> name_("name");
const aeternum::field_name<std::string> artist_("artist");
const aeternum::field_name<uint16_t> duration_("duration");
using record =
aeternum::fields<std::string, std::string, uint16_t>
::record<tag, name_, artist_, duration_>;
} }
❖ Imagine a music database: all songs have a name, artist,
and duration, but there is optional metadata such as
lyrics
25. Metadata: example
namespace music { namespace lyrics {
constexpr aeternum::atom tag("lyrics");
struct line {
std::string text;
uint16_t timestamp;
};
const aeternum::field_name<immer::vector<line>> lines_("lines");
const aeternum::field_name<std::string> author_("author");
using record =
aeternum::fields<immer::vector<line>, std::string>
::record<tag, lines_, author_>;
} }
namespace music { namespace metadata {
const aeternum::field_name<
music::lyrics::record::tagged> lyrics_("lyrics");
} }
26. Metadata: example
auto never_gonna = music::song::record::make(
"Never Gonna Give You Up",
"Rick Astley",
183);
never_gonna = never_gonna
| music::metadata::lyrics_.set(
music::lyrics::record::make(
immer::vector<music::lyrics::line> {
{ "Never gonna give you up", 22 },
{ "Never gonna let you down", 26 }
},
"rickroller89"));
27. Future work
❖ Implement serialisation and std::less out of the box
❖ Hashing and equality comparison should be augmented
when extending records with metadata
❖ Metadata fields should behave as prisms, not lenses
❖ Unit tests, perfect use case for RapidCheck:
https://github.com/emil-e/rapidcheck
❖ A better memory policy to avoid many small allocations
❖ Benchmarking (… also against Clojure)
28. Conclusions
❖ Persistent, dynamic maps can be made mostly
ergonomic in C++ with minor abuse of syntax
❖ Structural equality comparison and hashing allow you
to place these objects in collections
❖ Lenses allow you to manipulate deeply nested
immutable structures with ease