Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

Thursday, June 11, 2026

My programming languages of choice: 06-11-2026

Shell

If the real work can be done with existing utilities.

Bash:
  • My default
  • Included with Git for Windows

PowerShell:
  • Included with Windows
  • JSON/XML support

PowerShell has delusions of being general-purpose and thus is horribly verbose. It requires typed parameter definitions and uses long function names (for example, "cd" is an alias for "Set-Location"). I can't imagine how anyone uses it interactively, where everything is one-time-use and speed is king. But you can call the entire .NET library from PowerShell, so it's very powerful for scripting, or so I've heard.

Your user is running Windows. Your user has some combination of settings that will manage to cause your script to fail, so he will need to read through your script and execute the commands interactively, copying and pasting with the mouse to mitigate syntax pain. Your user does not wish to install a new language for every utility you write. Therefore, you should write your programs in a language that is included with Windows and is interpreted rather than compiled. This narrows down your choices to the singleton set of PowerShell.

Though Windows, as mentioned, comes with PowerShell installed by default, it also blocks PowerShell scripts from running by default, so you may have to guide your user through that. Microsoft has obviously assumed that most people will wish to use PowerShell interactively, for which purpose it is a stomach cramp, or they could have just published it to winget and let you install it yourself. Also, the PowerShell included with Windows is ancient, arcane, and wrong; if you want a non-buggy experience, you'll need to install the latest cross-platform PowerShell (which is actually a separate program!) and use that. So PowerShell is installed by default with Windows, except that it isn't, and is primarily used interactively, for which it is no good, and is best for scripting, which is disallowed by default. And if you just run your scripts using the default installation to avoid installing anything, you'll still need Internet access to look up the command to allow scripting, because, instead of something simple like "chmod +x", it's "Set-ExecutionPolicy -Bypass", or possibly one of three or so other arguments instead of "Bypass", the difference between which escapes me at the moment. And you'll have to assure Windows that, yes, you know what you are about and would like to use their scripting language for Windows to run scripts on Windows.

The high practicality of running PowerShell, combined with the low practicality of writing it, is one of the best arguments I have heard for the use of AI.

Interpreted (non-shell)

Interpreted languages need installed to run, so I rarely ship them to others. I write my personal utilities either in Bash or in compiled languages. The result is that I almost never write programs in interpreted languages. I know Python and have programmed in Ruby, but I haven't found a non-educational use for either.

On the other hand, if someone else has written a program in an interpreted language, installation and continued updates are very easy for you. Just clone the Git repository somewhere useful and add "cd <source directory> && git pull" to your update script for each program.

Node.js:
  • Programmers probably have it installed already.
  • JSON support if jq is insufficient
  • Native regex support

Perl:
  • Native regex support
  • Easy for reading and writing files
  • Included with Git for Windows
  • Doesn't induce guilt like JavaScript since no one uses Perl anymore

awk: Line-by-line file transformations, I guess?

Compiled

I compile to native code and ship an executable. I don't do runtimes.

C99: Default choice

C#:
  • JSON/XML support
  • Needs an IDE by design
  • Verbose
  • Opinionated about all the wrong things

F#:
  • Can do (almost) anything C# can do
  • Can define functions without first making a class to put them in
  • Syntax almost as concise as Python, and sometimes much more so
  • Union types with exhaustive matching so that switches finally just work
  • Everything is like writing a pipeline of transforms in Bash. This is by design.

Of these three languages, everything today is written in C#. It has the tools. Who wants to go back to C? But when you try C after programming in C# for a long time, the first thing you notice (in your starting main.c, before you have started to worry about CMake, malloc, or header files) is the sudden calm. The shouting in your head has stopped. You suddenly know where everything is. When you say that you need a FooStruct, C allocates you a FooStruct, no constructor call needed. When you allocate some memory, you know exactly what goes into it. You don't have to remember which static manager class you put that function in because there are no qualifiers or namespaces of any sort. Or classes. Functions go where you put them. You can do funky things with enum case values without fear. You control everything. The world is yours.

It's addicting. I'm addicted.

F# is what you use to lose half the pain of C# while keeping the conveniences. Except that no one ever uses it because we're all used to programming in C# and switching syntaxes is hard. And, after a few years, C# will halfheartedly steal any idea it can with a new feature. You'll have to learn a new syntax, but you will suddenly have one fewer splinter in your foot, and all will be well with the world and with C#. The new feature won't work as well slapped onto C# as it did in F#, where it has existed from the early versions, but who will know? So we will all just continue to use C#, telling ourselves that it's just as good as F# now, which lie we will preserve so long as we do not spend an afternoon remembering how to use F# and recalling its true rhythm.

Tuesday, March 24, 2026

Programming languages and paradigms

Steps for creating a programming language:

  1. Think about who will want to use your new language and for what.
  2. Conjecture a unified mental model of how a programmer should think about your language. Make it fit how your users think and their expected use cases.
  3. Translate your mental model into machine code.
  4. Announce your new language to the world, making sure to report how users should think about your language.

Steps for creating a modern general-purpose programming language:

  1. Define your target audience as "everyone" and their expected use cases as "anything ever".
  2. Make a unified mental model of how everyone should approach doing anything ever. Model it after how everyone currently does everything.
  3. ???
  4. Be a large corporation. Make everyone use your language to interact with your products. Eventually, people will start using it for other tasks out of familiarity.

That was probably the fastest I have yet derailed a blog post. I want to talk about how to make a proper mental model.

Switch-statements

C# requires a control flow statement at the end of every case in a switch-statement. I'm sure they wished to add an implicit `break` after every (non-empty) case, but that would have shocked developers coming from C, where cases fall through by default. So they resigned themselves to making the user choose control-flow behavior explicitly, presumably reasoning that any default would surprise someone.

I know why C chose default fall-through and C# didn't.

In my oversimplified understanding, control flow constructs compile to jump-instructions--the equivalent of using `goto` everywhere. Switch-statements compile to jump tables, which figure out how far forward in the code to jump before you start executing again.

Notably, a jump table says nothing about where to jump after that. It doesn't know what a switch case is. So a `break` statement at the end of the switch case is another jump instruction--extra effort for the runtime. Fall-through is the default in C because it is the default for the implementation. C probably expects its programmers to know already basically what the assembler will do and to write programs based on that knowledge.

C# doesn't do that. C# would consider that an implementation detail. Why would you need to know assembly concepts to program in C#? In C#, fall-through requires the use of `goto`, the most damning marker of unidiomaticity that I can imagine. The creators of the C# language did not want people using switch-statements with fall-through. (Stephen Toub does, though, if I remember correctly.)

(Actually, switch-statements don't always compile to jump tables in C#. The probably do if you're switching on int (or char or bool), but they can also do pattern matching using the same syntax. In C#, the code you write may have a completely different implementation based on context.)

The point

Why does every modern language try to be opaque?

Richard Gabriel says that simplicity of implementation is at least as important as simplicity of interface. What sense does this make unless the user is supposed to understand the implementation? And Joel Spolsky argues that the user will need a correct mental model of the implementation anyway. These are big figures in software, worthy of our attention.

We all agree that programs should be simple. Is there any plausible measurement of software simplicity other than how easy it is for the computer to run? ("Quick for the programmer to write" is a separate question; a throwaway script may rely on brute force.)

To write good code, you must first have the right mental model. The right mental model is that which most correctly describes the problem. In programming, every problem is flipping some bits somewhere into the proper state. So should we not be thinking in those terms? Should we not use technologies that we are allowed to understand?

Thursday, February 5, 2026

Generate more, parse less

Be liberal in what you accept, and conservative in what you send.

--Postel's Prescription. (I think that this popular quote is relevant to this post, but I can't decide how.)

I wrote a code generator to take a C enum type and make a string parsing function for it. While writing this parser, I ran into parsing difficulties.

Code has to be generated from some other information. To get that information, I had to read it from another file. So I had to parse it myself before I could write my parser.

On my first try, I started by reading the header file containing the enum type. I immediately ran into apparently necessary feature creep. What if the user put other code before the enum in the file? What if the user defined values for the cases? These cases would require me to expend a lot more work to implement, but a user would be shocked to find that his code suddenly didn't work because he did these perfectly normal things. I implemented more features than I needed for my use case, but still felt that my approach was far too naive and constrained. Eventually I gave up.

The next time, I made a text file listing the cases I wanted. Then I iterated over that and generated both the enum and the parser function. Easy as pi.

For the understanding of non-programmers, which looks easier to deal with?

gardening-tools.h:


  #ifndef GARDENINGTOOLS_H
  #define GARDENINGTOOLS_H
  
  typedef enum GardeningTools {
    Trowel,
    Hose,
    Dynamite
  }
  
  #endif // GARDENINGTOOLS_H

Or gardening-tools.enum:


  Trowel
  Hose
  Dynamite

But they contain the same information! I can generate the second from the first!

If I have to read data, why would I encode it in a format that has more features than I need to use right now?

Hooray for flat, simple data layouts! My three lines of data doesn't need the power of C. Here is the entire spec for my layout: Put a line break after each case name. I get the enum type name from the input file name.

I can add more features if I wish. Since my format is simple, modifying it is easy. Values for the enum cases? Put a space between the case name and value. File name different from the enum name? One extra parameter. Documentation comments? Now lines starting with "//" pass through to the output unmodified. But I probably won't need any of that, so why bother now?

Code generation: Accomplished

Number of library references added: 0

Tuesday, January 20, 2026

Rethinking my past dislike of C#

In response to my previous post.


We don't use "goto", of course, because we want our iteration to be structured.

But "for" is only for collection iteration with indexing. For everything else, we use "while", even though "for" gives the reader the accumulator variable, means of accumulation, and end condition upfront. And then we scatter in a few "break"s to confuse the hurried reader further.

Still annoyed at the limited idiom of "for".


We use "for" instead of "foreach" for performance

On lists that could have been arrays. We always call "ToList", never "ToArray".

ToArray often builds a list first, then converts it to an array, which takes an extra copy operation. ToArray could be worth it if you're just using Select or other length-preserving transformations.

Also, most codebases don't use "for" instead of "foreach" for performance. That's not idiomatic.


We don't need discriminated unions. Classes and interfaces are enough for any situation.

And then we "switch" on them and have to repair bugs having to do with missing cases that were created later.

Now that I am older and wiser, I realize that my greatest mistake was when I thought that inheritance might have been worth using to get exhaustive matching. If you are programming in C#, learn to accept that you don't get exhaustive matching. If you use inheritance, you will end up with so many layers of indirection that you never quite know where to find the code that does the thing. Don't do polymorphism. Kill it on sight. You are allowed to use "if" and to switch on enums. That is all.

C# is supposedly getting DUs one of these days. I wonder whether that will change my opinion.


We don't use static fields and singleton classes. Instead, we use dependency injection frameworks to ensure that only one instance of each class is generated and that all other classes have access to that instance. Which is different.

I'm withholding judgment on DI until I have been bitten by global objects. To figure this one out, I'll need to use more global objects.


We don't have an order of compilation below the project level. Every class in a project, unless it is private to another class, is accessible from every other class. We deal with circular class dependencies by not worrying about them. Code goes wherever happens to feel best at the moment.

I haven't thought about this in a while. F#'s file ordering was quite nice.

I think that this problem probably mostly goes away if you understand what your program does. Of course, this requires writing a program that a human can understand.

The non-F# solution is to split the program into small units with narrow interfaces. Maybe that is why the early C programmers encouraged us to split functionality into separate executables and communicate between them with plain text. Your interface can't be very complex if you have to parse all input and output.


"IConvertsFooToBar" is supposed to be prettier than "Func<Foo, Bar>" for some reason.

Granted, "Func" is an ugly word. But I guess we had already used up all the punctuation elsewhere.

And we need to have separate "Func" and "Action" delegates since you can't have a value of type "void".

Same answer as for polymorphism: C# wasn't designed for passing functions. Don't do that.

Don't pass interfaces, either, if you can help it. Refer to the previous comment on avoiding polymorphism. Flat code is easy to read.


We use static typing so that we can be sure that all representable cases are valid.

Unless they're null. Anything could be null. (Except structs, but we don't use structs.)

A defined value representing "no value" is highly useful. This is a sub-problem of understanding your program's flow so that you know the range of possible values at any point. This, again, requires writing an understandable program.


"default". Without "default", you have to make two versions of every generic method, one for classes and one for structs. With "default", we still have to make two versions of every generic method since the two have completely different behaviors. For classes, it returns "null", which might blow up your program if you're not on guard. For structs, it returns an instance in which every field is set to its own "default", which is significantly worse since it's very difficult to detect.

Now that I have written a little C, "default" makes sense. Now it is the distinction between structs and classes that I don't understand. Why can I only allocate structs on the stack and classes on the heap? Isn't allocation strategy more related to data access and lifetime than to data type? I have yet to explore functional programming in C, but at least I can pass a reference to stack-allocated data.


Exceptions are unexceptional. We wrap deep calls in try/catch because something in there will probably throw an exception at some point, and just because there was an exception doesn't mean that we need to crash now.

Catching all exceptions from a function call is evil; you don't know exactly where the exception was thrown or how the program state has changed. You must at least take some effort to recover rather than continuing blithely on as though no error had occurred.

That's more a pitfall than a language problem, though F#'s native Either type provides a much nicer alternative to exceptions than C# does.


Everything is a "manager". "Manager" is pretty close to synonymous with "class" when you think about it.

This is an anti-pattern, but also C#'s fault for making us wrap all code in classes while telling us that each class should do one thing. How do you name a class and method that do one thing? WidgetFrobber.FrobWidget? WidgetFrobber.Frob? WidgetFrobber.Do? FrobWidget.Execute? I usually go with WidgetFrobation.Do, but it still feels wrong. Organizing code by verbs is often better than organizing it by nouns.


I don't mind C# as much as I did then; I have since spent much time learning to simplify code to avoid the features that make C# painful to use. C# has records now, which take most of the headache out of classes. If you avoid polymorphism of any sort--interfaces, inheritance, or passing delegates--and structure your code as a straightforward imperative program, then modern C# isn't so bad.

Monday, January 19, 2026

Do you have left-pad syndrome?

The npm left-pad incident

How to parse CLI args:

  1. Iterate over the array of CLI args.
  2. Inside the loop, switch on each item.

Just make sure, if any of your options take parameters, to use a for-loop instead of a foreach-loop so that you can just increment the counter in the switch case.

If you install a library to parse CLI args, you might have left-pad syndrome.

How to parse CSV:

  1. Call String.Split.
If you install a library to parse CSV, you definitely have left-pad syndrome.

Tuesday, November 5, 2024

Stuff I don't understand about C#

Rant ahead. Criticisms of the language and the language idioms are interspersed.

Disclaimer: I've been wrong about C# before. My lack of understanding may be caused by my lack of knowledge. If I am proven wrong, then I become a better programmer, which I appreciate, so contradict me freely.

Why do we do this to ourselves?


We don't use "goto", of course, because we want our iteration to be structured.

But "for" is only for collection iteration with indexing. For everything else, we use "while", even though "for" gives the reader the accumulator variable, means of accumulation, and end condition upfront. And then we scatter in a few "break"s to confuse the hurried reader further.


We use "for" instead of "foreach" for performance

On lists that could have been arrays. We always call "ToList", never "ToArray".


We don't need discriminated unions. Classes and interfaces are enough for any situation.

And then we "switch" on them and have to repair bugs having to do with missing cases that were created later.


