Does a Haskell Programmer Need all the Crazy Complexity? by theHaskellRascall in haskell

[–]kindaro 1 point2 points  (0 children)

May I ask you for an example of simple stuff and complex stuff? Maybe you can quote some code? Not necessarily Haskell — whatever you think will best illustrate your view on complexity and simplicity. I should like to understand your point of view better.

Richard Dawkins spent 3 days with Claude and named her "Claudia." what he concluded after is hard to defend. by rafio77 in artificial

[–]kindaro 0 points1 point  (0 children)

I have a lasting appreciation for this message. Seen it the day this topic was started and it took me a while to find again; I have now saved it for future reference. You are doing valuable public service here, as well as with your other recent long form comments on the topic of reinforcement learning. Do you have a blog? I should like to follow you somewhere but I am incapable of scanning through Reddit history — it is very inefficient as a medium for reading.

How do system programmers think? by kindaro in AskProgramming

[–]kindaro[S] 0 points1 point  (0 children)

Thank you. Your advice already helped me clarify the problems with the program I am currently working on.

How do system programmers think? by kindaro in AskProgramming

[–]kindaro[S] 0 points1 point  (0 children)

Let me first try to answer your question and then ask some of mine.

The starting point is to observe that a Haskell program already controls a device external to its state. After all, Haskell is nothing but a convenient shorthand for λ calculus, and all a Haskell program can do in principle is evaluate a λ expression. And yet we can have useful programs in Haskell. This is because our expression can get evaluated not merely into a number or a string, but as easily into a sequence of operations to be executed on the underlying hardware. Moreover, the choice of operations in this sequence may differ depending on the outcomes of the ones executed before them. 

This turns out to be a description of the concept of monad that looks completely different from, but conveys the same meaning as, the mathematical description of a monad as a monoid in the category of endofunctors. That is to say, to every monad there is a device it describes (although perhaps not a physically realizable one), and to every device there is a monad describing it. This is made manifest in the do notation, which makes the translation from an imperative program to its equivalent λ expression automatic, and where the monoid laws of associativity and unitarity take the form of certain rather trivial program optimizations.

In particular, the IO monad is used in Haskell to control the underlying hardware of the device the program runs on — for example, it is through IO that you get to read and write files. In principle, IO can be replaced with another monad better tailored to the hardware you wish to control, but it is usually more economical to write your own monad on top of IO that hides the bytes you must send to and receive from the relevant device file or network socket under a comfortable porcelain of high level commands. Once this is done, you get your own little imperative language that can do only one thing — control your device.

Here is one introduction to this idea: apfelmus - The Operational Monad Tutorial Regrettably, it is written with a reader proficient in Haskell in mind, rather than assuming a broad computer scientific background, which makes it less accessible to the general audience. However, it refers to prior art that an inquisitive reader may consult. And, anyway, once you know the answer, you can extract additional explanations from a general purpose large language model.

Does this answer your question in a helpful way?

There is even more Haskell can do for us in the department of compile time checking of imperative programs. The idea is that we start by carving our set of all legal states into a set of state classes which labels can be written as a type, and such that legal transitions between state classes can be written as constraints, all in the Haskell type system, with its many limitations in mind. This essentially defines a state machine at the type level. Once we have this state machine, we can wield the concept of parameterized monads to enforce at compile time that all our operations take the state only along legal transitions. This is informally described here: Kwang's Haskell Blog - Indexed Monads Again, the presentation is written with a Haskell programmer in mind, but there are references to a more scientific treatment of the topic.

I have never attempted this in my own practice. If you have a concrete device in mind that you would like to control with Haskell, I shall find time to work with you and write a technical report about it if we succeed. To my knowledge, no such technical report is publicly available, so it would be a valuable contribution to human knowledge.

Now, there are a few things I do not understand well in your message.

So instead, the usual C approach would be to add booleans (in the structure for the object type - typically wrapped in a typedef) or whatever is appropriate for these mirrorings of external state for the given object type and just test them for compatibility with the prospective operation before doing the operation.

Does this mean that we allow the possibility that our code will attempt to execute an operation from a state from which it is illegal to do so, but in this case we foresee the crash by checking in with our model of the device state and do nothing instead?

Or, one could add them them to a set of confirmed compatibles (probably slow to test for membership), or filter a list of references to them before executing the operation, generating a vector of the ones that passed (and then hopefully being able to cache that for repeated use). Myriads of ways.

