Skip to content

Instantly share code, notes, and snippets.

@vurtun
Last active May 25, 2025 07:41
Show Gist options
  • Save vurtun/192cac1f1818417d7b4067d60e4fe921 to your computer and use it in GitHub Desktop.
Save vurtun/192cac1f1818417d7b4067d60e4fe921 to your computer and use it in GitHub Desktop.

Revisions

  1. vurtun renamed this gist Oct 26, 2020. 1 changed file with 0 additions and 0 deletions.
  2. vurtun revised this gist Oct 26, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion API Design: Granularity.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    API Design: Granularity (Janurary-2017)
    API Design: Coroutines APIs (Janurary-2017)
    -----------------------------
    I am currently dealing with a lot of libraries at work. Both third party
    as well as libraries written or being currently in process of being
  3. vurtun revised this gist Jun 3, 2018. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions API Design: Granularity.md
    Original file line number Diff line number Diff line change
    @@ -310,6 +310,6 @@ otherwise well written library just because abstractions are to hastely applied
    and granularity is not taken into account. Any kind of feedback is welcome and
    if there is some interest I may write a few more of these on API design.

    [1] https://wiki.qt.io/API_Design_Principles
    [2] https://www.youtube.com/watch?v=ZQ5_u8Lgvyk (Casey Muratori - Designing and Evaluating Reusable Components - 2004)
    [3] https://gist.github.com/pervognsen/d57cdc165e79a21637fe5a721375afba (alternative API - Per Vognsen)
    [1] https://wiki.qt.io/API_Design_Principles
    [2] https://www.youtube.com/watch?v=ZQ5_u8Lgvyk (Casey Muratori - Designing and Evaluating Reusable Components - 2004)
    [3] https://gist.github.com/pervognsen/d57cdc165e79a21637fe5a721375afba (alternative API - Per Vognsen)
  4. vurtun revised this gist Jun 3, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion API Design: Granularity.md
    Original file line number Diff line number Diff line change
    @@ -210,7 +210,7 @@ while (unzip(&un, &req, 0, 0, 0, 0);) {
    un.toc.addr = malloc(req.toc.size);
    un.toc.alignment = req.toc.alignment;
    un.toc.size = req.toc.size;
    } break;
    } break;}
    }
    assert(!un.err);
    ```
  5. vurtun revised this gist Jun 3, 2018. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions API Design: Granularity.md
    Original file line number Diff line number Diff line change
    @@ -58,23 +58,23 @@ easy to do and hard things possible'.
    In general the two main characteristics of APIs are control and ease of use:
    For orthogonal APIs you preferably want:

    1) Unabstracted control over resources for library user:
    1. Unabstracted control over resources for library user:
    By Unabstracted I mean does not rely on abstraction to provide
    resources (templates, allocators, file abstractions,callbacks,...).
    Instead resources are either provided beforehand or can only be requested,
    but never taken.
    2.) Simple but not neccessaringly easy to use
    2. Simple but not neccessaringly easy to use
    Simple and easy are probabily one of the most misused
    words. Simple is an absolute term of how intertwined your
    design is and easy as an relative term of how much work
    it takes to get things running with your library.

    While for diagonal APIs it is more preferable to have:

    1.) library controls resources
    1. library controls resources
    Control over resources ranges from abstracted
    to completly taken away from library user.
    2.) Easy to use but not neccessaringly simple
    2. Easy to use but not neccessaringly simple
    Main focus is in making things as easy
    as possible for new users. Basically make
    easy things easy to do.
  6. vurtun revised this gist Jun 3, 2018. 1 changed file with 39 additions and 37 deletions.
    76 changes: 39 additions & 37 deletions API Design: Granularity.md
    Original file line number Diff line number Diff line change
    @@ -62,7 +62,7 @@ For orthogonal APIs you preferably want:
    By Unabstracted I mean does not rely on abstraction to provide
    resources (templates, allocators, file abstractions,callbacks,...).
    Instead resources are either provided beforehand or can only be requested,
    but never taken.
    but never taken.
    2.) Simple but not neccessaringly easy to use
    Simple and easy are probabily one of the most misused
    words. Simple is an absolute term of how intertwined your
    @@ -73,7 +73,7 @@ For orthogonal APIs you preferably want:

    1.) library controls resources
    Control over resources ranges from abstracted
    to completly taken away from library user.
    to completly taken away from library user.
    2.) Easy to use but not neccessaringly simple
    Main focus is in making things as easy
    as possible for new users. Basically make
    @@ -102,12 +102,12 @@ floating around but in general most provide an API roughly
    like this (does not matter if OO or imperative):

    ```c
    struct unzip {...};
    int unzip_open(struct unzip*, const char *path);
    int unzip_list(struct unzip*, struct unzip_file_info *array_to_fill);
    int unzip_stat(struct unzip*, const char *file, struct unzip_file_info *info);
    int unzip_extract(struct unzip*, const char *file, void *mem, size_t size);
    int unzip_close(struct unzip*);
    struct unzip {...};
    int unzip_open(struct unzip*, const char *path);
    int unzip_list(struct unzip*, struct unzip_file_info *array_to_fill);
    int unzip_stat(struct unzip*, const char *file, struct unzip_file_info *info);
    int unzip_extract(struct unzip*, const char *file, void *mem, size_t size);
    int unzip_close(struct unzip*);
    ```
    I hope most of these functions are self-explanatory. You have a function
    @@ -241,9 +241,11 @@ to transistion between.
    Another example are most text file formats like JSON or XML. Most library APIs
    for these two formats look generally similar to this:
    int json_load(struct json*, const char *path);
    int json_load_string(struct json*, const char *str);
    struct json_value *json_query(struct json*, const char *path);
    ```c
    int json_load(struct json*, const char *path);
    int json_load_string(struct json*, const char *str);
    struct json_value *json_query(struct json*, const char *path);
    ```

    Once again this is a good, easy to understand and use API. Like with the previous
    example you could provide additional abstraction. Abstract files and memory management.
    @@ -261,39 +263,39 @@ diagonal API for ease of use. Rarely you get a orthogonal API as well.
    Enough talk lets look on what I would propose for a orthogonal API:

    ```c
    enum json_token_type;
    struct json_token {
    enum json_token_type type;
    const char *str;
    size_t size;
    };
    struct json {
    const char *stream;
    size_t available;
    };
    int json_read(struct json*, struct json_token*);
    enum json_token_type;
    struct json_token {
    enum json_token_type type;
    const char *str;
    size_t size;
    };
    struct json {
    const char *stream;
    size_t available;
    };
    int json_read(struct json*, struct json_token*);
    ```
    This is a simple streaming API which just reads in one token after the other
    and does not care for memory or file resources as long as it has a stream of
    text to process. A simple use case example would be:
    ```c
    void *file_memory = ...;
    size_t file_size = ...;
    struct json js;
    js.stream = file_memory;
    js.available = file_size;
    struct json_token tok;
    while (json_read(&js, &tok)) {
    if (tok.type == JSON_OBJECT_BEGIN) {
    /* ... */
    } else if (tok.type == JSON_OBJECT_END) {
    /* ... */
    } else if (...)
    }
    void *file_memory = ...;
    size_t file_size = ...;
    struct json js;
    js.stream = file_memory;
    js.available = file_size;
    struct json_token tok;
    while (json_read(&js, &tok)) {
    if (tok.type == JSON_OBJECT_BEGIN) {
    /* ... */
    } else if (tok.type == JSON_OBJECT_END) {
    /* ... */
    } else if (...)
    }
    ```

    Once again this orthogonal API is not as easy to use as a simple call to
  7. vurtun revised this gist Jun 3, 2018. 1 changed file with 64 additions and 70 deletions.
    134 changes: 64 additions & 70 deletions API Design: Granularity.md
    Original file line number Diff line number Diff line change
    @@ -59,14 +59,11 @@ In general the two main characteristics of APIs are control and ease of use:
    For orthogonal APIs you preferably want:

    1) Unabstracted control over resources for library user:
    ----------------------------------------------------
    By Unabstracted I mean does not rely on abstraction to provide
    resources (templates, allocators, file abstractions,callbacks,...).
    Instead resources are either provided beforehand or can only be requested,
    but never taken.

    2.) Simple but not neccessaringly easy to use
    -------------------------------------------
    Simple and easy are probabily one of the most misused
    words. Simple is an absolute term of how intertwined your
    design is and easy as an relative term of how much work
    @@ -75,12 +72,9 @@ For orthogonal APIs you preferably want:
    While for diagonal APIs it is more preferable to have:

    1.) library controls resources
    ------------------------
    Control over resources ranges from abstracted
    to completly taken away from library user.

    to completly taken away from library user.
    2.) Easy to use but not neccessaringly simple
    -----------------------------------------
    Main focus is in making things as easy
    as possible for new users. Basically make
    easy things easy to do.
    @@ -138,46 +132,46 @@ Once again this orthogonal API is not meant to replace the diagonal API. Instead
    provides the basis or foundation to implement it. So enough talk here is my low-level API:
    ```c
    union unzip_request {
    int type;
    struct file {
    int type;
    size_t offset;
    size_t available;
    } file;
    struct toc {
    int type;
    size_t size;
    size_t alignment;
    } toc;
    struct memory {
    int type;
    size_t size;
    } mem;
    };
    struct unzip {
    enum unzip_status err;
    struct file {
    void *mapped;
    size_t offset;
    size_t available;
    size_t total_size;
    } file;
    struct toc {
    void *addr;
    size_t alignment;
    size_t size;
    } toc;
    struct memory {
    void *addr;
    size_t size;
    } mem;
    };
    int unzip_init(struct unzip*, size_t total_size);
    int unzip_init_with_toc(struct unzip*, void *toc, size_t size);
    int unzip(struct unzip*, union unzip_request*, int file_index, void **mem, size_t *size, struct unzip_file_info *info);
    union unzip_request {
    int type;
    struct file {
    int type;
    size_t offset;
    size_t available;
    } file;
    struct toc {
    int type;
    size_t size;
    size_t alignment;
    } toc;
    struct memory {
    int type;
    size_t size;
    } mem;
    };
    struct unzip {
    enum unzip_status err;
    struct file {
    void *mapped;
    size_t offset;
    size_t available;
    size_t total_size;
    } file;
    struct toc {
    void *addr;
    size_t alignment;
    size_t size;
    } toc;
    struct memory {
    void *addr;
    size_t size;
    } mem;
    };
    int unzip_init(struct unzip*, size_t total_size);
    int unzip_init_with_toc(struct unzip*, void *toc, size_t size);
    int unzip(struct unzip*, union unzip_request*, int file_index, void **mem, size_t *size, struct unzip_file_info *info);
    ```

    First things first. This API is obviously a lot harder to understand than
    @@ -196,29 +190,29 @@ and provides either exactly what was previously requested or more. Finally you c
    So lets take a look how function 'unzip_open' could be implemented that way (another version here [3]):

    ```c
    struct unzip un;
    byte *zip_file_memory = ...
    size_t zip_file_size = ...
    union unzip_request req;
    unzip_init(&un, zip_file_size);
    while (unzip(&un, &req, 0, 0, 0, 0);) {
    switch (req.type) {
    case UNZIP_REQUEST_FILE_MAPPING: {
    /* request file mapping */
    un.file.offset = req.file.offset;
    un.file.available = req.file.available;
    un.file.mapped = zip_file_memory + req.file.offset;
    } break;
    case UNZIP_REQUEST_TOC_MEMORY: {
    /* request memory for table of contents */
    free(un.toc.addr);
    un.toc.addr = malloc(req.toc.size);
    un.toc.alignment = req.toc.alignment;
    un.toc.size = req.toc.size;
    } break;
    }
    assert(!un.err);
    struct unzip un;
    byte *zip_file_memory = ...
    size_t zip_file_size = ...

    union unzip_request req;
    unzip_init(&un, zip_file_size);
    while (unzip(&un, &req, 0, 0, 0, 0);) {
    switch (req.type) {
    case UNZIP_REQUEST_FILE_MAPPING: {
    /* request file mapping */
    un.file.offset = req.file.offset;
    un.file.available = req.file.available;
    un.file.mapped = zip_file_memory + req.file.offset;
    } break;
    case UNZIP_REQUEST_TOC_MEMORY: {
    /* request memory for table of contents */
    free(un.toc.addr);
    un.toc.addr = malloc(req.toc.size);
    un.toc.alignment = req.toc.alignment;
    un.toc.size = req.toc.size;
    } break;
    }
    assert(!un.err);
    ```
    This is a lot of code on the library user side. But like I said low-level or
  8. vurtun renamed this gist Jun 3, 2018. 1 changed file with 15 additions and 3 deletions.
    18 changes: 15 additions & 3 deletions API Design: Granularity → API Design: Granularity.md
    Original file line number Diff line number Diff line change
    @@ -107,12 +107,14 @@ First up is a zip archive unpack API. There are lots of them
    floating around but in general most provide an API roughly
    like this (does not matter if OO or imperative):

    ```c
    struct unzip {...};
    int unzip_open(struct unzip*, const char *path);
    int unzip_list(struct unzip*, struct unzip_file_info *array_to_fill);
    int unzip_stat(struct unzip*, const char *file, struct unzip_file_info *info);
    int unzip_extract(struct unzip*, const char *file, void *mem, size_t size);
    int unzip_close(struct unzip*);
    ```
    I hope most of these functions are self-explanatory. You have a function
    to open and close a zip file. Functions to query file information and
    @@ -135,6 +137,7 @@ But it does not have to be that way. Next up is my interpretation of a orthogona
    Once again this orthogonal API is not meant to replace the diagonal API. Instead it
    provides the basis or foundation to implement it. So enough talk here is my low-level API:
    ```c
    union unzip_request {
    int type;
    struct file {
    @@ -175,6 +178,7 @@ provides the basis or foundation to implement it. So enough talk here is my low-
    int unzip_init(struct unzip*, size_t total_size);
    int unzip_init_with_toc(struct unzip*, void *toc, size_t size);
    int unzip(struct unzip*, union unzip_request*, int file_index, void **mem, size_t *size, struct unzip_file_info *info);
    ```

    First things first. This API is obviously a lot harder to understand than
    the first API. While it has fewer functions it has a more complex data
    @@ -191,27 +195,31 @@ and provides either exactly what was previously requested or more. Finally you c

    So lets take a look how function 'unzip_open' could be implemented that way (another version here [3]):

    ```c
    struct unzip un;
    byte *zip_file_memory = ...
    size_t zip_file_size = ...

    union unzip_request req;
    unzip_init(&un, zip_file_size);
    while (unzip(&un, &req, 0, 0, 0, 0);) {
    if (req.type == UNZIP_REQUEST_FILE_MAPPING) {
    switch (req.type) {
    case UNZIP_REQUEST_FILE_MAPPING: {
    /* request file mapping */
    un.file.offset = req.file.offset;
    un.file.available = req.file.available;
    un.file.mapped = zip_file_memory + req.file.offset;
    } else if (req.type == UNZIP_REQUEST_TOC_MEMORY) {
    } break;
    case UNZIP_REQUEST_TOC_MEMORY: {
    /* request memory for table of contents */
    free(un.toc.addr);
    un.toc.addr = malloc(req.toc.size);
    un.toc.alignment = req.toc.alignment;
    un.toc.size = req.toc.size;
    }
    } break;
    }
    assert(!un.err);
    ```
    This is a lot of code on the library user side. But like I said low-level or
    orthogonal code is simpler but not necessarily easier. But the user has total
    @@ -258,6 +266,7 @@ diagonal API for ease of use. Rarely you get a orthogonal API as well.
    Enough talk lets look on what I would propose for a orthogonal API:
    ```c
    enum json_token_type;
    struct json_token {
    enum json_token_type type;
    @@ -269,11 +278,13 @@ Enough talk lets look on what I would propose for a orthogonal API:
    size_t available;
    };
    int json_read(struct json*, struct json_token*);
    ```

    This is a simple streaming API which just reads in one token after the other
    and does not care for memory or file resources as long as it has a stream of
    text to process. A simple use case example would be:

    ```c
    void *file_memory = ...;
    size_t file_size = ...;

    @@ -289,6 +300,7 @@ text to process. A simple use case example would be:
    /* ... */
    } else if (...)
    }
    ```
    Once again this orthogonal API is not as easy to use as a simple call to
    'json_load'. But it provides a lot more control and allows for different
  9. vurtun revised this gist Jun 18, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    API Design: Granularity
    API Design: Granularity (Janurary-2017)
    -----------------------------
    I am currently dealing with a lot of libraries at work. Both third party
    as well as libraries written or being currently in process of being
  10. vurtun revised this gist Feb 21, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -215,7 +215,7 @@ So lets take a look how function 'unzip_open' could be implemented that way (ano

    This is a lot of code on the library user side. But like I said low-level or
    orthogonal code is simpler but not necessarily easier. But the user has total
    control over all resource. In this case both memory as well as file resources.
    control over all resources. In this case both memory as well as file resources.
    The library only knows about memory and it does not care where it came from. Which in
    term is a simpler design but not necessarily easier to use.

  11. vurtun revised this gist Feb 21, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -83,7 +83,7 @@ For orthogonal APIs you preferably want:
    -----------------------------------------
    Main focus is in making things as easy
    as possible for new users. Basically make
    easy things need to be easy to do.
    easy things easy to do.

    Important to note here. What I just described is not absolute.
    Instead it depends heavily on the problem you are solving. So
  12. vurtun revised this gist Feb 9, 2017. 1 changed file with 10 additions and 10 deletions.
    20 changes: 10 additions & 10 deletions API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -62,8 +62,8 @@ For orthogonal APIs you preferably want:
    ----------------------------------------------------
    By Unabstracted I mean does not rely on abstraction to provide
    resources (templates, allocators, file abstractions,callbacks,...).
    Instead resources are either provided or can only be requested
    and not taken.
    Instead resources are either provided beforehand or can only be requested,
    but never taken.

    2.) Simple but not neccessaringly easy to use
    -------------------------------------------
    @@ -81,13 +81,13 @@ For orthogonal APIs you preferably want:

    2.) Easy to use but not neccessaringly simple
    -----------------------------------------
    Main focus point lies in making things as easy
    Main focus is in making things as easy
    as possible for new users. Basically make
    easy things easy to do.
    easy things need to be easy to do.

    Important to note here. What I just described is not absolute.
    Instead it depends heavily on the problem you are solving. So
    see it more like a pointer with some compromises
    it is more like a pointer with some compromises
    to be taken. Furthermore neither orthogonal or diagonal APIs
    are "bad" or "good" in any sense of the imagination.
    They just have different use cases and goals.
    @@ -115,8 +115,8 @@ like this (does not matter if OO or imperative):
    int unzip_close(struct unzip*);

    I hope most of these functions are self-explanatory. You have a function
    to open and close a zip file. Functions to query file information and finally
    functions to extract a file. You could add some more functionality like open
    to open and close a zip file. Functions to query file information and
    to extract a file. You could also add some more functionality like open
    from memory and extracting a file to an OS path and other extracting variants.
    You furthermore could provide abstractions for file input and memory management
    and error handling. But I want to keep it simple for now.
    @@ -178,9 +178,9 @@ provides the basis or foundation to implement it. So enough talk here is my low-

    First things first. This API is obviously a lot harder to understand than
    the first API. While it has fewer functions it has a more complex data
    structure. Let me try to explain it. Basically this is what I would describe as
    an request based API (also known as coroutine, state machine or push/pull APIs).
    You repeatedly call `unzip` and each time the API will
    structure and function arguments. Let me try to explain it. Basically this is
    what I would describe as an request based API (also known as coroutine,
    state machine or push/pull APIs). You repeatedly call `unzip` and each time the API will
    return to you what is currently needed to process the zip archive.

    So if you call `unzip` the first time you would get an request back for file access.
  13. vurtun revised this gist Feb 9, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -44,7 +44,7 @@ biggest issue I encountered so far in libraries. If there is any interest I
    may write a little bit more about each in a similar post.

    Granularity is at its core a simple concept. For every high-level API there
    needs to be a low-level API to achive the same goal with more user control.
    needs to be a low-level API to achieve the same goal with more user control.
    Casey at this point talks about functions instead of APIs but I try to keep it more
    abstract.

  14. vurtun revised this gist Feb 4, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -180,7 +180,7 @@ First things first. This API is obviously a lot harder to understand than
    the first API. While it has fewer functions it has a more complex data
    structure. Let me try to explain it. Basically this is what I would describe as
    an request based API (also known as coroutine, state machine or push/pull APIs).
    You repeatatly call `unzip` and each time the API will
    You repeatedly call `unzip` and each time the API will
    return to you what is currently needed to process the zip archive.

    So if you call `unzip` the first time you would get an request back for file access.
  15. vurtun revised this gist Feb 4, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -116,8 +116,8 @@ like this (does not matter if OO or imperative):

    I hope most of these functions are self-explanatory. You have a function
    to open and close a zip file. Functions to query file information and finally
    functions to extract a file. You could add some more functionality like opening
    from memory and extracting a file to an OS path and other variants of extracting.
    functions to extract a file. You could add some more functionality like open
    from memory and extracting a file to an OS path and other extracting variants.
    You furthermore could provide abstractions for file input and memory management
    and error handling. But I want to keep it simple for now.

  16. vurtun revised this gist Feb 4, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -116,7 +116,7 @@ like this (does not matter if OO or imperative):

    I hope most of these functions are self-explanatory. You have a function
    to open and close a zip file. Functions to query file information and finally
    functions to extract a file. You could add some more functions like opening
    functions to extract a file. You could add some more functionality like opening
    from memory and extracting a file to an OS path and other variants of extracting.
    You furthermore could provide abstractions for file input and memory management
    and error handling. But I want to keep it simple for now.
  17. vurtun revised this gist Feb 2, 2017. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -251,9 +251,9 @@ So what are the problems with this API. I don't always want to create a DOM. I o
    to directly parse the file. You could even go as far as we did by writing a parser
    generator which hand parses the file and fills our program specific data structures
    with data. This simply is not possible with the above API. I am now forced to rewrite
    the whole library because of the API even if what I need is already exists. But it was not
    written to be exposed as an API. Once again this is not that these diagonal APIs are bad in any way. It just
    only supports a high-level way of use. Most of the time however what is written is only the
    the whole library because of API and therefore design decisions.
    Once again this is not that these diagonal APIs are bad in any way. But only providing one
    either orthogonal or diagional API is. Most of the time however what is written is only the
    diagonal API for ease of use. Rarely you get a orthogonal API as well.

    Enough talk lets look on what I would propose for a orthogonal API:
  18. vurtun revised this gist Feb 2, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -237,7 +237,7 @@ multiple. Each providing a tradeoff between control and ease of use, with an opt
    to transistion between.

    Another example are most text file formats like JSON or XML. Most library APIs
    for these two format look generally similar to this:
    for these two formats look generally similar to this:

    int json_load(struct json*, const char *path);
    int json_load_string(struct json*, const char *str);
  19. vurtun revised this gist Feb 2, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -114,7 +114,7 @@ like this (does not matter if OO or imperative):
    int unzip_extract(struct unzip*, const char *file, void *mem, size_t size);
    int unzip_close(struct unzip*);

    I hope most of these functions are self explanatory. You have a function
    I hope most of these functions are self-explanatory. You have a function
    to open and close a zip file. Functions to query file information and finally
    functions to extract a file. You could add some more functions like opening
    from memory and extracting a file to an OS path and other variants of extracting.
  20. vurtun revised this gist Feb 2, 2017. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -251,10 +251,10 @@ So what are the problems with this API. I don't always want to create a DOM. I o
    to directly parse the file. You could even go as far as we did by writing a parser
    generator which hand parses the file and fills our program specific data structures
    with data. This simply is not possible with the above API. I am now forced to rewrite
    the whole API even if what I need is actually already inside the library. But it was not
    written to be exposed as an API. Once again this is not that this API is bad in any way. It just
    only supports a high-level way to use. Most of the time however what is written is only the
    diagonal API for ease of use.
    the whole library because of the API even if what I need is already exists. But it was not
    written to be exposed as an API. Once again this is not that these diagonal APIs are bad in any way. It just
    only supports a high-level way of use. Most of the time however what is written is only the
    diagonal API for ease of use. Rarely you get a orthogonal API as well.

    Enough talk lets look on what I would propose for a orthogonal API:

  21. vurtun revised this gist Feb 2, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -222,7 +222,7 @@ term is a simpler design but not necessarily easier to use.
    But what about multithreading. Well as soon as the above code run correctly the
    table of contents will be inside the 'toc' memory block. You can take this
    memory block and initialize another unzip struct by calling 'unzip_init_with_toc'
    and load file in parallel.
    and load files in parallel.
    Furthermore you can even store the toc to disk or send it over sockets. You could
    even directly store it in a file and load it at runtime and just access everything
    in a multithreaded fashion from the beginning.
  22. vurtun revised this gist Feb 2, 2017. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -171,6 +171,7 @@ provides the basis or foundation to implement it. So enough talk here is my low-
    size_t size;
    } mem;
    };

    int unzip_init(struct unzip*, size_t total_size);
    int unzip_init_with_toc(struct unzip*, void *toc, size_t size);
    int unzip(struct unzip*, union unzip_request*, int file_index, void **mem, size_t *size, struct unzip_file_info *info);
  23. vurtun revised this gist Feb 2, 2017. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -152,6 +152,7 @@ provides the basis or foundation to implement it. So enough talk here is my low-
    size_t size;
    } mem;
    };

    struct unzip {
    enum unzip_status err;
    struct file {
  24. vurtun revised this gist Feb 2, 2017. 1 changed file with 8 additions and 9 deletions.
    17 changes: 8 additions & 9 deletions API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -181,9 +181,9 @@ an request based API (also known as coroutine, state machine or push/pull APIs).
    You repeatatly call `unzip` and each time the API will
    return to you what is currently needed to process the zip archive.

    So if you call `unzip` the first time you would get an request code back for file access.
    So struct `request` inside `struct unzip` would contain a file offset and number of
    bytes to be mapped written in 'unzip.request.file'. The user fills out 'unzip.file'
    So if you call `unzip` the first time you would get an request back for file access.
    So struct `request` would contain a file offset and the number of
    bytes to be mapped written in 'request.file'. The user fills out 'unzip.file'
    and provides either exactly what was previously requested or more. Finally you call function
    `unzip` again.

    @@ -215,7 +215,7 @@ This is a lot of code on the library user side. But like I said low-level or
    orthogonal code is simpler but not necessarily easier. But the user has total
    control over all resource. In this case both memory as well as file resources.
    The library only knows about memory and it does not care where it came from. Which in
    term is a simpler design but not easy to use.
    term is a simpler design but not necessarily easier to use.

    But what about multithreading. Well as soon as the above code run correctly the
    table of contents will be inside the 'toc' memory block. You can take this
    @@ -250,10 +250,9 @@ to directly parse the file. You could even go as far as we did by writing a pars
    generator which hand parses the file and fills our program specific data structures
    with data. This simply is not possible with the above API. I am now forced to rewrite
    the whole API even if what I need is actually already inside the library. But it was not
    written to be exposed as an API. Once again this is not that this API bad in any way. It just
    only supports a high-level way to use the API. Interesting sidenote it is easier to
    create a diagonal API from a orthogonal API than the other way around. Most of the
    time however what is written is only the diagonal API or ease of use.
    written to be exposed as an API. Once again this is not that this API is bad in any way. It just
    only supports a high-level way to use. Most of the time however what is written is only the
    diagonal API for ease of use.

    Enough talk lets look on what I would propose for a orthogonal API:

    @@ -292,7 +291,7 @@ text to process. A simple use case example would be:
    Once again this orthogonal API is not as easy to use as a simple call to
    'json_load'. But it provides a lot more control and allows for different
    use cases while it also can be used to actually implement the diagonal API
    from above if really wanted or needed.
    from above.

    To sum this all up I hope I could provide a small overview over granularity,
    diagonality and orthogonality in API design. Nothing I proposed here invalidates
  25. vurtun revised this gist Feb 2, 2017. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -136,19 +136,19 @@ Once again this orthogonal API is not meant to replace the diagonal API. Instead
    provides the basis or foundation to implement it. So enough talk here is my low-level API:

    union unzip_request {
    int code;
    int type;
    struct file {
    int code;
    int type;
    size_t offset;
    size_t available;
    } file;
    struct toc {
    int code;
    int type;
    size_t size;
    size_t alignment;
    } toc;
    struct memory {
    int code;
    int type;
    size_t size;
    } mem;
    };
  26. vurtun revised this gist Feb 2, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -132,8 +132,8 @@ Another questions especially important today is multithreading. Does this
    library support multithreading and how easy is it to implement. So most of these
    problems are more advanced and often require a complete rewrite of a library.
    But it does not have to be that way. Next up is my interpretation of a orthogonal API.
    Once again this API does not replace the other API. Instead it provides the basis
    or foundation to implement it. So enough talk here is my low-level API:
    Once again this orthogonal API is not meant to replace the diagonal API. Instead it
    provides the basis or foundation to implement it. So enough talk here is my low-level API:

    union unzip_request {
    int code;
  27. vurtun revised this gist Feb 2, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -131,8 +131,8 @@ away from the user of this API. Both file handling and memory management.
    Another questions especially important today is multithreading. Does this
    library support multithreading and how easy is it to implement. So most of these
    problems are more advanced and often require a complete rewrite of a library.
    But it does not have to be that way. Next up is my API I wrote. Once again
    this orthogonal API does not replace the other API. Instead it provides the basis
    But it does not have to be that way. Next up is my interpretation of a orthogonal API.
    Once again this API does not replace the other API. Instead it provides the basis
    or foundation to implement it. So enough talk here is my low-level API:

    union unzip_request {
  28. vurtun revised this gist Feb 2, 2017. 1 changed file with 6 additions and 6 deletions.
    12 changes: 6 additions & 6 deletions API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -121,12 +121,12 @@ from memory and extracting a file to an OS path and other variants of extracting
    You furthermore could provide abstractions for file input and memory management
    and error handling. But I want to keep it simple for now.

    I would proclaim that this is a good API. I would bet that every somewhat proficent
    programmer could understand and use this. So what is the problem here. I mean there
    must be a reason why I took this example. For me this is a high-level or diagonal API.
    It at least fits the description I gave at the top of this post.
    I would proclaim that this is a good API. I bet that every somewhat proficent
    programmer could understand and use this just fine. So what is the problem here?
    I mean there must be a reason why I took this example. For me this is a high-level
    or diagonal API. It at least fits the description I gave at the top of this post.

    So what are some problems. First of resource control is completly taken or abstracted
    First of resource control is completly taken or abstracted
    away from the user of this API. Both file handling and memory management.
    Another questions especially important today is multithreading. Does this
    library support multithreading and how easy is it to implement. So most of these
    @@ -245,7 +245,7 @@ Once again this is a good, easy to understand and use API. Like with the previou
    example you could provide additional abstraction. Abstract files and memory management.
    Good error handling. Finally few more utility and "redundant" functions.

    So what are the problems here. I don't always want to create a DOM. I often just want
    So what are the problems with this API. I don't always want to create a DOM. I often just want
    to directly parse the file. You could even go as far as we did by writing a parser
    generator which hand parses the file and fills our program specific data structures
    with data. This simply is not possible with the above API. I am now forced to rewrite
  29. vurtun revised this gist Feb 2, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -61,7 +61,7 @@ For orthogonal APIs you preferably want:
    1) Unabstracted control over resources for library user:
    ----------------------------------------------------
    By Unabstracted I mean does not rely on abstraction to provide
    resources (templates, allocators, file abstractions,...).
    resources (templates, allocators, file abstractions,callbacks,...).
    Instead resources are either provided or can only be requested
    and not taken.

  30. vurtun revised this gist Feb 2, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions API Design: Granularity
    Original file line number Diff line number Diff line change
    @@ -196,12 +196,12 @@ So lets take a look how function 'unzip_open' could be implemented that way (ano
    union unzip_request req;
    unzip_init(&un, zip_file_size);
    while (unzip(&un, &req, 0, 0, 0, 0);) {
    if (req.code == UNZIP_REQUEST_FILE_MAPPING) {
    if (req.type == UNZIP_REQUEST_FILE_MAPPING) {
    /* request file mapping */
    un.file.offset = req.file.offset;
    un.file.available = req.file.available;
    un.file.mapped = zip_file_memory + req.file.offset;
    } else if (req.code == UNZIP_REQUEST_TOC_MEMORY) {
    } else if (req.type == UNZIP_REQUEST_TOC_MEMORY) {
    /* request memory for table of contents */
    free(un.toc.addr);
    un.toc.addr = malloc(req.toc.size);