We don't use static fields and singleton classes. Instead, we use dependency injection frameworks to ensure that only one instance of each class is generated and that all other classes have access to that instance. Which is different.


We don't have an order of compilation below the project level. Every class in a project, unless it is private to another class, is accessible from every other class. We deal with circular class dependencies by not worrying about them. Code goes wherever happens to feel best at the moment.


"IConvertsFooToBar" is supposed to be prettier than "Func<Foo, Bar>" for some reason.

Granted, "Func" is an ugly word. But I guess we had already used up all the punctuation elsewhere.

And we need to have separate "Func" and "Action" delegates since you can't have a value of type "void".


We use static typing so that we can be sure that all representable cases are valid.

Unless they're null. Anything could be null. (Except structs, but we don't use structs.)


"default". Without "default", you have to make two versions of every generic method, one for classes and one for structs. With "default", we still have to make two versions of every generic method since the two have completely different behaviors. For classes, it returns "null", which might blow up your program if you're not on guard. For structs, it returns an instance in which every field is set to its own "default", which is significantly worse since it's very difficult to detect.


Exceptions are unexceptional. We wrap deep calls in try/catch because something in there will probably throw an exception at some point, and just because there was an exception doesn't mean that we need to crash now.


Everything is a "manager". "Manager" is pretty close to synonymous with "class" when you think about it.


I'm tired. I miss programming functionally.

Tuesday, September 10, 2024

Different meanings of "any" in C# and Python

I first learned to program in Python and now work in C#.

Python has the function "any", which takes in any number of parameters and checks whether any of them are truthy. This makes sense since Python has a concept of object truthiness.

C# has the Linq method "Any", which takes in an IEnumerable<T> and a Func<T,bool>. It calls the function on each element of the collection and checks whether any of these calls returns "true". This makes sense since C# has no concept of truthiness; a hypothetical C# "Any" method that did not pass in a function could only take a collection of boolean values.

Additionally, C# has a separate Linq method "Any" that takes in only a collection. It checks whether the collection has any elements in it. C# can have two methods called "Any" since it, unlike Python, has method overloading.

Despite this very logical reasoning, I periodically end a long bug hunt by discovering that, having a list of bools, I called "Any()" on it instead of "Any(a => a)".

I miss F#'s "IsEmpty".

If you can, avoid using the same term for two different concepts.

Friday, May 17, 2024

OOP for FP programmers, part 2: Why bother when we can pass functions?

I am still exploring, but since the previous post I am having difficulty finding a use case in which object-orientation provides any functionality or simplicity that function parameters do not. So I intend to test the hypothesis that object-oriented programming offers nothing over function parameters.

The point of OOP is inheritance-based polymorphism. (Polymorphism, I think, means "choosing which entity is called at runtime", but that would technically define if/then statements as polymorphic, so my definition might not be specific enough.) OOP's inherited classes can be passed into any function that takes the parent type. But you can do the same thing by taking a function of a specific signature (or an object of specified functions, the equivalent of an interface). OOP does offer the ability to find all inheritors of a class or an interface, but the functional programmer can do the same thing by extracting a record of parameters (the equivalent of an interface) and then finding all uses of that. A class also has the advantage of a debug-visible name; I highly doubt that debuggers will start recording the names of functions passed as parameters anytime soon, so the best alternative would be to pass with each function a string describing it (ugh).

So class inheritance does not not appear necessary. Is it useful? I intend to use the scientific method to find out. I determine never to type a colon character in C# until I find a good use for OOP. I leave myself two exceptions: I may use inheritance for compatibility with existing systems and for domain modeling with immutable data.

Wednesday, May 15, 2024

OOP for FP programmers, part 1

The following content is egregiously oversimplified.

I learned to program in Python, where classes are a sign of overthinking, and then worked using F#. Now I need to learn object-oriented programming for working in C#. This is difficult because:

  1. No one ever explains object-orientation to be understood by functional programmers.
  2. Many OOP and FP constructs are semantically quite similar, yet not quite identical.
  3. No one ever compares OOP and FP constructs; at best, they ask whether we should use object-oriented or functional programming, a highly questionable question.

