Idiomatic C
2018
Why learning LISP makes you a better engineer
The Tale of Two Paradigms
No matter what language you work in, programming in a functional style provides benefits.
You should do it whenever it is convenient, and you should think hard about the decision when it isn't convenient.
John Carmack
Imperative: "how"
Declarative: "what"
The Tale of Two Paradigms
-
statements
-
loops
-
procedures
-
mutable state
-
expressions
-
recursion
-
functions
-
immutable state
Imperative
Declarative
The Tale of Two Paradigms
int fib_iter(int n)
{
int a = 1, b = 1;
for (int i = 2; i <= n; ++i)
{
int old_a = a;
a = b;
b = old_a + b;
}
return b;
}
int fib_rec(int n)
{
if (n < 2)
return 1;
else
return fib_rec(n - 2) +
fib_rec(n - 1);
}
int fib_tail(int n,
int i=2, int a=1, int b=1)
{
if (i > n)
return b;
else
return fib_tail(n,
i + 1, b, a + b);
}
Constructors
Declarative
push_event(event(id = 42,
data = 24));
struct event {
int id;
void *data;
};
void push_event(const struct event *e);
Imperative
int *data = malloc(sizeof(*data));
*data = 24;
struct event e;
e.id = 42;
e.data = data;
push_event(&e);
Constructors
Not-so-imperative: compound literal
int *data = malloc(sizeof(*data));
*data = 24;
push_event(&(struct event){
.id = 42, .data = data
});
push_event(&(struct event){
.id = 42,
.data = ({
int *data = malloc(sizeof(*data));
*data = 24;
data;
})
});
Constructors
Not-so-imperative: statement expression
void *data = ({
int *data = malloc(sizeof(*data));
*data = 24;
data;
});
push_event(&(struct event){
.id = 42,
.data = data,
});
push_event(&(struct event){
.id = 42,
.data = ({
int *data = malloc(sizeof(*data));
*data = 24;
data;
}),
});
Constructors
Wait, what?
#define EVENT(_id, _data) &(struct event){ \
.id = _id, \
.data = ({ \
__typeof(_data) *data = malloc(sizeof(_data)); \
memcpy(data, &_data, sizeof(_data)); \
data; \
}), \
}
struct payload {
int a;
int b;
};
push_event(EVENT(42, (int){ 24 }));
push_event(EVENT(42, ((struct payload){ .a = 4, .b = 5 })));
Constructors
A more realistic example
#define I2C_ADDR(_bus_id, _i2c_addr) \
&(const struct i2c_addr) { \
.bus_id = _bus_id, \
.i2c_addr = _i2c_addr \
}
#define I2C_XFER(_tx, _tx_size, _rx, _rx_size) \
&(const struct i2c_xfer { \
.tx = _tx, \
.tx_size = _tx_size, \
.rx = _rx, \
.rx_size = _rx_size, \
}
#define I2C_ADDR_MAX7360 \
I2C_ADDR(I2C_BUS_KEYPAD, 0x70)
i2c_transfer(
I2C_ADDR_MAX7360,
I2C_XFER(&command, 1, response, 1),
0);
struct i2c_xfer {
const void *tx;
uint8_t tx_size;
void *rx;
uint8_t rx_size;
};
struct i2c_addr {
enum i2c_bus_id bus_id;
uint8_t i2c_addr;
};
Imperative
Iteration
Declarative
for foo in foos:
frob(foo)
map(frob, foos)
sum = 0
for foo in foos:
sum += foo
sum = reduce(operator.add, foos, 0)
foos = [ 1, 2, 3, 4, 5 ]
def frob(i):
# ...
Iteration
Indices
struct foo;
struct foo theFoos[32];
for (size_t i = 0; i != 32; ++i)
{
frob(&theFoos[i]);
}
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
for (size_t i = 0; i != ARRAY_SIZE(theFoos); ++i)
{
frob(&theFoos[i]);
}
Iteration Processing
Higher order functions
#define MAP(function, iterable) \
do { FOREACH(i, iterable) function(*i); } while (0)
MAP(frob, foos);
#define REDUCE(function, iterable, _initial) ({ \
typeof(_initial) initial = _initial; \
FOREACH(i, iterable) \
initial = function(initial, *i); \
initial;
})
int add(int a, int b) { return a + b; }
int sum = REDUCE(add, foos, 0);
Containers
void list_map(struct list_item *list,
void (*fun)(void *data))
{
for (const list_item *i = list;
i;
i = i->next)
{
fun(i->data);
}
}
struct list_item {
void *data;
struct list_item *next;
};
A more realistic example
struct tree_item {
void *data;
struct tree_item *left, *right;
};
void tree_map(struct tree_item *tree,
void (*fun)(void *data))
{
if (!tree) return;
fun(tree->data);
tree_map(tree->left, fun);
tree_map(tree->right, fun);
}
Containers
- https://developer.gnome.org/glib/stable/glib-data-types.html
- https://en.cppreference.com/w/cpp/container
Contexts
Imperative
Declarative
resource.acquire()
action()
resource.release()
with mutex:
function()
resource.acquire()
try:
action()
finally:
resource.release()
Contexts
static const struct spi_device flash_spi;
uint8_t flash_device_id_get(void)
{
uint8_t device_id;
spi_start(&flash_spi);
spi_send(&flash_spi, FLASH_CMD_DEVICE_ID);
device_id = spi_send(&flash_spi, 0);
spi_stop(&flash_spi);
return device_id;
}
Blocks in C
Contexts
Blocks in C: abusing 'for'
bool spi_start(const struct spi_device* spi);
bool spi_stop(const struct spi_device* spi);
#define SPI_BLOCK(spi) for(bool __rc = spi_start(&spi, 0); \
__rc; \
__rc = spi_stop(&spi) || false);
#define MUTEX_BLOCK(m) for(bool __rc = mutex_lock(&m); \
__rc; \
__rc = mutex_unlock(&m) || false);
#define ATOMIC_BLOCK() for(bool __rc = disable_interrupts(); \
__rc; \
__rc = enable_interrupts() || false);
Contexts
uint8_t flash_device_id_get(void)
{
uint8_t device_id;
SPI_BLOCK(flash_spi)
{
spi_send(&flash_spi, FLASH_CMD_DEVICE_ID);
spi_send(&flash_spi, 0);
spi_send(&flash_spi, 0);
spi_send(&flash_spi, 0);
device_id = spi_send(&flash_spi, 0);
}
return device_id;
}
Blocks in C: abusing 'for'
Thank you
Questions?
Idiomatic C @ Silvair
By Michał Lowas-Rzechonek
Idiomatic C @ Silvair
Why learning LISP makes you a better engineer
- 698