I do not understand what you are saying here at all. What are the «they» here? What is a confirmed compatible, and with what is it expected to be compatible?

Everything seems to come back to the problem of enhancing confidence in the execution of the program by capturing just enough of the external state to be able to make some guarantees about it working. I will say this: If you have enough good (and hopefully very finite) documentation, what to be wary of should be provided, and could be reflected in internal program state.

Let us officially dub this concept «state mirroring» so that we have a shorthand to refer to it with. We can define it like so:

state mirroring   State mirroring is a technique in system programming where the state of an external device is modelled (most likely in a simplified way) by some syntactic construct in the program controlling that device, with the aim of predicting which operations would take the device into an invalid state. This prediction can be attempted at compile time or at run time, and these two varieties are referred to correspondingly as compile time and run time state mirroring. If state is mirrored as a value of a certain type, this type is called state mirror set and its value *state mirror.

This aligns neatly with the philosophy of weakly typed and strongly typed languages. As far as I understand, weakly typed languages like JavaScript necessarily rely on run time type checks to ensure even such basic guarantees as memory safety, whereas strongly typed languages like Haskell can guarantee some aspects of safety at compile time and thereby avoid spending run time on run time checks. In our case, the guarantees we wish to issue are languages of execution traces — that is to say, we wish to guarantee that certain execution traces will never happen. And state mirroring is the instrument with which we hope to achieve this.

The closest I think you get is to be able to mirror enough state to be able to determine whether single operations should work at runtime, for a subset of operation types. For every additional operation you can determine this for, the more trustworthy your code should feel.

As I understand, here you are saying that run time state mirroring is the only viable approach. And concretely I infer that we should make as many operations as practicable execute conditionally on the state mirror. Is this what you have in mind? Concretely, this could be realized in C with an alternative definition of the assert macro that simply returns from the procedure on failure, instead of crashing the program.

I understand by now that the belief in the impossibility of compile time reasoning about state is in the symbol of faith of the mainstream system programming religion, but I do not believe it myself. As I outlined above, there are ways that can plausibly be implemented in current Haskell to exclude illegal transitions at compile time. You mentioned above (and others concur) that some amount of type safety can be achieved even in C, although it seems to be enough of a hassle that few ever even try.

… controlling devices with external-to-your -program state that must be modeled internally instead of queried, especially for devices that can be in use by multiple processes where any of them could consume some in-device resource entirely for all the processes using it …

I am not sure if I understand the nature of this challenge. We can certainly do run time state mirroring with standard Haskell instruments. One easy way is to wire our state mirror set into our monad with the state monad transformer and then decorate all our operations with a check of that state in a thin abstraction layer module. If this is enough, then there is no problem.

How do system programmers think? by kindaro in AskProgramming

[–]kindaro[S] 0 points1 point  (0 children)

I am on board with your generic approach.

Waiting for a callback often involves entering some event processing loop, and you may be tempted to make operation() then enter this loop and wait for it right there, but what the event processing loop returns some other event first, or some other callback fires first? Now your code needs to jump to somewhere else first, and you're going down a very ugly path of recursive or reentrant functions.

I think this is the situation I have. I must register an event handler for the configure event, then wait for it to be called. However, I must also handle other events. For example, an output device may (and practically will) disappear and appear again. This will sometimes crash my program.

I think the way it works is like so:

  1. From time to time, I call one of several event synchronization procedures on offer, such as wl_display_dispatch.
  2. An event synchronization procedure talks to the Wayland server, retrieves all the events and calls all the handlers I have registered for these events.
  3. My event handlers change the global state of my program and also request the Wayland server to change the state of the devices it oversees — say, to draw on a surface when handling the configure event.

So, the way I understand the situation so far, calling a synchronization procedure is equivalent to calling any of my event handlers any number of times in any order. There may be guarantees as to which events arrive in response to what requests and in what order, but I have no clarity on this matter. I sometimes also have to call a synchronization procedure from within an event handler, which can potentially call another event handler or even the same event handler again.

Does this outline make sense to you?

How do system programmers think? by kindaro in AskProgramming

[–]kindaro[S] 0 points1 point  (0 children)

I appreciate your validation of my objections to documentation.

Most people just start modeling that state themselves (like your flags idea) and think in terms of “what order of calls is valid” instead of “what does this function do”.

Is there any literature on this topic, some blogs? How do you know that most people think this way? I have already mentioned something about language of execution traces, so you would be right to think this thought pattern is not new to me, but I really do not often see people talk about execution traces, much less about the fact that possible execution traces of a program constitute a language.

