Skip to content

Dynamic array and array view types #7

@capr

Description

@capr

Here's two basic libs that we could use as a starting point for a discussion:

https://github.com/luapower/dynarray
https://github.com/luapower/arrayview

arrayview is a struct with just a pointer & length that maps the idea of a finite array over a given buffer so that you can apply bound-checked access, copying, sorting, etc. over that buffer. dynarray is a malloc'ed arrayview, so basically an arrayview with additional methods and overloads for inserting and removing elements, etc. (and it actually uses arrayview instead of duplicating its code). The APIs are documented in the source code for now for many reasons (for one, this code was already been rewritten once and it will be further modified with increased usage).

Two points I want to make on these libs.

1. The necessity of a tier-0 lib

Even though they are the most basic libs you can imagine, they are necessarily not dependency-free, as they both depend on the low module (https://github.com/luapower/low) which is my tier-0 lib as discussed in #3. Hopefully it's now easier to see why that kind of lib is needed and what's in it. Here's an incomplete checklist:

  • addmethods(T, func) - pattern for declaring methods lazily for containers, see How to implement recursive types? terra#348
  • addproperties(T) - adds T.properties which can be quotes or macros so that t.prop redirects to t.properties.prop.
  • after_getmethod(), before_entrymissing() etc. - allows a metamethod to be assigned multiple independent handlers -- this way you can have say addmethods() on a type which assigns __getmethod and still assign __getmethod afterwards for a different purpose.
  • gettersandsetters() - calls t:get_<name>() for t.<name> and t:set_<name>(val) for t.<name> = <val>
  • iif(), min(), max(), assert() - ...
  • copy(dst, src, len) - typed memmove
  • equal(a, b) - typed memcmp
  • alloc() - typed realloc
  • hash(uint32|uint64, buf, len, hash) - default hash function
  • C headers are included with a wrapper around includec() so that 1) the calls are memoized, 2) symbols are dumped into a single table which the low module inherits so that setfenv(1, 'low') gives unprefixed access to functions like memset etc.

Some of these are one-liners that can be copy-pasted to remove the dependency on the tier-0 lib, but some are not, so we need to talk about #3.

2. Number of users is the only reliable indicator of API quality

As I mentioned before, I think the best way (IMO the only way) to make a good lib is to first have an use case for it and have the lib emerge out of that. I strongly believe that an API is only as good as the number of calls to it that are made from a variety of contexts. I can't think of a stronger indicator of quality than that. An API that's not heavily used by at least 2-3 apps/libs is not ready for prime-time no matter how much design effort is put into it. My libs only have 1-2 users so far and already have undergone major refactorings and even rewrites. I'm not sure what the action point is here, rather than the advice to not waste time with "designing" APIs for imagined use cases: nobody's going to use them, and for good reason.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions