[HN Gopher] Cdecl: C Gibberish - English
       ___________________________________________________________________
        
       Cdecl: C Gibberish - English
        
       Author : mkl95
       Score  : 82 points
       Date   : 2022-04-23 09:44 UTC (13 hours ago)
        
 (HTM) web link (cdecl.org)
 (TXT) w3m dump (cdecl.org)
        
       | jonpalmisc wrote:
       | Surprised to see this is the second result when googling "cdecl"
       | despite its naming collision with the calling convention.
        
         | buescher wrote:
         | The unix utility probably predates the Microsoft C compiler.
        
       | xscott wrote:
       | The biggest problem with C's pointer syntax is that it's prefix.
       | If pointers were suffix, perhaps using a character that isn't
       | doing primary duty as the multiplication operator, there wouldn't
       | be any need for a Gibberish to English translator. Maybe think of
       | @ as "address":                   int i;          // variable
       | which is integer         int p@;         // pointer that returns
       | integer         int f();        // function returns integer
       | int f@();       // pointer to function         int f()@;       //
       | funtion that returns pointer         int a[10];      // array of
       | ten integers         int a[10]@;     // array of ten pointers
       | int a@[10];     // pointer to and array         int a[10]()@;
       | // array of ten functions that return a pointer
       | 
       | Of course C is stuck with accidents/decisions made 50 years ago.
       | But a language like Go turns all the declaration syntax on its
       | head and still doesn't solve the problem. Rob Pike's blog post
       | says as much (scroll to the bottom section on pointers):
       | 
       | https://go.dev/blog/declaration-syntax
       | 
       | He suggests Pascal's caret syntax (^), which wouldn't work if it
       | was also used as the binary xor operator.
        
         | masklinn wrote:
         | > The biggest problem with C's pointer syntax is that it's
         | prefix.
         | 
         | The biggest problem with C's pointer syntax is that it's prefix
         | _to the value_.                   int p*
         | 
         | is just as horrible as                   int *p
         | 
         | because in both cases, it obscures that the type of p is
         | "pointer to integer".
         | 
         | C's array and function syntax have exactly the same issue, the
         | entire thing is back-asswards.
         | 
         | Let me demonstrate by converting your examples to a more
         | regular (and logical) language[0]:                   let i: i32
         | // integer         let i: Box<i32> // pointer to integer
         | let i: fn() -> i32 // function returning an integer         let
         | i: Box<fn() -> i32> // pointer to a function returning an
         | integer         let i: fn() -> Box<i32> // function which
         | returns a pointer to an          let a: [i32;10] // array of 10
         | integers         let a: [Box<i32>;10] // array of ten pointers
         | let a: Box<[i32;10]> // pointer to an array         let a:
         | [fn() -> Box<i32>;10] // array of ten functions that return a
         | pointer
         | 
         | Even if you want to shorthand pointers (because you have only
         | one and want it to be built-in), it remains significantly more
         | readable than both C actual and your version.
         | 
         | [0] which doesn't mean perfect e.g. one could argue against the
         | special syntax for arrays
        
           | xscott wrote:
           | You changed your post, so this is a reply to the changes.
           | 
           | Boxes are containers, not pointers or references. Let's look
           | at all of those those using the star syntax and operator
           | inside of an unsafe block? You'll end up with stuff like:
           | fn main() {             let mut a = [0i32; 10];
           | let p: *mut [i32] = &mut a;                  // Rust did NOT
           | solve this problem             let x: i32 = unsafe { (*p)[1]
           | };                  print!("{x}\n");         }
           | 
           | Note the annoying parentheses because they used prefix syntax
           | for pointers, again.
        
             | masklinn wrote:
             | > Boxes are containers, not pointers or references.
             | 
             | There is no actual difference between the two. Pointers and
             | references could have been `Pointer<T>` and `Ref<T>` with
             | the exact same ABI. They're not for UX reasons, one of
             | similarity with C, and one of syntactic overhead.
             | 
             | > // Rust did NOT solve this problem
             | 
             | > let x: i32 = unsafe { (*p)[1] };
             | 
             | That is a debate of precedence, which is a very different
             | concern than what I was replying about, and what TFA is
             | about: the absurd mess that are C type declarations.
        
               | xscott wrote:
               | > There is no actual difference between the two.
               | 
               | Rust has raw pointers, references, slices, and arrays,
               | each with special syntax in type/variable declarations.
               | You're conflating those with Boxes, and it isn't adding
               | clarity to the discussion. It's not like C++ removed this
               | problem now that people use `std::unique_ptr<T>`.
               | 
               | Rust's raw pointer syntax is exactly as bad as C's,
               | because it's the same syntax. Yes, it could use some
               | other syntax like `Ptr<T>` for declaration, but it
               | doesn't. And that still says nothing about what operator
               | syntax to use when dererencing the pointer.
               | 
               | Perhaps it could be:                   let p: Ptr<T> =
               | whatever;         let x = p.deref(); // suffix syntax!
               | 
               | > That is a debate of precedence, which is a very
               | different concern
               | 
               | It's not a different concern, and it's a problem no
               | matter what the precedence. If prefix pointer syntax
               | binds tightly, you'll need parens in one case. If it
               | binds loosely, you'll need parens in another case:
               | *(binds_tightly.foo[10]);  // subscript the array first
               | because star binds tightly
               | (*binds_loosely).bar[10];  // dereference the pointer
               | first because star binds loosely
               | 
               | However, if pointer syntax is suffix (like arrays,
               | function calls, and field accesses), you simply don't
               | need parens in either case. The operations are just in
               | order.
               | 
               | > (the absurd mess that are C type declarations)
               | 
               | Hating on C is trendy, that's fine. But a lot of people
               | are confused about what the problem really is. It could
               | be fixed with one change, but since people can't see it
               | clearly, they change everything. And in cases like Go or
               | Rust, they change almost everything and still don't solve
               | the problem.
               | 
               | A language could achieve C's goal of "declaration follows
               | usage" if pointers had suffix syntax. The prefix pointer
               | syntax is where crazy "winding rules" and "Gibberish to
               | English" translators come in.
        
           | xscott wrote:
           | It's clear you prefer the final type as suffix. That just
           | means you need another syntax or keyword (such as "let") to
           | introduce a variable declaration.
           | 
           | However function calls, array subscripts, and field accesses
           | are the same "kind" of operation as dereferencing a pointer.
           | Put the type last if it makes you happy, but you'll have
           | "horrible" syntax if you don't treat pointers the same as the
           | others:                   something.bar[10]()  // access the
           | field, subscript the array, call the function
           | something.foo@       // access the field, dereference the
           | pointer
        
             | mananaysiempre wrote:
             | > It's clear you prefer the final type as suffix. That just
             | means you need another syntax or keyword (such as "let") to
             | introduce a variable declaration.
             | 
             | Not really? Assuming type and variable names are lexically
             | distinguishable via the lexer hack or, say, capitalization.
             | Here's an LR(1) grammar (in Grammophone[1] syntax) with
             | postfix types and dereferencing:                 stmt ->
             | init ; | expr ; .              init -> decl | decl = expr .
             | decl -> dtor type .       dtor -> ambi | dfun .       dfun
             | -> ambi ( decl ) | ambi ( darg ) | dfun ^ | dfun ( ) .
             | darg -> decl , | darg decl , .              expr -> summ |
             | summ = expr .       summ -> prod | summ + prod .       prod
             | -> post | prod * post .       post -> ambi | efun .
             | efun -> ambi ( expr ) | ambi ( earg ) | efun ^ | efun ( ) .
             | earg -> expr , | earg expr , .            ambi -> name |
             | ambi ^ | ambi ( ) .            # Stub       type -> int .
             | name -> x .
             | 
             | It lacks the comma operator or Go-style grouping of
             | multiple variables before a single type, but adding either
             | one is easy and both is possible, though in the latter case
             | the "ambi" hack would need to be extended. I'm willing to
             | believe you could even do this with LL(1) / recursive
             | descent, but the grammar structure would probably end up
             | rather horrible.
             | 
             | Whether this is a smart idea as far as ergonomics of syntax
             | errors go, I don't know.
             | 
             | [1] https://mdaines.github.io/grammophone/
        
               | xscott wrote:
               | By the way, I'm kind of surprised you didn't use 4-letter
               | names for your stub section at the bottom. How can you
               | suffer the inconsistency in such an otherwise consistent
               | grammar? :-)
        
               | mananaysiempre wrote:
               | I console myself with the idea that they are terminals,
               | and I can see if I forgot to define a nonterminal by
               | checking that no examples contain four-letter words.
               | Choosing a name for the sum nonterminal was much more
               | painful :)
        
               | xscott wrote:
               | My friend and I keep a link to the following because of
               | our similarly neurotic tendencies :-)
               | 
               | https://github.com/timvieira/justified-variables
        
               | xscott wrote:
               | > Assuming type and variable names are lexically
               | distinguishable via the lexer hack or, say,
               | capitalization.
               | 
               | Hah, I appreciate your point and careful reply, but
               | that's quite an assumption :-)
               | 
               | I think using capitalization does qualify as "another
               | syntax", which I was cautious to qualify. And (as you
               | know) the "lexer hack" means you need to declare your
               | types before you use them, which is a bummer for mutually
               | recursive types.
               | 
               | Anyways, I'll concede that you're right in that you _can_
               | do it, but I don 't think I was very wrong :-)
        
         | properparity wrote:
         | Meh, I find C syntax a total non-problem.
         | 
         | Don't think I've ever constructed a more complicated type
         | ('directly' so to speak, i.e without typedefs) than an array of
         | function pointers (array of interrupt handlers, taking no
         | arguments, returning void).
         | 
         | 99.999(repeating)% of the time I use a typedef for function
         | pointers and nothing looks particular complicated.
        
           | xscott wrote:
           | Yeah, I'm not confused by C's syntax any more either.
           | However, when I was learning it (about 30 years ago), things
           | were very non-obvious.
           | 
           | And there are a lot of people who want to throw the baby out
           | with the bathwater. Go is the one that really amuses me here:
           | They broke the "declaration follows usage", which I think is
           | a nice idea, and they _still_ have backwards pointer syntax
           | that needs parentheses to get it right.
           | 
           | > Don't think I've ever constructed a more complicated [...]
           | 
           | Heh, I'm not sure this is a great argument. It's kind of like
           | saying you don't care about the complexity/details in
           | floating point because you've never needed more than
           | integers. :-)
        
         | thwd wrote:
         | Go solves 99% of the problem quite nicely. You don't often
         | deref pointers in Go, because of auto-addressing. E.g. there's
         | no arrow operator as in C, the dot derefs when necessary.
         | 
         | I would say `v := (p.)` could've been the deref operator, but
         | what do I know.
        
           | masklinn wrote:
           | That you don't deref pointers in Go is not what fixes this
           | issue.
           | 
           | What fixes this issue is that, like most other languages
           | which are not C, Go understands that "is a pointer" is a
           | property of the _type_ , not the name / value.
           | 
           | So even if it used C-style declaration, Go would say
           | *int p
           | 
           | or, using a more verbose syntax
           | Pointer[int] p
        
             | kevin_thibedeau wrote:
             | Even C isn't consistent about this:                 int *
             | a, b; // "Property" of the variable            typedef int
             | * pint;       pint x, y; // Property of the type
             | 
             | This has created so much bad legacy code where pointer
             | typedefs are created for no good reason.
        
           | xscott wrote:
           | Why not solve 100% of the problem? And since pointer syntax
           | is really so rare, you wouldn't even see the suffix operator.
        
         | aardvark179 wrote:
         | I find that confusing in a bunch of other ways. I much prefer
         | the non C style of name : type rather than having the name
         | embedded in the middle of the type.
        
           | xscott wrote:
           | A bunch of ways? Maybe it's just confusing because you
           | haven't thought about it before.
           | 
           | There's certainly a tiny trade-off about where to put the
           | core type name. If you have a "let" keyword, your declaration
           | is just a little more verbose:                   int foo[10];
           | let foo: [10] int;
           | 
           | If you don't have type inferencing, you always need to
           | specify the type, and the let keyword and colon are just more
           | noise. I know it's a matter of taste, but I think the first
           | line is cleaner.
           | 
           | But my point isn't about where to put the core type (int).
           | It's that pointer dereferencing is the same kind of operation
           | as array subscripting, accessing fields, and calling
           | functions. So unless you put it in order with the rest, you
           | need ugly parens in any general case:                   //
           | prefix pointer syntax is convoluted and needs parens:
           | (*(x.foo.bar))("arguments")[10];              // grab field
           | foo, then field bar, then derefence pointer,         // then
           | call function, then subscript array
           | x.foo.bar@("arguments")[10]
        
             | aardvark179 wrote:
             | I do actually think about this sort of thing quite a bit,
             | and work with a bunch of people who have thought about
             | these kinds of issues far more than me. I'm firmly of the
             | belief that C style type syntax is never going to not be
             | confusing, and changing the pointer to postfix just changes
             | the places where it's confusing. Personally I think a core
             | problem is that declaration and use are different, and
             | while I agree that dereferencing is generally much clearer
             | as a postfix operator it doesn't solve the confusion on the
             | declaration side.
             | 
             | When reading your examples I found myself having to
             | constantly second guess the precedence of arrays and
             | pointers, and I'm not sure how I would declare the type of
             | a function that returns a pointer to an array, or an array
             | of pointers. I'm happy to accept the extra noise of let
             | because it reduces the overall time needed to understand
             | what code means, and we end up reading code so much more
             | than we write it.
        
               | xscott wrote:
               | > I do actually think about this sort of thing quite a
               | bit
               | 
               | Forgive me for being abrasive above.
               | 
               | > I'm firmly of the belief that C style type syntax is
               | never going to not be confusing
               | 
               | Yeah, C is going to stay C. No fixing that. The bummer is
               | when new languages repeat the problem because they don't
               | understand what causes it.
               | 
               | > Personally I think a core problem is that declaration
               | and use are different
               | 
               | I claim that declaration and use can be the same. In all
               | cases, you read the affixes for arrays, pointers,
               | function calls, and struct/union fields from left to
               | right.                   // declarations         int i
               | int a[10]         int f(int arg)[10] { ... }         int
               | p@                  // usage         print i
               | print a[1]         print f(1)[1]         print p@
               | 
               | You could change the declarations to use a "let" keyword,
               | but I don't think it'll make them more readable while
               | maintaining the goal of "declaration follows usage".
               | 
               | > I'm not sure how I would declare the type of a function
               | that returns a pointer to an array                   //
               | function, pointer, array         int f()@[10] ...
        
         | cturtle wrote:
         | I agree here. I really like what Zig has done with pointer
         | types. I wrote about it here [0]. What I like most is that all
         | the types consistently read from left to right, and there are
         | types that encode the cardinality of a pointer unlike C. I
         | wonder what you or others think about Zig's pointers syntax.
         | 
         | [0]: https://nathancraddock.com/blog/consistency-in-zigs-type-
         | sys...
        
           | chongli wrote:
           | I can't even get past the simple values syntax:
           | var value: i32 = 0;
           | 
           | This looks like you're assigning the value 0 to i32, a type.
           | It's nonsensical. Also what is the purpose of the var
           | keyword? Why not put the type there?
           | 
           | The array syntax makes even less sense!
           | 
           | I think what you're missing in your examples is how the
           | values are used after declaration. C syntax was designed so
           | that declaration resembles use. The reason array subscript
           | brackets come after the variable name is because that's how
           | you access elements of the array:                   int
           | arr[3] = { 12, 76, -42};         arr[1] += 4;         if
           | (arr[2] < 0) { printf("it's negative!\n"); }
        
             | glouwbug wrote:
             | Same goes for pointers.                   int *foo = &bar;
             | *foo = 1;
             | 
             | As far as syntax goes, tying the pointer to the identifier
             | doesn't make sense to me, at least. Pointers associated
             | with types makes much more sense:                   int*
             | foo = &bar;
             | 
             | C++ thinks so too.
        
         | marcodiego wrote:
         | IMHO, this makes things even more confusing. Now part of the
         | type comes before the id and part comes after.
        
           | xscott wrote:
           | Where the core type goes is orthogonal to the notation for
           | affixes (array, function, pointer). Put the type last if you
           | like :-)
        
       | andi999 wrote:
       | Everytime I read the chapter in K&R, I first get a headache, then
       | I think I understand it, then I think I dont understand it, but
       | it is something very easy (just follow the circle rule). Then
       | next year the cycle repeats.
        
         | MauranKilom wrote:
         | Except the circle rule doesn't really help unless you know when
         | to skip stuff because you're not done on the other side. The
         | right-left rule instead is precise and correct.
         | 
         | See https://news.ycombinator.com/item?id=5080081 and
         | https://web.archive.org/web/20190714064656/http://cseweb.ucs...
        
           | spicybright wrote:
           | I'm actually surprised I had to read this far down the
           | comments to see a reference to the right-left rule. Is that
           | not common knowledge anymore for C programmers?
        
       | jbandela1 wrote:
       | If you are using C++, you can make this a lot easier using this
       | template alias                  template<typename T>        using
       | ptr = T*;             ptr<const char> str:        const ptr<char>
       | pchar;        ptr<void(ptr<const char>)> fun_ptr;
       | 
       | Basically this simple trick removes most of the confusion, and
       | you can read the types left to right.
        
         | jcelerier wrote:
         | It also makes function pointer types much clearer:
         | using func_ptr = float(int);              // Given
         | float some_func(int);              // Then         func_ptr* a
         | = &some_func;              // Also         func_ptr* b = []
         | (int x) { return 0.f; };
        
       | SaulJLH wrote:
       | Site is very sparse, I "think" I know what this is for, but I'm
       | probably wrong, can anyone explain?
        
         | krossitalk wrote:
         | I think of it as a programmers aide much like regex101.com or
         | crontab.guru
        
           | Tempest1981 wrote:
           | Also:
           | 
           | - https://fuckingfunctionpointers.com/
           | 
           | - http://fuckingblocksyntax.com/
        
         | EMM_386 wrote:
         | Type in some syntax in the C programming language, it will
         | translate it into English to help you get a better mental
         | picture of what is going on.
        
           | SaulJLH wrote:
           | That's literally it? Thought might be more to it, hmm still
           | interesting, thanks!
        
             | theophrastus wrote:
             | Well it goes the other way too. English to arcane C
             | declaration. And that's what many of us have used it for
             | decades (and decades...). Try:                   declare
             | xyzzyx as pointer to pointer to array 153 of const double
        
       | buescher wrote:
       | Yeah, thanks to C pointer syntax, there's a big faction of
       | programmers that think that understanding indirect addressing is
       | a major intellectual accomplishment.
       | 
       | I don't have anything better to offer though - C pointers make a
       | whole lot possible in a very terse way.
        
         | MaxBarraclough wrote:
         | > I don't have anything better to offer though - C pointers
         | make a whole lot possible in a very terse way.
         | 
         | Terseness isn't a virtue in and of itself. If terseness helps
         | readability that's great, but unreadable terse code isn't
         | something to aim for.
         | 
         | One of the redeeming aspects of the C pointer declaration
         | syntax is that hairy declarations can be decomposed using
         | typedefs. Improving readability at the cost of reduced density
         | is a good trade.
        
         | glouwbug wrote:
         | C feels like it was built against the proverbial managerial
         | clock to prevent Rob Pike from rewriting yet another text
         | editor in PDP11 assembly
        
         | mhh__ wrote:
         | The fact that people write whole books about them probably
         | doesn't help either.
        
         | cryptoshit wrote:
         | Easiest way to understand pointers is to learn it in assembler
         | first, which is closer to what's actually going on. Then when
         | learning the C syntax, it all fits into place neatly, rather
         | than being mysterious asterisks and so on.
        
           | buescher wrote:
           | Yes, but knowing assembly language won't help you through
           | complicated C declarations of the sort used to demonstrate
           | cdecl.
        
           | mwattsun wrote:
           | I can't imagine being a programmer without starting out by
           | becoming proficient in assembler. It takes a lot of mystery
           | out of programming languages because you know whatever the
           | syntax is it all ends up as branches, calls, stacks,
           | registers and memory accesses.
        
             | derefr wrote:
             | I would heavily recommend first learning assembly with an
             | ISA that's _not_ x86, though. The x86 ISA has just as much
             | cruft as C, e.g. all the overlapped increasingly-sized
             | registers, the now-irrelevant segment registers, etc. And
             | x86 has some stuff that 's not legacy, but which makes
             | learning much harder -- e.g. assembler-level instructions
             | like LEA that compile to a set of entirely-different
             | opcodes of different shapes depending on what you do with
             | them. (IMHO, for best learning, you should be able to
             | easily _disassemble_ object code in the ISA  "by hand" back
             | into assembly, with just an opcode chart for reference.
             | Doing this with arbitrary x86 assembly would be quite the
             | challenge.)
             | 
             | I'm not sure what to recommend as an optimal learning ISA,
             | though. Ideally something that's mostly RISC, with a small
             | but unified memory model, and no virtual memory, and which
             | has all the math ops that embedded RISC ISAs never bother
             | to implement. (In the end, probably no real architecture
             | ever made that exact set of compromises, and so it'd have
             | to be something made up -- some kind of bytecode abstract
             | machine ISA designed specifically to be used in education,
             | an ISA equivalent to languages like Logo or Scratch, or
             | OSes like Minix.)
             | 
             | In practice, I think I would recommend learning assembler
             | by programming for the original Gameboy's LR35902 ISA
             | (which is almost, but not exactly, the Z80/8080 ISA). Not
             | because it's the optimal learning ISA per se, but because
             | it gives you a complete machine including graphical video
             | output, with a compact unified address space (no separate
             | IO-register read/write ops), modern tools that target it,
             | and good emulators that almost always include built-in
             | debuggers and memory viewers -- where, as well, the memory
             | is small enough to get it all on-screen and have room left
             | over.
             | 
             | Oh, and unlike other ISAs, where people either never hand-
             | write code for them, or where no hand-written code survives
             | in a readable form, with LR35902 we have several complete
             | projects with hand-written assembly codebases (or, well,
             | reverse-engineered + annotated disassemblies _of_
             | originally hand-written assembly -- just as good!) around
             | to be studied. And, of course, they 're just the sorts of
             | things kids interested in computers would be interested in
             | studying the workings of: video games!
             | (https://github.com/pret/pokered is a shining example
             | here.)
        
               | mwattsun wrote:
               | I feel very lucky I learned on the 68000 in the original
               | Mac. If a CPU could ever be considered a work of art, it
               | was the 68000
        
               | [deleted]
        
       | emmanueloga_ wrote:
       | what's stopping a new version of C from defining a better
       | declaration syntax? (better = easier to read, write and
       | understand)
        
       | teddyh wrote:
       | You know, you could just                 apt install cdecl
       | 
       | And then do it on the command line. But hey, let's turn
       | everything into a web service, with monitoring as a bonus
       | feature.
       | 
       | (Repost of https://news.ycombinator.com/item?id=11164685)
        
       | derw-lang wrote:
       | As part of Derw, I've added an English output generator[0], with
       | the idea that the output could be used to improve error messages
       | or help developers understand unfamiliar syntax, particularly for
       | those more used to Javascript rather than ML. Similar concept to
       | cdecl
       | 
       | 0 -
       | https://mobile.twitter.com/derwlang/status/15057099820246712...
        
       | marcodiego wrote:
       | It would be good if there was something similar for the other
       | constructs, like statements, loops, calls... That would become a
       | "C" to natural language translator. I wonder how hard it is to
       | turn:
       | 
       | for(int i = 0; i < x; i++)
       | 
       | into "declare 'i' as int and assign '0' to it; while 'i' is
       | smaller than 'x' do ... and increment 'i'."
        
       | flohofwoe wrote:
       | When you arrive at the sort of gibberish that you need a tool to
       | make sense of it, it's time to split the declaration into several
       | much simpler typedef building blocks (especially recommended for
       | function types).
       | 
       | Also for what it's worth, this isn't 'human readable' either, or
       | is it?:
       | 
       | "declare foo as pointer to function (pointer to const void)
       | returning pointer to array 3 of int"
        
         | bool3max wrote:
         | That seems perfectly readable to me, sans the part in the
         | parentheses.
        
       | mattiii wrote:
       | the c pointer syntax is perfectly fine after realize you its just
       | the operator precedence rules but in reverse.                 int
       | foo, *bar, baz(), (*zyzzy())();
       | 
       | i do not understand why blowhard rob pike types dislike it. its
       | exactly the kind of elegant you would think they would like
        
       ___________________________________________________________________
       (page generated 2022-04-23 23:01 UTC)