Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Make's interface is horrible. Significant tabs. Syntax which relies on bizarre punctuation... If only whoever authored Make 40 years ago had had the design acumen of a Ken Thompson or a Dennis Ritchie!

We're stuck with Make because of network effects. I wish that it could just become "lost" forever and a different dependency-based-programming build tool could replace it... but that's just wishful thinking. The pace of our progress is doomed to be held back by the legacy of those poor design decisions for a long time to come.



Maybe I'm in the minority, but I've always found its syntax to be quite nice (though admittedly a departure from most modern languages). Then again, I find using JSON or not-quite-ruby to configure a build incredibly bizarre and confusing, so I guess I'm just set in my ways...

In all seriousness, what's wrong with it? Significant tabs aren't great, but I feel like that's a relatively minor wart. The simple things are _very_ simple and straightforward. The more complex things are more complex, but usually still manageable...

I've seen plenty of unmanageable Makefiles, but I haven't seen another system that would make them inherently cleaner. (I love CMake, but it's a beast, and even harder to debug than make. If it weren't for its nice cross-platform capabilities, I'm not sure it would see much use. It's also too specialized for a generic build tool. Then again, I definitely prefer it to raw Makefiles for a large C++ project.)


"In all seriousness, what's wrong with it?"

1. Claiming a rule makes a target, but then fails to make that target, ought to be a runtime fatal error in the makefile. I can hardly even guess at how much time this one change alone would have saved people.

2. String concatenation as the fundamental composition method is a cute hack for the 1970s... no sarcasm, it really is... but there's better known ways to make "templates" nowadays. It's hard to debug template-based code, it's hard to build a non-trivial system without templates.

3. Debugging makefiles is made much more difficult than necessary by make's default expansion of every target to about 30 different extensions for specific C-based tools (many of which nobody uses anymore), so make -d output is really hard to use. Technically once you learn to read the output it tends to have all the details you need to figure out what's going wrong, but it is simply buried in piles of files that have never and will never be found in my project.

4. The distinction between runtime variables and template-time variables is really difficult and annoying.

5. I have read the description of what INTERMEDIATE does at least a dozen times and I still don't really get it. I'm pretty sure it's basically a hack on the fact the underlying model isn't rich enough to do what people want.

6. Sort of related to 2, but the only datatype being strings makes a lot of things harder than it needs to be.

7. Make really needs a debugger so I can step through the build, see the final expansions of templates and commands, etc. It's a great example of a place where printf debugging can be very difficult to make work, but it's your only choice.

That said, I'd sort of like "a fixed-up make" myself, but there's an effect I wish I had a name for where new techs that are merely improvements on an old one almost never succeed, as if they are overshadowed by the original. Make++ is probably impossible to get anybody to buy in to, so if you don't want make you pretty much have to make something substantially different just to get people to look at you at all.

Also, obviously, many of the preceding comments still apply to a lot of other build tools, too.


(I'm making a separate comment from the INTERMEDIATE explanation)

I'm a big fan of Make, but appreciated your detailed criticism, and found myself nodding in agreement.

#3: I like to stick MAKEFLAGS += --no-builtin-rules in my Makefiles, for this reason. This, of course, has the downside that I can't take advantage of any of the builtin rules.

#7: There is a 3rd-party GNU Make debugger called Remake https://bashdb.sourceforge.net/remake/ https://sourceforge.net/projects/bashdb/files/remake/ It comes recommended by Paul Smith, the maintainer of GNU Make.


> I have read the description of what INTERMEDIATE does at least a dozen times and I still don't really get it.

It took many reads, but I think I get it.

As a toy example, consider the dependency chain:

   foo <- foo.o <- foo.c
          ^^^^^
             `- candidate to be considered "intermediate"
Depending on how we wrote the rules, foo.o may automatically be considered "intermediate". If we didn't write the rules in a way that foo.o is automatically considered intermediate, we may explicitly mark it as intermediate by writing:

    .INTERMEDIATE: foo.o
So, what does "foo.o" being "intermediate" mean? 2 things:

1. Make will automatically delete "foo.o" after "foo" has been built.

2. "foo.o" doesn't need to exist for "foo" to be considered up-to-date. If "foo" exists and is newer than "foo.c", and "foo.o" doesn't exist, "foo" is considered up-to-date (if "foo" was built by make, then when it was built, "foo.o" must have existed at the time, and must have been up-to-date with foo.c at the time). This is mostly a hack so that property #1 does not break incremental builds.

These seem like useful properties if disk space is at a premium, but is something that I have never wanted Make to do. Rather than characterizing it as "a hack on the fact the underlying model isn't rich enough", I'd characterize it as "a space-saving hack from a time when drives were smaller".

----

If, like me, you don't like this, and want to disable it even on files that Make automatically decides are intermediate, you can write:

    .SECONDARY:
Which tells it 2 things:

a. never apply property #1; never automatically delete intermediate files

b. always apply property #2; always let us hop over missing elements in the dependency tree

I write .SECONDARY: for #a, and don't so much care for #b. But, because #1 never triggers, #b/#2 shoudn't ever come up in practice.


Concerning expansion, I guess the article is not claiming that it is good, it doesn't even mention this "feature". I guess it's just saying: "make can do everything your gulp can, it's better, faster and more readable", that's without using any variable expansion feature.

As soon as more JavaScript developers start writing very very simple Makefiles, tooling will improve and maybe someone will come up with a better make.

The other option is to let people keep using webpack and gulp until they come up with another JS-based build-system, webpack 5 or 6, and grulpt or whatever comes after grunt/gulp.


I'm happy to see this kind of detailed criticism. I would be happy to use a new tool if it is similarly general, and has a declarative style. Other commenters brought up Bazel, which I am looking forward to learning about.


With the debugging expansion thing you're mentioning, now I'm craving a make built on some minimalist functional programming language like Racket where "expand the call tree" is a basic operation.


I've been writing Makefiles regularly for maybe 15 years and I always end up on this page every time I need to write a new one: https://www.gnu.org/software/make/manual/html_node/Automatic...

$< $> $* $^ ... Not particularly explicit. You also have the very useful substitution rules, like $(SRC:.c=.o) which are probably more arcane than they ought to be. You can make similar complaints about POSIX shell syntax but at least the shell has the excuse of being used interactively so it makes sense to save on the typing I suppose.

That's my major qualm with it however, the rest of the syntax is mostly straightforward in my opinion, at least for basic Makefiles.


give pmake a shot sometime.. the syntax/semantics are much more 'shell-like' imho and some things are just much more possible.. (e.g. looping rather than recursive calls to function definitions)

manual: ('make' on a bsd is PMake)

https://www.freebsd.org/cgi/man.cgi?query=make&apropos=0&sek...

but most linux flavors have a package somewhere..


> $(SRC:.c=.o)

I use

  $(patsubst %.c,%.o,$(SRC))
instead, which I find easier to remember.


Isn't that a GNU extension? Here's an other problem right there, figure out which dialect of Make you're using and their various quirks and extensions.


I think you can compile gmake for most platforms.


> I've seen plenty of unmanageable Makefiles, but I haven't seen another system that would make them inherently cleaner.

Not to push the particular product, but the approach:

FAKE (https://fake.build/), is an F# "Make" system that takes a fundamentally different tack to handling the complexity. Instead of having a new, restricted, purpose built language they've implemented a build DSL in F# scripts.

That yields build scripts that are strongly typed, just-in-time compiled, have full access to the .Net ecosystem and all your own code libraries, and are implemented in a first class functional language. That is to say: you can bring the full force of programming and abstraction to handle arbitrarily complex situations using the same competencies that one uses for coding.

As the build file grows in complexity and scope it can be refactored, and use functionality integrated into your infrastructure code, in the same way programs are refactored and improved to make them manageable. The result is something highly accessible, supportive, and aggressively positioned for modern build chains... If you can do it in .Net, you can do it in FAKE.


I don't like the syntax much, but I love the programming model. I think people who are used to imperative languages are put off by the declarative programming model of make.


> If only whoever authored Make 40 years ago

Make was created by Stuart Feldman at Bell Labs in 1976. The fact that it is still in use in any form and still being discussed here is a testament to what an amazingly good job he did at the time. Whether it is the right tool for any given modern use case is up to the people who decide to use it or pass it by. I still work with almost daily, and it's in wide use by backend system engineers if my experience is any guide. Yes, it's quite clunky but also quite powerful and reliable at the few things it does. Its also pretty much guaranteed to already be installed and working on every nix system, and that's not nothing.


Look at the git makefile: https://github.com/git/git/blob/master/Makefile

You know what you have to do to build git? Type make. It's amazing.


If fixing things in this makefile is not your job, then it really is amazing.


Honestly its like any other tool we use: once you know the rules governing its behavior it really isn't that hard to debug issues. Make is very consistent in most cases. There are plenty of traps, and they're made easier to fall into given the archaic syntax, but you don't typically have to fall into them over and over again :).


Yes but some tools have... shall we say, irregular rules. Tell me how CMake's argument quoting works for example.


Actually, you might want to glance at the first few hundred lines of platform specific stuff you are supposed to set manually before the main makefile begins. Literally meant to be set by hand, but the Git developers made educated guesses about the specifics of each platform.


> The fact that it is still in use in any form and still being discussed here is a testament to what an amazingly good job he did at the time.

Not necessarily.

> It’s also pretty much guaranteed to already be installed and working on every nix system, and that's not nothing.

First mover advantage.

The fact that no modern language, basically nothing outside of C/C++ uses it, says a lot. And even those are moving away, see Cmake & co.


> basically nothing outside of C/C++ uses it

That's how it has always been, though. In the '90s you didn't need to run Perl or Tcl through it because you weren't compiling anything. The Venn diagram of "Platforms that have make" and "Popular compiled languages" comes up with only asm/C/C++.

Many languages want to do things their way, such as Erlang, Common Lisp, Java, etc. Ruby and Python are interpreted and also don't need a build process. JS, until recently, was interpreted. Lots of NIH going around.

> And even those are moving away, see Cmake & co.

Cmake is closer to autoconf/automake/libtool. If you have serious cross-platform needs, then Cmake is a fine tool. But it's hardly less archaic than make (and only slightly less so than autoconf) and I'm dubious that too many people are really moving away rather than just picking up the newer, shiny tool for newer projects.

If I were doing a small static website or something that required a build with standard *ix tools, vanilla make would be my tool of choice, hands down. Tools like autoconf and, as the author pointed out, webpack provide a more specialized need.


> That's how it has always been, though. In the '90s you didn't need to run Perl or Tcl through it because you weren't compiling anything. The Venn diagram of "Platforms that have make" and "Popular compiled languages" comes up with only asm/C/C++.

Pascal/Delphi were wildly popular in the late 80's, early 90's, though. I don't remember it being built with make, though.


> Make's interface is horrible. Significant tabs.

True, one should not edit makefiles with notepad. Proper editors have support for editing them, though.

> Syntax which relies on bizarre punctuation...

Well documented, though.[1]

> a different dependency-based-programming build tool could replace it... but that's just wishful thinking

Use prolog[2], you should be able to write this in about 42 lines. But you'll end up with the same complaints ("the syntax, the magic variables!") because, in my experience, those are just superficial: The real problem, imho, is that declarative and rule based programming are simply not part of the curriculum, especially not for auto-didactic web developers. OTOH, it only takes an hour or two to grok it, when somebody "who knows" is around and explains. It really is dead simple.

[1] http://pubs.opengroup.org/onlinepubs/009695399/utilities/mak...

[2] https://en.wikipedia.org/wiki/Prolog


FWIW $< is < from redirecting input... a mnemonic for inputs.

$@ is target, because @ looks sort of like a bullseye.

significant tabs are gross, yes, but this is a one-time edit to your vimrc then you can forget about it forever.

it’s also installed everywhere and has minimal dependencies. it could be a lot worse. (see also: m4, autoconf, sendmail.cf)


But how do you remember when to use $^ and $<


Make is such a horrifically awful thing to work with that I just end up using a regular scripting language for building. Why learn another language with all its eccentricities and footguns when I already know several others?


Because, like many other things in programming, you'll end up with a half-baked and buggy implementation of make anyways.

Incremental builds by looking for changed dependencies, a configuration file with its own significant identifiers (i.e. a build DSL shoehorned into JSON or YAML), generalized target rules, shelling out commands, sub-project builds, dry runs, dependencies for your own script, parallelization, and a unique tool with (making an generalization here) insufficient documentation.

If you're really unlucky, you'll even end up with the equivalent of a configure.sh to transpile one DSL and run environment into the DSL for your custom tool.


> Because, like many other things in programming, you'll end up with a half-baked and buggy implementation of make anyways.

I'd argue that make is a half-baked and buggy implementation of make - so that's not really a drawback so much as the status quo.

E.g. I have scripts that exist mainly to carefully select the "correct" version of make for a given project to deal with path normalization and bintools selection issues on windows - and none of these ~3 versions of make work on all our Makefiles. One of those versions appears to have some kind of IO bug - it'll invoke commands with random characters missing for sufficiently large Makefiles, which I've already gone over with a hex editor to make sure there wasn't some weird control characters or invisible whitespace that were to blame. So, buggy and brittle.


I'd argue that there are some major concerns with the makefiles if they require the use of 3 different versions of make to get it all working - a situation I've never personally seen before. I'd suggest prioritizing fixing that before attempting tracking down the cause of other issues. As it stands, there are too many points of interaction to attribute any bugs to any one program.

That said, Windows has never been a strong platform on which to run make (or git, gcc, or any other [u/li]nix originated CLI tools). When I hear of folks using make, I tend to make the assumption that they're running on a [u/li]nix or BSD derivative.


> I'd suggest prioritizing fixing that

I've already had one upstream patch rejected on account of the additional complexity fixing it introduces, and would rather not indefinitely support my own fork of other people's build setups.

Or if I am going to indefinitely support my own fork, I might as well rewrite the build config to properly integrate with the rest of whatever build system I happen to be using - at least then I'll get unified build/compilation options etc.


> path normalization and bintools selection issues on windows

make was never intended to be a cross-platform tool. If you face problems using it on Windows, then that's on you.


All the more reason to use a regular scripting language for building, then, since there are several that are plenty cross-platform.


That is, unless the folks who maintain and champion make want me to use make. It's on them to court me, the cross-platform developer, not the other way around.


Again, if you're a cross-platform developer, make is not for you. Do you also expect zsh enthusiasts to court Windows devs?


I kind of disagree, it's quite possible to use gnu make on Windows with msys (though requires some scripting discipline and probably not for everyone).

I'm currently doing this for a cross platform c++ project. Same makefile used for Linux, Mac, and windows+msys+cl.exe. (yes, fair amount of extra variables and ifdefs to support the last one...)


Incremental builds. It's a pain in the ass to write this in a good, generic way yourself. If your build tools don't already understand it, then make (and similar tools) makes for a nice addition versus just a script that invokes everything every time.

EDIT: Oh, and parallel execution, but smart parallel execution. Independent tasks can be allowed to run simultaneously. Very useful when you have a lot of IO bound tasks. Like in compilation, or if you set it up to retrieve or transmit data over a network.

It's not too hard to do that in your custom script, but more care is required because until you custom script reaches make level internal complexity you will have to manually track dependencies or make sub-optimal assumptions.


I very much prefer Rake for orchestrating multiple build systems in a web project or dependency installation or just any sort of scripting. It comes with a simplified interface to shelling out that will print out the commands you are calling.

If for some reason the project is ruby allergic, I'll try to use Invoke [0].

Sometimes I feel like people's usage of Make in web projects is akin to someone taking an axe and hand saw out to break down a tree for firewood when there are multiple perfectly functioning chainsaws in the garage.

[0] http://www.pyinvoke.org/


This is why I like redo; it handles all of the dependency stuff for you, but your build scripts are written in pure sh


I have been meaning to spend some time with redo!

https://cr.yp.to/redo.html


always been curious about redo. which variant do you use, and what do you use it for?


I use apenwar's because I installed it a long time ago and haven't needed anything more; it does look like it may be abandoned though.

I use it for anything where I need job scheduling around creating output files; even my DVD ripping is managed with redo calling into ffmpeg.


What's your solution for mutliple-output processes like yacc/bison (which creates both an .h and a .c file?)


I don't run into those too often.

When I do, there are two cases:

1) All dependencies are on only one output file (e.g. link stages that generate .map files, compile phases that generate separate .o and debug files). I just treat these as normal

2) I may need to depend on each of the files (I don't use yacc/bison, but it sounds like this would qualify). I select one file to be the main output and have the .do file for that ensure that the secondary outputs go to a mangled name; I then have the .do files for the secondary outputs rename the mangled file.

quick example for generating foo.c/foo.h

foo.c.do:

    generate-file --cout "$3" --hout "$2-mangled.h"
foo.h.do:

    redo-ifchange "$2.c"
    mv "$2-mangled.h" "$3"


If the h output changes but the c doesn’t, this rule will miss it.

I once solved it by making a tar file or all the outputs and extracting it to both .c and .h but that’s incredibly kludgy, still looking for a better solution


As long as the timestamp changes on the C file, that's fine, right? At least with the version of redo I use timestamps are used by default, not file contents.


Do you honestly think Make has no advantages over conventional scripting languages when it comes to building software? I suspect you know that it’s designed for that task and has been used for that task for several decades. Presumably you respect the community over those decades sufficiently to have a strong prior belief that there are good arguments for Make (as well as downsides), even if you can’t be bothered to research them.


I honestly think any advantages it has are significantly outweighed by all its disadvantages.

And no, I don't respect the community. The community very often makes "The Way Things Are Done" its personal religion and refuses to ever change anything for the better.


Another advantage of using a scripting language is that the hard work of portability will already have been done for you by the authors of the scripting language.

Make, in contrast, works within a shell and invokes programs which may be either wildly or subtly incompatible across platforms. Add in the lacking support for conditionals in POSIX make and portability is a nightmare.


On the other hand, make invokes well known shell commands; I already know what they do, when I see them used.

If you are invoking your functions (or functions imported from random library), I have to check them for what they do.


it is possible that if you need to do that much in a makefile that you are running into shell incompatibilities then perhaps you are attempting to cram too much complexity into your build system and perhaps should be using something like Docker to decrease incidences of “works on my machine/os/distro/et c”.

(I am aware that this advice does not hold true in all cases; just that for many of them overly complicated build systems is a code smell.)


To be fair, at the core much of what you're going to be doing is invoking command-line tools. I mean, most compilers are invoked as command-line tools.


because then people new to your project can see a makefile and know that “make” and “make test” and “make install” probably work without having to learn your homebrew, one-off build system.


This argument for Make is exclusively based on network effects. Fine. we accept that we're stuck with it. It still sucks.


I disagree. It’s an argument for convention. This is the same reason we have package.json or Dockerfile or Pipfile or Rakefile - it tells us the standard interface for “install this”. It’s not specific to make.

Docker is new and didn’t have any network effect until recently.


How many of those languages are declarative, not imperative? The advantage of Make is that you only need to give it the inputs and the outputs.



Compared to the autotools I'd say make is fairly decent! But yeah, it's a mess. I wonder if it's really possible to improve significantly over the status quo if you're not willing to compromise on flexibility and design the build system alongside the language itself, like Rust does with cargo for instance.

Make on the other hand is completely language agnostic, you can use it to compile C, Java, LaTeX, or make your taxes. Make is a bit like shell scripts, it's great when you have a small project and you just want to build a few C files for instance[1] but when it starts growing there always comes a point where it becomes hell.

[1] And even then if you want to do it right you need compiler support to figure header dependencies out, like GCC's various -M flags.


It's not like C is famous for having particularly good syntax. If anything, it's the worst thing about it.


They must have got something good though. Else why would there entire families of C-like languages ?

Plus, to me C syntax is particularly good. You're writing real words and the computers does the things you tell it to. To the letter.


> Else why would there entire families of C-like languages ?

C became popular because Unix became popular, and when designing a language that intends to become popular one aims for a ratio of 10% novelty and 90% familiarity. Like, ever wonder why Javascript's date format numbers the months starting from zero and the days starting from one? It's because Eich was told to make JS as much like Java as he could, and java.util.Date numbers the months from zero and the days from one, which Java itself got from C's time.h. (Not coincidentally, Java and JS are both in the C-like language family.)

> You're writing real words

In C? Not compared to ALGOL, COBOL, Pascal, and Ada, you're not. :)

> the computers does the things you tell it to. To the letter.

As long as you're not using a modern compiler, whose optimizations will gleefully translate your code into whatever operations it pleases. And even if one were to bypass C and write assembly code manually, that still doesn't give you complete control over modern CPUs, who are free to do all sorts of opaque silliness in the background for the sake of performance.


To be fair with myself I was mostly snarking around op comment who dismissed the C language as if it was some ancient relic.

But even without accounting for compiler optimizations and cpu architecture, the C language just straight up lies to its user. You could code something entirely with void*.

PS: what I meant by "real words" is that you're naming functions and calling them by their name. Which in itself is very powerful.


> They must have got something good though. Else why would there entire families of C-like languages ?

Network effect. If you wanted to write for unix, you almost had to use C originally.

> Plus, to me C syntax is particularly good.

It's meh. It's not the most straightforward when defining complex types like arrays or function pointers.

> You're writing real words and the computers does the things you tell it to. To the letter.

So yeah, about that...


Everybody I know found it confusing at first, but its logical, and modular. I don't know a language with a better type declaration syntax. It gets impractical when you define function that return functions that return... because these expression grow on the left and right simultaneously. But realistically, you don't do that in C, and other than that, I find it easy to read the type of any expression...

    const int *x[5];
    *x[3];  // const int
    x[3];  // const int *
    x;   // ok - there is first a decay of x[5] to *x, so: const int **
Is there any other syntax that has this modularity?


> But realistically, you don't do that in C

IMHO, that's a self-fulfilling prophecy. If it were reasonably easy to do that in C, it would be done more.


It is quite reasonably easy enough compared to any real practical programming task. For example:

  // your ordinary function declarations
  int plus2(int i) { return i + 2; }
  int times2(int i) { return i * 2; }

  typedef int (*modfun)(int);
  // a very reasonable syntax altogether
  modfun get_modfun(bool mul) { return (mul ? times2 : plus2); }
Nests as well if you ever wanted to:

  modfun get_modfun_no_mul(bool mul) { return plus2; }

  typedef modfun (*modfun_getter)(bool);
  modfun_getter get_getter(bool allow_mul_opt) { return (allow_mul_opt ? get_modfun : get_modfun_no_mul); }


C is a systems programming language. It doesn't have closures or other features from functional programming. In other words, you can't "create functions at runtime". That is why you basically never see a function returning another function.


So, go is also a "systems" language, so the terms more or less meaningless now. Assuming you mean a language we can easily compile to an independent binary capable of being run directly on a microprocessor with no support, I offer you rust as a counter-example.

Also, functional programming and returning functions does not mean they are created at runtime.


It's a fact that C doesn't have closures. That is my point. I happen to like that fact, but you don't have to agree with me.

And "creating functions" means: closing over variables ("closures"), or partial application. I think it takes at least that to be able to return interesting functions.

(and whether go is a systems programming language is at least debatable. I think the creators have distanced themselves from that. It depends on your definition of "systems". You can't really write an OS in go).


> It's a fact that C doesn't have closures.

OK.

> That is my point.

Not unless your first two sentences have absolutely nothing to do with each other. Your point appears to be that because it's a low-level language it doesn't have these features, which is false.

> I happen to like that fact, but you don't have to agree with me.

Or I just think you don't have any experince using better languages. That isn't to say that other language could supplant C, but just that it's difficult for me to image actually liking the C type system (or lack thereof) and lack of first class functions. It's incredibly limiting and requires a lot of hoops to be jumped through to do anything interesting.

> And "creating functions" means: closing over variables ("closures"), or partial application.

Well, you said at runtime. Of course you can "create" functions at compile or programming time!


> Your point appears to be that because it's a low-level language it doesn't have these features, which is false.

I would think that first class closures do indeed _not_ belong in a low-level language. They hide complexity, and you want to avoid that in low-level programming. Not necessarily for performance reasons, but more from a standpoint of clarity (which in turn can critically affect performance, but in subtler ways).

> Or I just think you don't have any experince using better languages.

Nah, I have experience in many other languages, including Python, C++11, Java, Haskell, Javascript, Postscript. The self-containment, control, robustness and clarity I get from a cleanly designed C architecture is just a lot more appealing to me. The only other language I can stand is Python, but for complex things, it becomes actually more work. For example, because it's so goddamn hard to just copy data as values in most languages (thanks to crazy object graphs).

> It's incredibly limiting and requires a lot of hoops to be jumped through to do anything interesting.

It depends on what you are doing. It's a bad match for domains where you have to fight with short-lived objects and do a lot of uncontrolled allocations and string conversions. My experience in other domains (including some types of enterprise software) is more the opposite, though. Most software projects written in more advanced languages are so damn complicated, but do nothing impressive at all. They are mostly busy fighting the complexity that comes from using the many features of the language. But those features help only a bit (in the small), and when you scale up they come back and bite you!

Here's a nice video from a guy who gets shit done, if you are interested: https://www.youtube.com/watch?v=khmFGThc5TI

> Well, you said at runtime. Of course you can "create" functions at compile or programming time!

Closures and partial application are done at runtime. The values bound are dynamic. So in that sense, the functions are indeed created at runtime. I sense that you were of the impression that a closure would actually have the argument "baked" in at compile time (resulting in a different code, at a low level) instead of the argument being applied at runtime. That's not the case, unless optimizations are possible. If that was really your impression, this makes my point regarding avoiding complexity and that closures do not belong in a low-level language. (Look up closure conversion / lambda lifting)


I'm really not sure what you mean by "modularity". There's a lot of languages with much more readable and composable type declarations than C. For example in OCaml:

  let x : (int list ref, string) result option =
    let x1 : int = 0 in
    let x2 : int list = [ x1 ] in
    let x3 : int list ref = ref x2 in
    let x4 : (int list ref, string) result = Ok x3 in
    Some x4
The declaration of List.map:

  val map : ('a -> 'b) -> 'a list -> 'b list


I've got no issue with C syntax either, but to be clear, syntax != semantics.

To cut to the chase:

* syntax = structure

* semantics = meaning

C syntax would include things like curly braces and semicolons, whereas C semantics would include things like the functionality of the reserved keywords.

This SO answer gives a more detailed explanation:

https://stackoverflow.com/a/17931183/1863924


The nice thing about C is that it's a great cross-platform assembly language. I wouldn't call its syntax good or bad.


> The nice thing about C is that it's a great cross-platform assembly language. I wouldn't call its syntax good or bad.

Is it though? It's definitely available on a huge number of platforms, but are the implementations compatible?

And even if they are, there is so much undefined behavior that taking advantage of the cross-platform nature is not nearly as easy as it should be.


Exactly. If C hadn't been invented then someone would have invented it later under a different name.

Because it's obvious people need a minimalistic portable assembler.


Before C was invented there were already other companies writing OSes in high level languages, but yeah thanks to its victory now it gets all the credits.

History is re-written by winners as usual.


I wasn't thinking in terms of winners.

But what were those other OSs and languages? Sounds interesting!


You can start here,

https://en.wikipedia.org/wiki/System_programming_language

https://en.wikipedia.org/wiki/Category:Systems_programming_l...

Some examples, out of my head.

- Burroughs, now being sold as Unisys ClearCase, used ESPOL, later replaced by NEWP, which already used the concept of UNSAFE code blocks;

- IBM used PL/8 for their RISC research, before switching to C, when they decide to go commercial selling RISC hardware for UNIX workstations

- VAX/VMS used Bliss

- Xerox PARC started their research in BCPL, eventually moved to Mesa (later upgraded to Mesa/Cedar), these languages are the inspiration for Wirth's Modula-2 and Oberon languages

- OS/400, nowadays known as IBM i, was developed in PL/S. New code started to be replaced by C++. It was probably the first OS to use a kernel level JIT with a portable bytecode for its executables.

BitSavers and Archive web sites are full of scanned papers and manuals from these and other systems.


Most languages become popular because they have a reputation of necessity. Which is to say, they are popular due to marketing, not due to quality. (As most things are.)


Isn't C just following the syntax of ALGOL? Or are we referring to the parts specific to C?


C is a simpler Algol, yes. It messed up the dangling-else problem, and lost nested procedures, among other things.


It also lost call-by-name parameters, which should be a considered a good thing.


Sort of related -- I'm sure this made it to HN when it was new -- is https://beebo.org/haycorn/2015-04-20_tabs-and-makefiles.html.


I'm really glad software development has largely moved away from the terse names and symbols that used to be so common. I mean patsubst? What a terrible function name! At the very least I would have added the 'h' in path.


> I mean patsubst? What a terrible function name! At the very least I would have added the 'h' in path.

The "pat" in "patsubst" means pattern, not path, so adding an h would be incorrect. (https://www.gnu.org/software/make/manual/html_node/Text-Func...)


Why would you add an 'h' to 'pattern'?


I think that just supports my argument, really. I've never really used makefiles. The blog post talked about using patsubst to change a path. The name of the function essentially gives no clue at all what it does.


"the blog post talked"

Isn't that the problem with using blog posts as educational sources?

(I'm happy I was learning all my toolkit long before blogs became a thing. It's hard to open and read Physics book, when someone on YT talks funny about its second chapter.)


I smell a 8 character limitation somewhere :)


>and a different dependency-based-programming build tool could replace it...

Make one that's generic and provides significant enough improvements that people care.


> Significant tabs

.RECIPEPREFIX option has been available for 7 years. Stop whining about non-issues.




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: