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

  • 652