> but C is also hands down the best language to get a sense of how the computer is running your program.
C is in an odd position right now to argue it is how the machine is really working. Computers are more complicated since bigger caches entered the picture. Hell I do not think even ASM is a good approximation on how machine really work given the data dependencies will make stuff being processed in parallel instead of sequentially.
What you could argue is that C is the archetype for an imperative language procedural language with a clean mapping to ASM. That is different than how the machine works. Simpler architecture have less distance between their ASM and what is really hapenning.
> C is in an odd position right now to argue it is how the machine is really working
This is exactly what makes it great for teaching. Students don’t need to know actual arch or hardware details. They just need to grasp the core concepts of what is happening on the hardware.
For that purpose the ideal teaching language is lower level than python, javascript, Ocaml, etc without diving into nitty gritty arch specifics.
> an imperative language procedural language with a clean mapping to ASM
C really isn't as great for this as is often suggested either, not for decades at least
K&R's original compiler on an actual Digital machine from that era makes the case best, but remember this is the era when if you hot loop over modifying a variable your compiler is going to emit memory stores for each iteration - because that's what you wrote, isn't it? No modern C compiler would do this because it's awfully slow.
Likewise that iteration of C doesn't have what you'd recognise as function prototypes, it doesn't care whether your function takes six arguments, here are six arguments the first two are integers, good luck with that. In assembler that makes sense, but you don't do that in modern C either.
C is still a close-to-the-metal language, but it is programming an abstract machine and it is important that the programmer knows that's not really how the machine works, if you want to learn about the machine you will need to write at least assembler and possibly just go learn electronics. Good luck.
> C really isn't as great for this as is often suggested either, not for decades at least
It's good enough for undergraduate teaching. In C, you can easily explain the relation between a struct definition and its layout in memory. It's much more difficult in Caml (what's the relation between an algebraic datatype and its layout in memory?) or Java (which introduces pointers that you never asked for).
We (University of Paris-Cité) are teaching Java in first year, then C and Caml in second year, with seemingly good results.
If the compiler optimizer doesn't reorder the struct fields depending on which flags or pragmas are used, and you could teach that as well in other compiled languages, ignoring the market size of each language.
The C optimizer is not allowed to re-order, so, unless you've specifically used some implementation override you know exactly how C will lay out basic types.
Likewise packing isn't allowed by the standard, so you'd again only need to talk about packing if you want to.
This seems like a reasonable place to start. Like the way driving school teaches you a U-turn but not a J-turn. Is a J turn actually a thing you might need? Maybe, but it's definitely not where we should start.
Reading the documentation, I just notice lots of familiarity with my own solution using haskell and gi-gtk4. I think that anybody that read https://bichanna.github.io/posts/tea-time/ will get the same ideas. This basically boils down to:
1. Use a native widget system such as GTK (I evaluated Qt too)
2. Wrap widgets into a component datatype that is aware on how to update the widget when the props/model changes.
3. Define a dispatch closure that is passed to widget that accepts messages. Actions on the widgets should use this instead of mutating a reference.
4. That dispatch closure is component aware and it will call the update function on all the widgets.
There are some extra complications for long running computations but the model is a good base to build on. I wish the best to the relm team, most likely I will use them when programminmg in rust.
I haven’t used Haskell much. I thought to try it with agentic help, but then read this post that its compilation times might make it unviable. But what’s your experience?
Mathematician (optimization) turned software engineer, 5+ of experience. I believe that with good mathematical models we can ship better code and products. I have good experiences doing front-end development but I am a better backend developer. I have a slight bias to functional programming. I pride myself on shipping products that our users have loved. If you work on a domain with good data, I would love to take a look.
Mathematician (optimization) turned software engineer, 5+ of experience. I believe that with good mathematical models we can ship better code and products. I have good experiences doing front-end development but I am a better backend developer. I have a slight bias to functional programming. I pride myself on shipping products that our users have loved. If you work on a domain with good data, I would love to take a look.
this is an interesting observation. I have various explanation on why that might be the case.
on a commercial setting there is more pressure to deliver code then to review it. combine it with the lack of our benevolent dictator for life (that has been on the project the world life cycle, not just recently), there is no one with power to actually say no to changes.
language geeks are novelty seekers. they will use every feature of their language . stronger languages have more features to abuse. so on a commercial setting you will have all the features being used without much thought an architectural design that says you that no, we shouldn't do that.
that's also why I think projects with benevolent dictators for life on the open source ward don't fall these in paths even though they use languages that are stronger.
you could restrict yourself to use languages that have only one way to be used, so python as it was originally. or use a language little abstraction power. but you will suffer in other ways. as abstraction power is genuinely useful. accidental complexity has a way of getting in by expressive means or by social means.
Yep. Even with "old drugs" the new programs using those give significantly better chances than with the old programs. Year of data collecting sure have given results.
I had testicular cancer in 2006 in Chile. I was a teenager so I went with the public system (AUGE back then). My parents friends were giving the condolences for my soon to occur death. The doctors had a laugh as in the last 10 years (by then) my chances of dying of it became basically nil.
Last year we hired a new guy at $JOB that 1 week after being hired was diagnosed with testicular cancer too. He really thought he was gonna die. He was at stage 4 (which is a highly zone specific metric). Last time I saw him he is still coding and more bald. It was more difficult to sort payments than the treatment as he is in the US.
Most testicular cancer is now reliably curable by harsh but effective chemotherapy. Mission accomplished? Not quite. Curative drugs such as cisplatin are scarce. Their patents are long-expired, so they're less profitable to manufacture, so manufacturers don't manufacture them. Hooray for the free market.
> It's because it's objectively harder to reason about performance with lazy evaluation as performance becomes non-local.
This is off-repeated, but has two interpretations. If you mean performance as in number of steps needed to reduce an expression, lazy evaluation is superior to strict evaluation. If you mean the indirection needed to access a thunk, well someone -- either the producer (strict) or the consumer (lazy) -- had to do that anyways. If GHC can determine that the demand for the value is strict, it won't generate the thunk in the first place. Instead it will provide a specialize calling convention via the worker/wrapper transform, which is standard. On the un-optimized case we still have dynamic pointer tagging (which is a transgression on the T of the STG machine) which serves as a tag to check whether we are dealing with a value or a thunk quickly. So if by non local performance you mean the indirection, modern GHC shows that is not true.
If you means that space leaks are a non-local property and they affect performance, well you are sort that right. But as with all programming, we have defensive patterns against that.
There are 2 types of space leaks: liveness leaks and strictness leaks. Only the first one are a non-local property, ie the appear as a consequence of the composition of different code pieces. But given the default purity of Haskell, those can only appear on long lived data references with are syntactically marked by:
- IORef, MVars, TVars
- get/put pairs over a state environment
So what you do is to specify in the types that values stored on those environments are to be evaluated before being stored, so references don't leak. I speak about this on this
This reply neatly captures that functional programming tends to consider performance/efficiency in terms of number of reductions. Sadly other programmers tend to view it as wall clock time and the two don't correlate very well.
If a client comes to me and says one of the parts of their application was slow, and I come back to them saying I lowered the number of reductions with zero change in the wall clock time, they'll fire me, and rightly so.
To be nitpicky, wall clock time isn't the same as CPU time or power draw. You can increase efficiency at the same time as you increase wall clock time because they aren't necessarily the same metric.
I think you're overcomplicating this. In a language with lazy evaluation, you can't know what gets evaluated without looking at the whole program (in the worst case). It's in that sense that performance becomes non-local.
Here's a simple specific example. Take the Haskell expression [1..n], for some big n. There is no general answer to the question "what would be the performance implications of replacing [] with [1..n]" – it depends on the context of [].
In a strict language, you can say (roughly at least) that replacing [] with [1..n] will add at minimum a certain number of extra CPU cycles and a certain amount of additional allocation. This kind of reasoning is pretty rough and ready, but it is accurate enough to be useful for reasoning about performance in practice.
I note that Simon Peyton-Jones has said these exact words in a public talk:
"Laziness makes it much, much harder to reason about performance."
I think it's extremely unlikely that he's mistaken or confused on this point.
> In a strict language, you can say (roughly at least) that replacing [] with [1..n] will add at minimum a certain number of extra CPU cycles and a certain amount of additional allocation.
It's not at minimum, it's always the worst case cost of N in strict languages, whereas the lazy setting provides you with amortized complexity.
I think you might be misunderstanding what I meant by "at minimum". I'm talking about the case of replacing a [] constant in some arbitrary piece of code with something like [1..10000]. Given strict semantics you'll of course incur at least the time and space costs associated with constructing the list (that's the "at minimum" bit), but you might also incur additional costs depending on what the rest of the code does with the list. For example, it might execute some arbitrarily expensive computation if the list has more than 20 elements, or whatever.
I think you might have thought I was saying that given a strict semantics it was somehow still possible that you wouldn't necessarily incur the full time and space penalty for constructing n elements (which of course is not the case).
Interesting comment, but many parts of it flew over my head. Would you by chance have some suggestions on where could I better my knowledge of Haskell internals/FP runtimes? Of course your blog has been added to my to-read list :)
I used to know Haskell to an “intermediate level”, but haven’t used it in years, but I am more familiar with “traditional” runtimes/compilers, like the JVM as a reference.
I am a (applied) mathematician turned software engineer. I have worked at backend services and iOS apps. I have some side projects involving micro controllers and kernel programming. Last job was on a haskell shop, I would like to keep using functional programming at work if possible. Problem domains with lots of data analysis are great as I can use my math background.
C is in an odd position right now to argue it is how the machine is really working. Computers are more complicated since bigger caches entered the picture. Hell I do not think even ASM is a good approximation on how machine really work given the data dependencies will make stuff being processed in parallel instead of sequentially.
What you could argue is that C is the archetype for an imperative language procedural language with a clean mapping to ASM. That is different than how the machine works. Simpler architecture have less distance between their ASM and what is really hapenning.
reply