5 ms·
I've done a few APIs similar in spirit to this one (and one similar in functionality, too), and I've found using a blend of a few of the mentioned methods to be
by jesse__ 4y ago
I've done a few APIs similar in spirit to this one (and one similar in functionality, too), and I've found using a blend of a few of the mentioned methods to be pretty effective.
I start with the basics and implement everything as explicitly (read: type safe) as possible:
struct bob_params {...}
struct line_params {...}
pdf_page_cmd_bob(bob_params *params) { ... }
pdf_page_cmd_line(line_params *params) { ... }
Then, when that works and I want to add patterns, which are a superset, I'd add something that is literally a superset of those functions.
enum pattern_type {
patterntype_Bob,
patterntype_Line,
}
struct pattern {
pattern_type type;
union {
bob_params Bob;
line_params Line;
}
}
pdf_pattern_cmd(pattern *pattern) {
switch (pattern->type) {
case patterntype_Bob: /* do bob drawing a bunch of times, or whatever */
}
}
// The pattern struct has a lot of different names (sum type, discriminated union, algebraic datatype, tagged union .. probably more), but the idea is the 'type' tag tells you which one of the union values to use.
I've tried everything and, as far as I can tell, this gets you the best of all worlds. It's pretty much as type-safe as things get in C. It's extremely flexible; you just mix the fundamentals together as you like when adding higher-order functions. It's fast; the compiler can see exactly what's going on, so you pay minimal runtime cost.
Yes, it's a bunch of typing to get the functions all spelled out, but it's really not that bad considering how easy/obvious the code actually is.
I use this pattern so much I actually wrote a little metaprogramming language that is capable of generating a lot of the boilerplate for you. Link in my bio, if anyone's interested in looking at it.
- twisteriffic 4y agoThat style speaks to me. Thanks!
- lelanthran 4y agoFunnily enough that's the most common pattern I see in my personal C code (for example, my little lisp interpreter - https://github.com/lelanthran/csl/blob/master/src/parser/atom.c#L386 https://github.com/lelanthran/csl/blob/master/src/parser/ato...) but I still recommend using the `Generic` keyword in C. For the next time I do a pattern like this, I'll be using `Generic` keyword to make the dispatch a compile-time match, not check at runtime.
- deleted 4y ago[deleted]
- KerrAvon 4y agoI think you may be disappointed. Every single time I've tried to use `_Generic`, I've found that it's more trouble than it's worth. They seem to have made it be useful for a very narrow case -- tgmath.h -- and not bothered to make it general enough to be applicable to a wide variety of things that you might like to use it with.
- jesse__ 4y agoCan you elaborate slightly on how you're planning on using _Generic to turn runtime dispatches into compile time ones? I'm not quite putting together how you can do that.
- lelanthran 4y agoThe example from the wikipedia page for C11 (https://en.wikipedia.org/wiki/C11_(C_standard_revision)#Changes_from_C99 https://en.wikipedia.org/wiki/C11_(C_standard_revision)#Chan...) is compile time determination of the function to call: #define cbrt(x) _Generic((x), long double: cbrtl, \ default: cbrt, \ float: cbrtf)(x) In code you'll write `cbrt(foo)` and the correct function will be called for the type of foo. As I understand it, the `_Generic` selection is performed at compile time.
- JonChesterfield 4y agoRecommend `__attribute__((overloadable))` instead of _Generic. The former opts into C++ style name mangling, which is a bit of a mess in C, but interacts well with `static` as forwarding wrappers in a header. The latter is a ridiculous mess invented by the C committee.
- ablob 4y agoThe latter does not require any name mangling. Any function that is called, was first created by the programmer. You might not like it, but it certainly fits any C code that wants to interface with other languages. Furthermore, I could only find this feature in the clang-compiler and references to it outside of the online-docs in gcc.
- gabereiser 4y agocan we just agree that PDF sucks? Sometime's the API must operate on a spec that is unruly to begin with. I like this approach. It's about the best you can get when dealing with this without completely reinventing the storage format to support the API.
- jesse__ 4y ago0MgZz yea PDF has got to be one of the worst specs ever created. It's amazing PDF viewers work at all.
- cpeterso 4y agoIf you’re designing a stable API for external users, you might want to lock down your API even more by forward declaring the struct types as opaque in the public header file and only defining the struct members in a private library header file. This prevents users from messing with your library’s private state and allows you to change implementation details later without breaking binary compatibility. The disadvantage is that users can’t control how the structs are allocated or embed them in their own structs. /* foo.h */ struct foo_object; struct foo_object* foo_create(int flags, …); /* foo.c */ #include “foo.h” struct foo_object { int flags, … };
- jesse__ 4y ago>> The disadvantage is that users can’t control how the structs are allocated Yeah, that usually kills that plan for me. I also try to avoid heap allocation wherever possible (which is usually pretty easy), and that solution ends up with an everything-is-heap-allocated nest of pointers, which I really try to avoid. I typically do a request-response thing where the user code asks how big something is, then they do the allocation, and pass the allocation into the library. Windows does this a lot and, while Microsoft products are generally a giant garbage fire these days, I think this is one pattern they really got right.
- xyzzy_plugh 4y agoHaving spent a considerable time in the Mines of Microsoft, I see what you're getting at but argue that unless you're shipping proprietary, closed source code, this is just unnecessary. It's much simpler to just mark fields as private with some convention. If callers want to muck with your struct, they will. Opaque types is a big stick, I rarely reach for it. Occasionally it's useful, but I struggle to recall when a simpler, less hostile approach was worse.
- jesse__ 4y agoGenerally I agree, but have one big disagreement with those statements. >> It's much simpler to just mark fields as private with some convention. If callers want to muck with your struct, they will. Agreed, and people mucking with my struct is usually the least of my worries. If a user wants to do that, I say go to town. The "ask for the size first" approach has one major advantage compared to shipping the full struct: 1) You can hide all the random-ass internal structs you use, which can greatly reduce the amount of header code you have to ship. I like shipping a single header w/ a static lib and this can be a huge win for that use case. Note that I don't want to hide the internal stuff because I think users are incompetent and will ruin both our days by doing something dumb. I want to hide noise that they would literally never care about, and don't need definitions for. That all said, packaging an API is pretty tricky to get right and depends a lot on the actual functionality. Maybe we work on different subject matter, and therefore have different views.
- skissane 4y ago> I use this pattern so much I actually wrote a little metaprogramming language that is capable of generating a lot of the boilerplate for you. Link in my bio, if anyone's interested in looking at it. Your language [0] is an interesting idea. I've done a lot of similar stuff (not at work, I'm very rarely asked to write C code professionally, but I like mucking around with it in my personal time). My own approach to C metaprogramming involves obtuse preprocessor hacks (e.g [1]) and shell scripts that use grep/awk/sed to find macro invocations and spit out new header files as a result. Maybe your way is better. A word of warning though – do you realise your name for your language is an anti-gay slur in several dialects of English? You might end up unintentionally upsetting some people with it. [0] https://github.com/scallyw4g/poof https://github.com/scallyw4g/poof [1] https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,...
- jesse__ 4y ago>> My own approach to C metaprogramming involves obtuse preprocessor hacks I considered doing this, albeit briefly, but decided against it for two reasons: 1) I hate the preprocessor. 2) I found a few projects that did that already. Ultimately, writing a bespoke C parser and scripting language that sits on top of it was kind of an experimental accident. I just started doing it, and it's ended up working out so well that I've kept pushing on it to see where the natural conclusion is. >> do you realise your name for your language is an anti-gay slur in several dialects of English? I actually didn't realize that, thanks for pointing that out. I'll be changing the name.
- benj111 4y ago>anti-gay slur in several dialects of English So is puff. Am I to give up actual puff the magic dragon and sugar puffs? Being English I like the occasional puff on a fag aswell, there's a double entendre for every one there. And then there's the pouffe that I rest my legs on...
- skissane 4y ago> So is puff. Am I to give up actual puff the magic dragon and sugar puffs? Being English I like the occasional puff on a fag aswell, there's a double entendre for every one there. I never told him he had to change it. I was simply checking if he was aware it was an anti-gay slur (in some countries), in case he wasn't – and indeed he said he wasn't. Now he is aware that it is, it is his decision how to respond. He's said he's going to change the name. If he'd decided the opposite, to keep the name – it wouldn't be my place to criticise his decision. If someone else felt it was theirs, that would be between him and them, not me. And, personally, I've never heard ''puff'' been used as an anti-gay slur, but I've heard ''poof'' been used that way many times. But sure, when you tell me it has been, I believe you. If someone insisted that ''Puff the Magic Dragon's'' name be changed because ''puff'' is a slur, I'd think that was silly. But there's a big difference between a 50 year old song, which exists in many people's childhood memories (it does in mine), and a pre-alpha open source project which very few have heard of, where if there'd ever be a good time to change its name, it would be now.