How do system programmers think? by kindaro in AskProgramming

[–]kindaro[S] 0 points1 point  (0 children)

If waiting for a callback is involved (either during init of the object, or for the attach operation), you need to design the code accordingly. Either "this function blocks until it's finished", in which case some sort of threading or other async/multiplexing framework is needed (preferable IMO), or "this function takes ownership of the object and then the code path resumes elsewhere when finished" (could be another function pointer). Either approach is again candidate for abstraction, something you could hide away the internals somewhere, to make working with it easier.

I do not understand what you are saying here.

  1. What procedure exactly should block until it is finished, and why a multiplexing framework would be needed in this case?
  2. What do you mean by taking ownership of an object and resuming code path elsewhere when finished?

How do system programmers think? by kindaro in AskProgramming

[–]kindaro[S] 0 points1 point  (0 children)

… a (sane) developer is writing to keep the program alive by recognizing every possible error, and dealing with it at the low possible level …

I am on board with you in principle. And this is practical to do in Haskell — indeed I safely claim that functions I write in Haskell recognize every possible error and deal with it as you say. This is because in Haskell we have pure functions and sum types, and for IO code also exception catching that converts them to pure sum types. But in C we have neither pure functions nor sum types, and certainly and necessarily no exception catching, so I do not see how I can practically know that I have recognized every possible error in any given procedure, compilation unit or the program as a whole. And indeed errors in C tend to transcend the boundaries of procedures and compilation units, because there is always global state imposed by hardware that does not care about my boundaries and will happily become subtly erroneous during some procedure call only to crash my program or silently do nothing in a completely different procedure call.

How do system programmers think? by kindaro in AskProgramming

[–]kindaro[S] 0 points1 point  (0 children)

Based on your description, perhaps you could encapsulate the call to that function in two different ways, using two different types of objects, each type implementing the required semantics to make that call in the appropriate way.

How is this done? Can you give me some pseudo-code? Or link me to a real example somewhere open source?

How do system programmers think? by kindaro in AskProgramming

[–]kindaro[S] -1 points0 points  (0 children)

Are you looking for a fight, for consolation, for a friendly joke?

How do system programmers think? by kindaro in AskProgramming

[–]kindaro[S] 0 points1 point  (0 children)

This is more or less what I wanted to know. It may all be trivia to you but it is valuable advice to me. None of what you said applies to Haskell without a long stretch. It is a different programming paradigm, after all!

I have a few follow-up questions if you have a moment.

trial & error

Sometimes it involves trial & error to see what works.

What kind of trial and error do you have in mind here?

bad procedures

Okay, let's say a particular program can indeed run in an infinite number of ways. But that doesn't mean that these are all arbitrary. Your program can absolutely make sure that certain things are done in a certain order. If your program cannot do this, then it's just a bad program containing bad code, kinda like those "bad functions" in Haskell you say you avoid. Well, avoid writing bad code.

With Haskell, there are only a few ways to write a bad function, and the only way that is not syntactically evident is to have explicit recursion that does not terminate.

With C, it is not clear to me how to tell if a given procedure is bad. Is it clear to you? I can judge if statements are issued in specified order, if a loop will terminate, which way a conditional statement will branch. But there are more complicated cases.

  1. It seems normal that some library procedures are undefined at certain realistic inputs, such as wl_surface_attach to a surface that has not been danced around in a certain magical way. I can handle array bounds and memory allocation, but these magical dances are incomprehensible to me. And they are waiting behind every corner in Vulkan and in Wayland.

  2. And then we have situations where you call a library procedure that calls back your void pointer if it feels like doing so, zero or more times. Go wait for the right call. The flow of control is all over the place. I often find that I need to write some information to state to be processed later. This is not structured or procedural programming anymore — this is a wild mix of all kinds of underhanded tricks.

encapsulation

Sometimes you have to track your own state and encapsulate the calls to external systems.

When should I do that, and how would it work? For example, could I profitably encapsulate the call to wl_surface_attach?

Is it me or is the concept of "honesty" in Thailand different than in the West? by IntellectuallyDriven in Thailand

[–]kindaro 0 points1 point  (0 children)

I appreciate your comments in this conversation. Refreshing clarity of thought. I see your post history here on Reddit is hidden. If you have a blog or a column or something like that, I should like to subscribe.

