[HN Gopher] Using Zig's translate-C to understand weird C code
___________________________________________________________________
Using Zig's translate-C to understand weird C code
Author : cies
Score : 136 points
Date : 2021-11-03 09:57 UTC (13 hours ago)
(HTM) web link (zig.news)
(TXT) w3m dump (zig.news)
| WithinReason wrote:
| The author translates 1 line of C mess to 7 lines of Zig mess
| which are then manually simplified through transforms. Why not
| just apply transforms to the original C code? Or is this only for
| people who know Zig syntax but not C?
| sobeston wrote:
| Author here, I'm just showing how I did it. It took a few
| minutes to do it for myself - figured it would be worth the 15
| minutes or so it'd take to write it down. Just sharing my
| process, there's nothing too serious about this post.
|
| I've happened to have written a lot of C, but some areas (like
| this one) I'm not as confident in. This way of casting, and
| working with types is very poor syntax in my opinion (I mean
| below in this thread you have people arguing about the spiral
| rule and such, it's obviously a common confusion).
|
| Zig's way of expressing types and pointers is far superior,
| these transformations I made were done quickly and I didn't
| feel like there was any ambiguity or confusion in anything.
| Just a series of simple reductions, there are no "tricks" or
| easy mistakes to make in the code. It feels like a trivial
| proof.
|
| Obviously I am biased, this was posted to zig.news. I thought
| it was a neat showcase of translate-c, and how zig does some of
| these things nicer. I'm not telling everyone that they should
| do what I did, but this works for me and I'm happy to share.
| dosshell wrote:
| Or like use https://cppinsights.io/ ...
| michaelmior wrote:
| That gives the following output which I don't think is much
| more readable. void f(int * a) {
| void * p = reinterpret_cast<void *>(&a);
| ***reinterpret_cast<int *(*)[]>(p) = 1; }
| cies wrote:
| Maybe after several steps of manual processing it is :)
|
| (not sure what steps that should be)
| michaelmior wrote:
| Sure but I don't think C++ Insights really helped.
| abaracadabi wrote:
| I feel like the whole zig language is a mess. Not a mess
| architecturally but syntactically.
|
| Also the whole "no macros, no preprocessor" boasting is kind of
| annoying. Macros are often a powerful way to extend a language,
| and a preprocessing phase is nice way to add some logic before
| compilation. Sure a C preprocessor is limited but it works well
| for simple stuff.
|
| I'm just sick of these head-strong nerds inventing things and
| being like: our thing is better than your thing. Nanner-nanner-
| noo!
|
| I don't understand this desire to create a new language that
| looks like a mix of JavaScript and Rust.
|
| If anything, a cleaner syntax like Python makes more sense--
| give it some airiness. And I say that as a mostly C++
| programmer who has used Python occasionally and appreciates how
| easy it is on my eyes.
|
| Also, my recommendation for folks who want to invent a new
| language: simplicity first. Don't reinvent Rust or C++ because
| I'd rather spend time in either of those before investing lots
| of mental effort in a third option.
|
| For instance, I even consider C better than Zig just because
| the features are small and knowable. Python is better because
| the syntax is simple despite many language features. Go is a
| quaint little beast. Simple simple simple. Don't squeeze stuff
| in there to fit every niche.
| AtomToast wrote:
| Having few features that are small and knowable is pretty
| much the idea behind Zig though. It's supposed to be more of
| a C replacement than another C++/Rust. If you know C you can
| just read through the Zig language spec and see how stuff
| correlates and you just learned zig.
|
| Removing a whole secondary language for macros actually just
| makes the language simpler
| abaracadabi wrote:
| Fair enough, but then there's a whole C compatibility layer
| in zig?
| hansvm wrote:
| Maybe it's still not the language for you or for your
| projects, but for anyone else stumbling across this I don't
| think it's a fair characterization. Would you mind
| elaborating on where you think I'm off-base in the following?
|
| > head-strong nerds inventing things
|
| Let people have their fun.
|
| > preprocessor allows you to add some logic before
| compilation
|
| Zig's comptime feature does address that need. For most use
| cases where comptime and the preprocessor would both suffice,
| comptime has tons of strong advantages (type checked, errors
| traced back to the right lines, ability to write arbitrary
| turing-complete code without unholy syntactic black magic, no
| double evaluation, no need to wrap everything in extra
| parentheses, ....). It explicitly does not address use cases
| where you would actually want a textual preprocessor (like
| making the language look like Fortran), the argument being
| that the burden on reading unfamiliar code would be too high.
| The ease with which you can write arbitrary comptime code
| also enables you to do things like embed lookup tables in the
| binary, which in C you would ordinarily do by copy pasting
| from another tool (or writing constants by hand) or adding
| yet another preprocessor to the mix.
|
| Comptime isn't a clear win over preprocessing in all cases,
| but having written a lot of C and a little Zig, if I had to
| choose one for a hypothetical new project I'd be tempted to
| use Zig just for access to that one feature.
|
| > Syntax
|
| Zig does feel a little clunky to write to me right now, but I
| think that's mainly do to how often I'm using @someBuiltin()
| in Zig when I would be using an operator or an additional
| language feature in something like Python.
|
| That said, the syntax itself is incredibly simple. You can
| check that yourself for Zig [0], Python [1], and Go [2]. Zig
| has far fewer syntactic quirks and abilities than either of
| the other two. You have functions, operators, code blocks,
| types, reserved characters/keywords for builtin stuff, a few
| kinds of literals, and a little bit of syntactic sugar for
| working with structs.
|
| > Don't reinvent C++...for instance, I even consider C better
| than Zig just because the features are small and knowable
|
| To each their own. FWIW, Zig is explicitly and actively
| avoiding becoming a kitchen sink language like Rust or C++.
| They've added a few new features, and I personally find it
| easier to keep track of those than of all the different kinds
| of undefined behavior I might stumble across in C, especially
| since most of those (defer, errdefer, labeled break, ...)
| behave exactly as your intuition suggests they would.
|
| [0] https://ziglang.org/documentation/master/#Grammar
|
| [1] https://docs.python.org/3/reference/grammar.html
|
| [2] https://golang.org/ref/spec
| sedatk wrote:
| You somehow managed to criticize Zig for being something that
| C isn't, lacking macros, preprocessor and whatnot, and yet
| praise Go and Python for being opinionated about their
| features, which don't have preprocessors or macros.
| abaracadabi wrote:
| Not really. I praise Python for simple syntax and Go for
| simplicity of language features.
|
| And I criticize Zig for trying to be another kitchen sink
| (despite having no macros or preprocessor).
| dnautics wrote:
| You do understand that zig started out by removing things
| from C and then "adding rich constexprs" and that's it? It's
| arguably simpler than C, C is actually two languages (C and C
| preprocessor), three if you add make, five if you add cmake
| and autoconf.
| 10000truths wrote:
| make, cmake and autoconf are not not part of the C
| language, they are build tools.
| dnautics wrote:
| In practice you can't be productive in a large project in
| C without make, increasingly cmake (at the very least for
| _reading_ someone else 's c project). And it's part of
| the zig agenda to unify build tools, too. We are now in
| the 2nd decade of the 21st century, it is not strange for
| programming languages to be opinionated about build
| tools, ruby has rake, rust has cargo, js has, well, js is
| a mess, don't copy js, elixir has mix, etc (and those are
| just the languages I know).
| drfuchs wrote:
| What's the motivation for changing the visually appealing C
| syntax "ptr->field" to the whack "ptr.*.field"?
| sobeston wrote:
| ptr.field works.
| abaracadabi wrote:
| Spoken like a true headstrong nerd. A little
| condescending sarcasm to boot.
|
| I'm sure you own a device running an OS with quite a few
| ptr->field's working just fine.
| abaracadabi wrote:
| So it started as "let me reinvent the C" with ugly syntax?
| Little fns in ziggys little async; dots everywhere; try
| expects everywhere; too much punctuation. All the esoteric
| punctuation after loop constructs.
| xigoi wrote:
| Since you like Python's syntax, what's your opinion about
| Nim?
| abaracadabi wrote:
| Honestly, I'm not sure about Nim. I'd probably reach for Go
| before Nim because I hate layers in-between things and too
| many language paradigms.
|
| Main thing is that all these hip new languages will be dead
| in a couple years because they don't do anything all that
| innovative. Making "another C" or "another C target" versus
| Go which focuses on channels, slices, and go routines.
| These other languages that "make a better X" simply will be
| sidelined in production deployments because no matter the
| language there will always be warts.
|
| Really folks keep reinventing the wart in the name of
| something "better".
| rightbyte wrote:
| Or like use the cdecl cli tool ...
| mannykannot wrote:
| As someone unaware of Zig and its translate-C feature, I read
| the article as being about those things, not C.
| kristoff_it wrote:
| > Or is this only for people who know Zig syntax but not C?
|
| It's a blog post hosted on zig.news (so intended for an
| audience that knows Zig), I think we can safely assume that
| it's not meant to be an universal guide for understanding C
| code, but rather the recount of a (somewhat minor) experience
| lived by the author. It's even stated upfront:
|
| > @rep_stosq_void on Twitter posted this strange sample of C
| code, and I wanted to show my process of understanding this
| contrived C code.
|
| "I was presented with this code, and this is what I did to
| understand it"
| bruce343434 wrote:
| That line is honestly trivial for anyone familiar with the spiral
| rule.
|
| Interpret p as a pointer to an array of pointers to integer.
| Since arrays decay to pointers, you triple dereference it to get
|
| 1. the array
|
| 2. the first element of that array
|
| 3. whatever int that element is pointing to
|
| Then set it to 1.
|
| It took me about 5 seconds. I have no idea what that Zig is
| supposed to do on the other hand.
|
| Note: the programmer who wrote that C code was being very obtuse.
| This is essentially just `*a = 1`, the whole p thing is some form
| of baroque code.
| userbinator wrote:
| The spiral rule is incorrect in the general case as other
| comments have already pointed out (the correct way is to read
| in precedence order) but I agree that it was trivial to
| decipher. I guess the opposite could be true for an experienced
| Zig programmer.
| abainbridge wrote:
| I've always thought that the "obfuscated C competition" is
| proof that it is quite hard to obfuscate C. There'd be no
| challenge in most other languages. (I haven't got enough
| knowledge of Zig to know if it is as easy to understand as C in
| general).
| bruce343434 wrote:
| Yes, the IOCCC is always so interesting. Because the language
| is so spartan, there's often only one or two ways to do
| something, because to solve problems on a higher level you
| have to build the scaffolding yourself. Whereas in the usual
| dynamic scripting language you can reuse variables as
| different types, and conjure up a really convoluted approach
| in just a few statements.
| tyingq wrote:
| A fair amount of obfuscated C is also the macro processor.
| bruce343434 wrote:
| I feel lately every IOCCC submission just has an obligatory
| "replace some part of the code with arbitrary identifiers
| which the preprocessor will search and replace back".
| Honestly a cheap gimmick, but it leads to a bigger "whut"
| factor when first seeing the code.
| nicoburns wrote:
| > That line is honestly trivial for anyone familiar with the
| spiral rule.
|
| Perhaps, but the spiral rule is one of the most complicated
| parts of C. Most things are trivial to someone who knows how to
| do the thing.
| dnautics wrote:
| I thought the spiral rule was a tongue-in-cheek "rule" that
| was just made to point out a problem with C.
| klyrs wrote:
| If it's not wrong...
| [deleted]
| bruce343434 wrote:
| You're right of course. Every language has warts, and
| declaration, especially involving pointers, is C's. But once
| you internalize that declaration mirrors usage, together with
| the spiral rule, it will all immediately become clear. There
| is _some_ method to the madness.
| palotasb wrote:
| Any time the spiral rule comes up, I like to point out that
| it's wrong. It is instructive in a way because one learns more
| about C declaration syntax, but it is even more instructive to
| recognize why it is wrong.
|
| The spiral rule works only if there is no pointer to pointer or
| array of array in the type. But take this for example:
| +----------------------------+ |
| +-----------------------+ | | |
| +------------------+ | | | | | +-------------+ |
| | | | | | | +--------+ | | | | | |
| | | | +--+ | | | | | | | | | | ^ | | | |
| | | int * * | | | xxx[1][2][3] | | | ^ | |
| | | | | | | | | | | | | | | +-----+ | | |
| | | | | | | +----------+ | | | | | | |
| +---------------+ | | | | | ---------------------+
| | | | +-------------------------+ |
| +-------------------------------+
|
| The type of xxx is a [1-element] array of [2-element] array of
| [3-element] array of pointer to pointer to ints. I drew a
| spiral that passes through each specifier in the correct order.
|
| Notice that to make the spiral correct it has to skip the
| pointer specifiers in the first three loops. This is marked by
| |. This is not mentioned in the original spiral rules and one
| could be forgiven to parse the expression as xxx -> [1] ->
| pointer -> [2] -> etc. following a spiral that doesn't skip the
| pointers.
|
| The Right-Left Rule is quoted less frequently on HN but it's a
| correct algorithm for deciphering C types:
| http://cseweb.ucsd.edu/~ricko/rt_lt.rule.html
|
| The spiral rule can be modified to process all array specifiers
| before all pointer specifiers, but then you'd have to specify
| that the order to do so is right and then left. At that point
| it's just the Right-Left Rule.
| userbinator wrote:
| The summary is "array and function has higher precedence than
| pointer, and parentheses override all". This explains why
| int *f();
|
| is a function returning pointer to int, while
| int (*f)()
|
| is a pointer to function returning int. Likewise,
| int *a[]
|
| is an array of pointer to int, while int
| (*a)[]
|
| is a pointer to array of int.
| pmarreck wrote:
| between things like this and the pre- and post-
| increment/decrement operators, it's shocking that C code
| generates so many security holes ;)
| Joker_vD wrote:
| But &a gives you "int * *", and then it's dereferenced three
| times. How?
| nyc_pizzadev wrote:
| There is an array cast [] in there. So dereferencing that
| using a plain * takes you back to the original double
| pointer.
| [deleted]
| cecilpl2 wrote:
| The int** is not dereferenced three times.
|
| The cast says "treat this int* as an array of int*, then the
| first dereference says "give me the first element of that
| array", which gets you back the original int*.
| Joker_vD wrote:
| See, this would be a better, shorter, and more insightful
| explanation than that an article full of mechanical
| transformations performed by hand.
|
| "We start with a pointer-to-int called "a", take pointer to
| it and name it "p" (it's a pointer-to-pointer-to-int,
| although we store it in a pointer-to-whatever variable),
| then cast it to some weird type, dereference it _thrice_
| and store 1 into the resulting target. Two questions
| remain: a) what is that weird type? b) we have two levels
| of indirection but three dereferences, how does it work?
| The answer to the first question is that weird type is
| "pointer-to-array-of-pointers-to-int", and it helps us to
| answer the second question: dereferencing a value of that
| type is a no-op in arithmetical sense (but has a type-
| casting effect)."
| eMSF wrote:
| Unless I'm mistaken, this is almost, but not quite correct.
| (Firstly, what's being cast is `p` which is a `void-
| pointer`.) More importantly, the latter cast says to treat
| p as a `pointer to an array of int-pointers`, which means
| that the first dereference actually gives you the array.
| Expressions with an array type in most contexts (including
| this one) decay into pointers to their first elements -
| thus the second dereference gets you the first element of
| the array, an int-pointer, and the third one gets you the
| integer being pointed to.
|
| Also, because this array type is incomplete (it has no
| size), I don't think you could use it in any context where
| it didn't "decay" into a pointer to its first element (you
| can test what your compiler says if you try to measure the
| array's size with the sizeof operator when only using one
| dereference).
| [deleted]
| simias wrote:
| Do the parens in `int * (*) []` do anything? When I saw them I
| immediately assumed that function pointers would be involved,
| but it doesn't seem to be the case and now I'm confused.
|
| EDIT: Uh, apparently you can add parens to casts but not
| declarations.
|
| This doesn't compile: int (*) p = (int *)&i;
|
| This does: int *p = (int (*))&i
|
| I can't quite justify this behaviour.
| foxfluff wrote:
| Yes they do something. They are used to override precedence,
| just as you would in a math expression. Array indexing has
| higher precedence than pointer dereferencing, and
| declarations follow the same precedence that you have in
| expressions. int **p[123]; // p is
| array(123) of pointer to pointer to int int
| *(*p)[123]; // p is a pointer to array(123) of pointer to int
|
| Sometimes people find casts confusing because there is no
| identifier inside. But you can easily read it if you know
| where the identifier would be in an equivalent declaration.
|
| Precedence is usually documented in a man page called
| operator. http://man.openbsd.org/operator
| MauranKilom wrote:
| I think this is just about where the identifier has to go in
| a declaration. Otherwise the spiral rule [or rather right-
| left-rule, as pointed out elsethred] doesn't start at the
| right place (to over-simplify it).
|
| You can in fact have parentheses in declarations, but the
| identifier must be on the inside, not just to the right of
| everything: https://godbolt.org/z/vKzcYMdvK
| rightbyte wrote:
| The confusing part for beginners is maybe to know were to start
| the spiral (or right left scheme) in a cast since there is no
| identifier.
| bruce343434 wrote:
| That's a fair point. In C, you always start at the
| identifier. In case there is none, type declarations can
| contain parentheses*, and just like in math, parens resolve
| first, so it's from innner to outer. So in this case one
| starts with the `(*)`.
|
| * the tricky part is that `()` are also used to denote
| functions. So yeah, it's not always readable. `(*)()` would
| be a pointer to a function returning int (the default type)
| and taking an unspecified amount of arguments.
| steerablesafe wrote:
| Yeah, because the "spiral-rule" is dumb, it's not a spiral at
| all. It's just regular operator precedence.
| MauranKilom wrote:
| The only particularly messy part in the C code there is the
| (int*(*)[]) type cast.
|
| My intuition (because I don't usually have to deal with this kind
| of nonsense) is "cast to a pointer to an array of int pointers".
| cdecl confirms that: https://cdecl.org/?q=int*%28*x%29%5B%5D
|
| So we cast a (pointer to pointer to int) to (pointer to array of
| int pointers) [we can ignore the detour through void*] and then
| immediately dereference through all three layers. Which gives us
| back the only int in the program.
|
| Excellent example of how to be a Three Star Programmer I guess:
| https://wiki.c2.com/?ThreeStarProgrammer
| pmarreck wrote:
| TIL about cdecl.org
|
| I don't code in C day to day but this will help for the odd
| time I need to understand C code!
| _pmf_ wrote:
| Why is taking the address of a parameter legal? Doesn't this
| depend on the ABI and could be a register?
| ximeng wrote:
| https://stackoverflow.com/questions/34519318/c-address-of-fu...
| suggests it's guaranteed by the standard to be OK.
|
| In the standard at
| https://web.archive.org/web/20181230041359if_/http://www.ope...
| 6.5.3.2 Address and indirection operators Constraints
| 1 The operand of the unary & operator shall be either a
| function designator, the result of a [] or unary \*
| operator, or an lvalue that designates an object that
| is not a bit-field and is not declared with the
| register storage-class specifier.
|
| So as the parameter is an lvalue it is guaranteed to work with
| the & operator.
| jcranmer wrote:
| The parameter is copied into a stack variable so that you can
| take its address in such cases.
| pornel wrote:
| Out if curiosity I've checked what c2rust.com thinks about it:
| **(*(p as *mut [*mut libc::c_int; 0])).as_mut_ptr() = 1 as
| libc::c_int;
|
| which is still needlessly complicated, and not even quite
| accurate due to giving the array a 0 size (the as_mut_ptr()
| converts the array back to a C pointer).
| Arnavion wrote:
| >and not even quite accurate due to giving the array a 0 size
| (the as_mut_ptr() converts the array back to a C pointer).
|
| It doesn't seem inaccurate to me, more like the best choice at
| hand. If the C array has a known length, the Rust code has it
| too. Only if the C code has an array of unknown length does the
| Rust code use a 0-length array. Furthermore, if the C code
| indexes the array of unknown length, the Rust code uses
| .as_mut_ptr().offset(...) instead of directly indexing the
| array. So the fact that it represents C arrays of unknown
| length with Rust arrays of 0 length does not cause any problem,
| because the generated code is consistent.
| char foo(void* p) { char (*arr)[] = (char (*)[])p;
| return (*arr)[1]; } char bar(void* p) {
| char (*arr)[3] = (char (*)[3])p; return (*arr)[1];
| }
|
| ... translates to: pub unsafe extern "C" fn
| foo(mut p: *mut libc::c_void) -> libc::c_char { let
| mut arr: *mut [libc::c_char; 0] = p as *mut [libc::c_char; 0];
| return *(*arr).as_mut_ptr().offset(1 as libc::c_int as isize);
| } pub unsafe extern "C" fn bar(mut p: *mut
| libc::c_void) -> libc::c_char { let mut arr: *mut
| [libc::c_char; 3] = p as *mut [libc::c_char; 3];
| return (*arr)[1 as libc::c_int as usize]; }
| [deleted]
| tyingq wrote:
| For those mystified by certain bits of Perl, the built-in Deparse
| module is nice.
|
| perl -MO=Deparse /some/script
|
| Outputs a (usually) more readable equivalent, and also works for
| one-liners that use -e "somesnippet".
| Joker_vD wrote:
| TL;DR: void f(int *a) { int **p =
| &a; **p = 1; // the same as *a = 1; }
|
| Making a concise explanation of how exactly the third
| dereferencing disappeared is left as a further exercise for the
| reader.
| caslon wrote:
| Am I the only one who thinks that Zig's syntax looks incredibly
| ugly? I've never seen a snippet that didn't make my eyes glaze
| over from the syntax. What am I not getting here?
| deaddodo wrote:
| As someone who likes both C and Zig for their simplicity and
| explicitness, I don't find it "ugly".
|
| I _do_ find it practical, with an aesthetically barebones
| approach.
| kristoff_it wrote:
| This particular example is machine-generated code created by
| translate-c, it's meant to be semantically equivalent to the C
| code and even uses language features that you're normally not
| even supposed to use (c pointers).
|
| Reasonable Zig code looks more like this:
|
| https://github.com/riverwm/river/blob/master/riverctl/main.z...
|
| That said I think it's fine if you don't like the syntax. I
| think that some complaints are honestly too superficial to be
| legitimate (like complaining about builtins being prefixed with
| @), but at the same time Zig is often times prioritizing
| explicitness over "good looking".
|
| I personally consider Swift a very good looking language, but
| then I look at all the new features that got added since I used
| it last, remember that I value simplicity over aesthetics, and
| go back to Zig.
| dragonelite wrote:
| Ooh that looks a bit like Rust having quickly scrolling by. I
| have been keeping a eye on Zig but I'm waiting till the
| package manager stuff has been finalized and implemented.
| pjmlp wrote:
| You are not alone, if I miss @ everywhere I will just start an
| Objective-C project.
|
| Then using modules with JavaScript AMD pattern is also not
| appealing.
| AnIdiotOnTheNet wrote:
| If using '@' to prefix builtins[0] is the biggest
| complaint[1] people have about Zig, then Zig must doing a
| pretty good job.
|
| [0] or really any statement at all about aesthetics
|
| [1] I know for a fact pjmlp will also complain about lack of
| built-in gc or ref-count pointer safety.
| pjmlp wrote:
| Paying attention it seems.
| foxfluff wrote:
| Yeah, the syntax doesn't really excite to me too much. Which is
| a shame, because I would like to see a modernized "better C"
| that isn't more verbose than C.
| matheusmoreira wrote:
| Yeah. I think I'm so used to C by now that I just can't handle
| anything that doesn't look like C. It's like my brain just
| ignores text when it can't recognize the C code patterns.
|
| Semantically Zig is really interesting though.
| skocznymroczny wrote:
| I feel similar when it comes to non-OOP code. I don't like
| free functions and global variables just hanging there.
| kristoff_it wrote:
| OOP might have helped popularize usage of the dot notation,
| but namespaces are a different thing.
|
| Zig has no inheritance but everything is namespaced,
| including declaring functions inside struct definitions so
| that you can use them as if they were methods.
| matheusmoreira wrote:
| Yeah, me neither. Names must belong to some namespace, it
| really bothers me when code starts binding common nouns in
| a global context. C lacks namespaces so I use prefixes
| instead. At least this solution doesn't screw up the ABI
| like in C++.
|
| I hate global variables so much it's one of the reasons I
| got rid of libc. Freestanding C turned out to be a superior
| language just because it lacks all the libc cruft.
|
| I ultimately dropped Ruby because of global state. It's
| such a wonderful language but it has one fatal flaw: lack
| of proper modules. The require method just executes Ruby
| source files, modifying the global state of the
| interpreter. It ceased to be a beautiful language once I
| realized this. Python's modules are superior, and the
| Javascript approach is the best one: just a normal function
| that returns a normal object containing exported data and
| functions. Javascript modules are c.ompletely reified.
| cies wrote:
| It is common to make stuff you dont generally want in your code
| (but still need to be able to do because the language is
| sufficiently powerful) look ugly.
|
| Like unsafePerformIO in Haskell.
| capableweb wrote:
| > Am I the only one who thinks that Zig's syntax looks
| incredibly ugly?
|
| It's very subjective what is beautiful or ugly, of course. It'd
| be more interesting if you can offer specific critique rather
| than just calling it ugly.
|
| You're probably not the only one, I for one would call any non-
| lisp "ugly", but again, highly subjective, as many others find
| some C-like code beautiful but other C-like code ugly.
| jcelerier wrote:
| > It's very subjective what is beautiful or ugly, of course.
|
| if it was, something like trypophobia would not exist
| Talanes wrote:
| Trypophobia proves beauty subjective, not the other way
| around. I can find an image beautiful that someone with
| trypophobia finds deeply unsettling.
| capableweb wrote:
| What? Are you arguing that because a phobia (that not
| everyone had) exists, beauty is not subjective but
| absolute? I'd love to see how you measure beauty if so,
| including the "beauty of code".
___________________________________________________________________
(page generated 2021-11-03 23:01 UTC)