As I recall, C# supports this in a completely sensible way by distinguishing a[i,j] and a[i][j]. If I understand right, in C, a[i][j] means what C# would spell a[i,j], which does seem rather surprising and inconsistent
Not quite. As GP mentions, a[i][j] might mean either, depending on what the type of a is:
(a) If the type of a is “array of length N of pointer to (say) char” (declaration: char *a[N]), then a[i][j] means the jth char in the contiguous block pointed to by the ith pointer. In C#, this is what you get with an array of arrays.
(b) If the type of a is “array of length N of array of length M of char” (declaration: char a[N][M] — sic!), then a[i][j] means the jth element of the ith element, aka the (i*M+j)th char in the single contiguous memory block. In C#, this is what you get with a two-dimensional array.
The way this happens is a bit subtle:
(a) The value a, of type “array of size N of pointer to char”, first decays into “pointer to pointer to char”, then a[i] retrieves the ith “pointer to char” starting from it as a base, then in turn a[i][j] retrieves the jth “char” starting from that as a base.
(b) The value a, of type “array of length N of array of length M of char”, first decays into “pointer to array of length M of char” (sic!), then a[i] retrieves the ith “array of length M of char” starting from it as a base, which then decays into “pointer to char”, then a[i][j] retrieves the jth “char” starting from that as a base.
NB: There are no implicit references here, unlike in C#; in part (b), a is an N*M-byte chunk of memory and a[i] is an M-byte piece of it.
The docs/summaries part I can get behind if reviewed and improved by a human, but at least when working in pre-existing codebases, I tend to steer models away from writing comments, because I find that almost all comments they write are "not even wrong".
That is: they either reiterate what the code does, or would if the code were slightly clearer, or they tell half truths that are more confusing than helpful. Mostly they fail to emphasise the salient things, like the why over the what, that are not obvious from the code.
The idea that we could create a world where 'a big part of the future of hobbies and entertainment' is people listening to meaningless words made up by machines that help them feel good about themselves sounds horrifying. How could anybody feel ok about that? What would it say about the society we've built?
It would say that society changes, and people who were not used to a new world get upset about it, as it has always been throughout the entire history of humanity.
We were used to having psychologists and doctors in person, now the most common form is to have it through apps, and the younger generation does not care, it's in fact more efficient to get a prescription that you like than to spend time going places and having in-person meetings. But older generation finds it hollowing out and horrifying.
You need to accept that society moves on, and it can look different from your perspective.
A looooot of assumptions here. We have yet to see any of these brave new ideas actually work.
Therapy has never been more available, yet mental health is through the basement.
I’m also not seeing any evidence that young people are the driving force behind turning the world to shit. Every Gen Z person I know craves authenticity, connection, and meaningful work. All of this is the opposite.
It's interesting how every time this argument is made, its about subjective experiences of 'craving'. If this was the objective reality, we would have a majority of Gen Z engaged in movements, social groups and other concepts that would help them fulfill their 'cravings'.
However, it seems to not be the case, it seems like they prefer to spend their free time to doomscroll, or sit at home, and engage more in parasocial relationships that perhaps can be more on their terms, on their timeframes, and with their opinions.
That’s one explanation. The other explanation is that young people feel powerless to change anything, and that they are hooked against their will on deliberately addictive ad delivery platforms.
The more alarming conclusion here happens to be backed by a lot of science, unfortunately, so it’s not easy to dismiss.
In this case, the user is deciding that they choose what progress is. I am saying, that people who use the tool and value the utility of it decide what is progress. If people listen to the podcast, or use doctors in the phone because it provides them any value, it will be a change and a perceived progress for them.
If the generated podcasts did not bring any value to the users, such as validation, or engagement, they would not use them, and there would be no change.
> If the generated podcasts did not bring any value to the users, such as validation, or engagement, they would not use them, and there would be no change.
Your meaning and your truth, not necessarily other peoples who find their meaning and truth in other things.
Go to China, or Congo and you will find that the public might hold a different version of some truths than you do.
We had religions dominating the world order for thousands of years, which projected their versions of the truth onto their societies.
If we would extrapolate that to today and to your opinion, it would be that everyone in the middle ages actually had it all figured out, they knew that the religious texts about splitting oceans or the moon were fake, and were all just playing along with it for the social structure.
Maybe it just happens that the LLM-generated stuff is the next thing in this iteration.
> Your meaning and your truth, not necessarily other peoples who find their meaning and truth in other things.
The makers of those AI podcasts explicitly stated they were unconcerned with whether their content was factual, so this is not comparable to people that actually thought they were right. But if you're arguing that listeners of those podcasts will believe that made-up slop is truth, that that's the "their truth" you're talking about, then yes, that is exactly what I meant by "collapse of truth".
I'm not convinced it really works well in typescript. the lack of nominal types requires you to remember some pretty hacky incantations if you want something like a newtype wrapping a primitive type
my experience is that ocaml is more powerful than rust for enforcing this sort of type safety, because you have gadts that give you more expressive power, and polymorphic variants and object types (record row types) that give you more convenience. and the module system and functors of course.
you also avoid some abstraction limitations/difficulties that come from the rust borrow checker for places where garbage collection is just fine
It really feels like we’re solving the wrong problem sometimes. If a bad type can crash your application, sure, type safety is one answer but I have to admit I like the erlang approach; if something unexpected happens crash the process (not os process, erlang process) which has a very small blast radius on a well architected system (maybe doesn’t even fail the individual request that caused it). I wish more languages had this let it crash philosophy, it really allows for writing code exclusively for the happy path, safe in the knowledge that a -1 where a “string” should be isn’t going to take down production.
Somehow, it feels like a better solution than these complicated type systems. Does any other language do this outside BEAM?
When working on large, important software, crashing is not the worst thing that can happen; corrupting user data and/or allowing unauthorized access is.
The point of using the type system to do something like distinguish between sanitized and unsanitized strings is specifically to prevent these kinds of security breaches.
Erlang was designed for traditional telecom, where reliability of connections was the biggest factor, not security. I fail to see how Erlang’s approach can deal with the issue of security breaches or corrupted user data.
In a way I agree with you, and I'm not sure that what popular languages embrace or make it easy to follow this philosophy. My sense is that Erlang is still the leader.
But I did want to add something the article also touches on: types can be not only about ensuring safety or correctness at runtime, but also about representing knowledge by encoding the theory of how the code is supposed to work as far as is practical, in a way that is durable as contributors come and go from a codebase.
Admittedly this can come at the cost of making it slower to experiment on or evolve the code, so you have to think about how strongly you want to enforce something to avoid the rigidity being more painful than valuable. But it's generally a win for helping someone new to a codebase understand it before they change it.
Edit: another thought I had is that type mistakes do not always causes crashes. Silent corruption can be much more insidious, e.g. from confusing types which mean something different but are the same at the primitive level (e.g. a string, number or uuid)
For me this has been a life saver being the only back end developer at the company. I don’t have the energy nor time to think about every possible scenario, especially not the mobile client sending random strings to something that should be parsed as an uuid (has happened more than once).
By letting it crash I can have a look at the traces at my own leisure and a lot of them I never fix, because I don’t have to.
The amount of silencing (implementer error, but quite prevalent) of errors I’ve seen in typescript codebases are horrifying. Essentially ”try happy path, catch everything else and return generic error”, the result is is mostly the same for the user, but night and day for me who is trying to fix it.
Or have a static type system and something like BEAM. I'm not sure why this is a one or other approach, both are useful and unfortunately it doesn't seem like any languages include both. Gleam exists but doesn't really integrate with BEAM, it seems to have its own way of doing things that are more akin to Haskell, given its origins.
That's literally the entire point of BEAM lol, that's one of the main reasons it was made, to patch code that breaks after letting it fail. BEAM was designed for as little downtime as possible and if you don't need that then just use another more ergonomic language than Erlang or Elixir.
>if something unexpected happens crash the process
There are some expectations where that's a reasonable response to a violation, but there are many expectations where the violation implies a bug elsewhere and crashing the process will do nothing to address that that wouldn’t have been better accomplished with stronger compile time checking.
EDIT: previously the example in the parent comment was:
type NewType<T> = T & { __brand__ : Symbol }
---
This seems wrong; the type spelled `Symbol` refers to the boxed interface for symbols[0]. I suspect you meant to write `unique symbol` there, but it can't be used in that position.
I'm not sure if `NewType` in your comment is supposed to stand in for a specific newtype (in which case it probably doesn't need to be generic[1]) or if it's supposed to be a general-purpose type constructor for any newtype (in which case it should take a second type parameter to let me distinguish e.g. `EmailAddress` from `Password`[2]). The use of `unique symbol`s is also only really necessary if you want to keep the brand private to force users to go through a validation function or whatnot, otherwise you can just use string literal types.
I agree these incantations aren't big problems (it all falls out naturally from knowledge of TypeScript's type system, and can be abstracted away as per my comment in [2]), but the fact that you goofed in the very comment where you were trying to make that point is causing me to second-guess myself.
Right. Besides getting this incantation right, as gp did only after editing their comment, you also have to cast to create values of NewType. But generally you want to avoid casting in typescript if you care about type safety, so now everybody has to remember the rule that in this particular circumstance it's the right thing to do.
There are helper libraries to ease this (zod supports branded types, I think?), but I guess my general point is that while typescript might give you the ingredients you need to implement type safety in cases like this if you try really hard and remember all your rules everywhere, it doesn't come naturally so it's hard to maintain at scale.
I was on the Tube and wanted to get my reply in before entering a tunnel. I already corrected it whilst I was underground.
I think the point still stands - is this really a big problem? I guess I couldn't recite the syntax from memory, because I usually use a utility type for this
I think the send/recv with a timeout example is very interesting, because in a language where futures start running immediately without being polled, I think the situation is likely to be the opposite way around. send with a timeout is probably safe (you may still send if the timeout happened, which you might be sad about, but the message isn't lost), while recv with a timeout is probably unsafe, because you might read the message out of the channel but then discard it because you selected the timeout completion instead. And the fix is similar, you want to select either the timeout or 'something is available' from the channel, and if you select the latter you can peek to get the available data.
Single core performance isn't just clock frequency. It must be multiplied by average IPC, but really it's more difficult since you have to account for factors like new SIMD instructions. Effective IPC improvements are where a significant fraction of single core speedup came from in this period
The visitor pattern is a technique for dynamic dispatch on two values (typically one represents 'which variant of data are we working with' and the other 'which operation are we performing'). You would not generally use that in recursive descent parsing, because when parsing you don't have an AST yet, so 'which variant of data' doesn't make sense, you are just consuming tokens from a stream.
My guy... Do you think that parsers just like... concat tokens into tuples or something....??? Do you not understand that after lexing you have tokens (which are a "type") and AST node construction (an "operation") and that the grammar of a language is naturally a graph.... Like where else would you get the "recursion" from....
If that doesn't make sense I invite you to read some literature:
> makeAST():
> asks the tokenizer for the next token t, and then asks t to call the appropriate factory method the int token and the id token call makeLeaf(), the left parenthesis token calls makeBinOp() all other tokens should flag an error! does the above "smell" like the visitor pattern to you or not? Who are the hosts and who are the visitors?
I did continue reading the book (not the original author of that reply) but I do think it is distracting for newbies. I had to come back to this page over and over again to recollect memory about the pattern, because I usually read it one chapter or a few sections every week, so every time I had to remind myself how this visitBlah() and accept() pair works. I really think a big switch() (or anything that works but is simpler) would be a lot easier to understand.
The other reason I dislike this kind of stuffs is that I have someone in the team who really likes to use patterns for every piece of code. It's kinda difficult to tell whether it is over-engineering or not, but my principle is that intuition always beats less lines of code (or DRY), unless it is absurdly more lines of code or repetition. And to test that principle you just grab a newbie and see which one makes more sense to him.
> I really think a big switch() (or anything that works but is simpler) would be a lot easier to understand.
It's much easier conceptually to implement this using recursion instead of a while loop and a token stack (it's basically DFS). So I disagree with you there.
> The other reason I dislike this kind of stuffs is that I have someone in the team who really likes to use patterns for every piece of code. It's kinda difficult to tell whether it is over-engineering or not, but my principle is that intuition always beats less lines of code (or DRY), unless it is absurdly more lines of code or repetition. And to test that principle you just grab a newbie and see which one makes more sense to him
I'm with you - I really don't give a shit about patterns (which was my whole original point - who cares). But that last part I don't agree with - systems code (like a parser) doesn't need to be legible to a noob. Of course we're talking about a textbook so your probably right but like I said most production parsers and AST traversals are written exactly this same way. So anyone learning this stuff hoping to get a job doing it should just get used to it.
> so every time I had to remind myself how this visitBlah() and accept() pair works. I really think a big switch()…
This is just and alternative implementation of the visitor pattern. Whether you implement it using dynamic dispatch or a switch or an if stack its all the same pattern…
I see that you've found an example of how recursive descent parsing actually can be implemented with the visitor pattern, which I've never come across before, and I didn't read it carefully enough to understand the motivation - but that doesn't mean they are the same thing - the recursive descent parsers I've seen before just inspect which tokens are seen and directly construct AST nodes
as an adendum, the reason I don't understand the motivation is that the visitor pattern in the way I described it is useful when you have many different operations to perform on your AST. If you have only one operation on tokens - parsing into an AST - I'm not sure why you need dynamic dispatch on a second thing, the first thing being the token type. Maybe the construction is that different operations correspond to different 'grammar rules'?
You're overindexing on maximally generic visitor pattern. If you have one type of visitor but nonetheless dispatch based on type that's still visitor pattern.
EDIT: to be honest who even cares. My initial point was why in the hell would you stop reading a book because a particular "pattern" offends you. And I'll reassert it here: who cares whether a recursive descent parser fits the exact definition of visitor pattern or not - you have members of a class that do stuff (construct AST nodes) and possibly track other data and then call other members. I usually call that a visitor class even if it's the only one that ever exists <shrug>
Ok, that's true, but my claim is that recursive descent parsing does not have to use the visitor pattern and indeed using recursive descent parsing is not the same as using the visitor pattern (you can do the former without the latter and I claim that you usually do)
I think I'm missing something here. if you have a grammar rule R with children A and B, and a function in your recursive descent parser that corresponds to R, why can R not call the parser functions for A and B, which return AST nodes themselves, and then construct another AST node using the result of those? Where was the visitor pattern required here?