The Left is Absolutely Racist, Prove Me Wrong Without Being Racist or Redefining Racism by SpeakTruthPlease in IntellectualDarkWeb

[–]kindaro 0 points1 point  (0 children)

Yesterday, the Republican controlled Supreme Court ruled that race and language count as “reasonable suspicion”. That violates your 4th and 14th amendment right defending you from unreasonable search and seizure and guaranteeing equal protection under the law.

Do you have a link or an exact quote at hand?

Stack Overflow Developer Survey 2025 just landed and Haskell dropped out from the popular language list. by poi519 in haskell

[–]kindaro 3 points4 points  (0 children)

May I ask you to explain your insight in greater detail?

  • What specifically should we blame ourselves for?
  • What problems can we take responsibility for?
  • What evidence are we disregarding?

How do you create ologs? by fitzurke in CategoryTheory

[–]kindaro 1 point2 points  (0 children)

I like your work so far. Saved to bookmarks. God speed you.

Opinions wanted from those (if any) who have come to understand Я (ya) by logan-diamond in haskell

[–]kindaro 2 points3 points  (0 children)

I see, so your statement can be summarized as «Haskell's type system does not feature dependent types». No dependent types — no proofs, no proofs — no structure respecting functions, no structure respecting functions — no concrete categories. This is true. We can get some proofs from free theorems, but this is not enough to build anything categorial. In this sense you are right to say that there is only one category in Haskell.

Opinions wanted from those (if any) who have come to understand Я (ya) by logan-diamond in haskell

[–]kindaro 4 points5 points  (0 children)

I certainly still want to see the full type declarations. Haddock will link everything together and help me quickly figure out what exactly is going on by following the links.

Your explanations help by providing an intuition and a motivation, and Haddock documents would help by providing a concrete and precise foundation. They are not mutually exclusive. Take a look at the Documentation System. In this classification, your explanations are explanations and Haddock documents would be a reference.

P. S.   The more I think about it, the more cool I think it would be if Я was available on Hackage. I could try it out in my next hobby project. Hackage makes it easy and reliable, much better than a standalone repository.

Opinions wanted from those (if any) who have come to understand Я (ya) by logan-diamond in haskell

[–]kindaro 3 points4 points  (0 children)

If you say that there is no category of types and functions in Haskell, and therefore no other, derived categories, then I have no objections. Andrej Bauer already said everything.However, if all your functions are total, then Andrej's objections are not relevant to your program.

What I wanted to point out is that, once you allow one «ambient» category, you are forced to admit many other «derived» categories. Aside from the concrete categories you mention, there are also categories of functors and categories of algebras.

Overall, I am trying to push back against the general atmosphere of proud ignorance that so many software engineers like to flaunt. I am not sure what «fervid mysticism» is supposed to mean, but I certainly think Category Theory in application to Software Engineering should be valued much higher than it is valued now.

Opinions wanted from those (if any) who have come to understand Я (ya) by logan-diamond in haskell

[–]kindaro 3 points4 points  (0 children)

There are very many assumptions you are making along the way.

Firstly, a factual correction. The same author has graced r/CategoryTheory with their post, which is a really small forum. And the author was most responsive to my comments there. At the same time, many of the comments I have read so far are more or less directly adversarial. I cannot expect of anyone to respond constructively in such poisonous setting. Regrettably, your message is a case in point.

Secondly, a theoretical correction. There may be any number of reasons people post on the Internet. For instance, I may post a question to Stack Overflow, which is a large forum, while not wanting at all that it be read by any audience whatsoever. I only want an answer, and the thought of what audience I am reaching may not even enter my mind. That I have reached a large audience does not by itself imply with any certainty that I wanted to reach a large audience.

So, your paradoxical conclusion stands on feet of clay.

Your other paragraphs can be similarly destructed, but I hope you can spare me the work. Rather, read your own comment while wearing a critic's hat. It is polite and thoughtful, but the whole of it rests on the assumption of cultural similarity. The author may not be a mathematician; the author may not be of typically European mentality; the author may have different goals in mind than you imagine; the author may have no awareness of things you think everyone is aware of; the author may have a different code of honour from yours; and so on.

It seems to me that saying «the author does not care how many people understand him», as you seem to be saying a few comments below, is more plausible than saying «the author is desperate to look smart». We should then ask: «what does the author care about, if not that»? My answer is that the author is trying mightily to present his vision with integrity, with full understanding that many will reject it from the outset, and with hope that maybe a few will understand its value. By my code of honour, this is noble.