6 ms·
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 multip
by xscott 4y ago
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 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.
- thwd 4y agoGo 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 4y agoThat 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 4y agoEven 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 4y agoWhy not solve 100% of the problem? And since pointer syntax is really so rare, you wouldn't even see the suffix operator.
- aardvark179 4y agoI 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 4y agoA 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 4y agoI 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.
- 4y ago
- masklinn 4y ago> 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 4y agoIt'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 4y ago> 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/ https://mdaines.github.io/grammophone/
- properparity 4y agoMeh, 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 4y agoYeah, 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. :-)
- cturtle 4y agoI 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-system/#simple-types https://nathancraddock.com/blog/consistency-in-zigs-type-sys...
- chongli 4y agoI 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 4y agoSame 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 4y agoIMHO, this makes things even more confusing. Now part of the type comes before the id and part comes after.
- xscott 4y agoWhere the core type goes is orthogonal to the notation for affixes (array, function, pointer). Put the type last if you like :-)