Here is what I have so far:

Polymorphism is accomplished using discriminated unions, inheritance, and runtime type checks. Runtime type checks are a Wrong Thing because they do not give you exhaustive matching, whereas either other style requires you to say somewhere that you are purposely choosing not to implement logic. Discriminated unions require definition of all subtypes in one place but allow adding arbitrary use cases elsewhere. Inheritance requires definition of all use cases in one place but allows adding arbitrary subtypes later. Discriminated unions can be faked using Church encoding or the Visitor pattern. So you should use inheritance if you want to see all defined behaviors in one place, but discriminated unions if you want to see all defined subtypes in one place.

Inheritance works only on the instance level. Static inheritance is probably theoretically possible but would be a Wrong Thing for reasons I do not yet understand. The proper OOP solution is to model all polymorphic behaviors themselves single-method classes with no state, at which point the Jack Diederich in my head tells me to convert them to functions and just pass in a function parameter. I have not tried enough times to know yet why he is wrong.

Classes hold encapsulated state. They also hold the behaviors that may operate on that state. They also hold polymorphic behaviors, with or without state. They are also a means of organizing static methods. Each class should have its own file all to itself to reflect the class's status as the basic unit of programming. You cannot program in OOP effectively unless you dream at night of the soft embrace of the class.

Interfaces are objects of functions, sometimes with field references. They serve the same role as function parameters except without duck typing. Interfaces are different from function (and ref) parameters in that they can hold multiple functions, so we should probably use function parameters at first and then extract multiple function parameters into interfaces. Except that the Interface Segregation Principle might imply that at least most interfaces should have a single method?

This all seems highly complicated. I don't know why anyone goes to the trouble to learn OOP instead of matching on DUs and passing functions around. But if there is a reason, I want to find out.

Friday, May 3, 2024

If "worse is better", then why monoliths?

Richard Gabriel, in his essay The Rise of "Worse Is Better", argues that systems should be written first for functionality and only later for correctness--that is, for consistency and completeness.

His reasons:

  1. Systems that focus on practical use are less computationally expensive to use, and thus receive widespread adoption on underpowered machines.
  2. Because of the assumption of inconsistency, users will become accustomed to needing to know something about the implementation of the system. (This also ameliorates leaky abstractions.)
  3. Components will be kept small and reused ("because a New Jersey language and system are not really powerful enough to build complex monolithic software").

It took a while, but I have finally begun to agree with this way of thinking. "Make it work, make it right, make it fast" seems to demand first making a pile of utilities and then conglomerating it into a language or framework or IDE or whatnot when that becomes useful. The world is still under development, and I can help, so I should build instead of complaining. Systems are not complete because they are never complete; if they were, they would have nothing for me to add.

But now, after rereading the conclusion of the article, I have realized that I mostly use and work on kitchen-sink "workflow" programs that have received none of the stated benefits of "worse is better":

  1. They are computationally expensive and supported only on certain architectures.
  2. Their users have no idea how they work or why certain interactions are more likely to produce bugs.
  3. They feature primarily GUI interfaces, thus not being integrated easily, nor do they apparently integrate other programs, since each one features yet another awful, flashy, underpowered text editor.

So why do we have monolithic programs? Do users really desire all those details to be hidden and thus inaccessible? What do they do when the program fails to work as intended, anyway? Have Oracle and Microsoft pushed us into thinking in terms of monoliths against our better interests so that they can sell us products related to their platforms? Or do we have good reasons to build programs that each do fifteen different things?

And if we have indeed written monolithic programs, then have we also perhaps written needlessly bloated languages? Should we reconsider our usual implicit assumption that everything should be done in the same language, which implies that each language must cover all use cases?

My programming languages of choice: 06-11-2026

Shell If the real work can be done with existing utilities. Bash: My default Included with Git for Windows PowerShell: Included with Windows...