Hacker News new | past | comments | ask | show | jobs | submit | nickcw's comments login

In my opinion, not allowing circular dependencies is a great design choice for building large programs. It forces you to separate your concerns properly.

If you get a circular dependency something is wrong with your design and the article does a good job on how to fix them.

I sometimes use function pointers which other packages override to fix circular dependencies which I don't think was mentioned in the article.

My only wish is that the go compiler gave more helpful output when you make a circular dependency. Currently it gives a list of all the packages involved in the loop which can be quite long, though generally it is the last thing you changed which caused the problem.


In the abstract, I think I agree with you. But in reality, what I see is that go projects use far fewer packages than, say, programs in Java. Many go projects use one or two omnibus packages--principally, I expect, to avoid having to worry about circularity issues.

By forcing this design pattern on developers (something no other language does), I think the result has been overall worse code rather than better.

Perhaps a warning, rather than stop-the-compiler error would have been a better choice. Not sure.

Either way though, I wholly agree that the compiler gives too little information, which is curious because it knows the needed data and should easily be able to present it in a useful way.


I see the opposite in most Java programs: poor organization with packages based on a type of thing (eg models, controllers) rather than related behaviors.

Go doesn’t have warnings, which is great - if something is worth warning about, it is also worth erroring about. It never ceases to amaze me when a brand new JavaScript project spits out dozens of errors after pulling in a common library, and everyone thinks that is ok.


Of course Go has warnings.

It has go vet, which is not a linter, and according to the authors doesn't need comments to ignore checks, because the checks are always correct about you having written shitty code. Except where it might warn you about something completely outside your control.


Well then why not just integrate it into the compiler and make them errors?

> If you get a circular dependency something is wrong with your design

Packages not being able import from each other circularly is purely a compiler limitation. It says nothing about the realities of software development.

This notion stems from the idea that software design is inherently hierarchical, and that there is always a clear "higher level" and "lower level" between every possible software module.

What I've found in practice is that this is a fictional concept. Circularity between modules is very common and natural (especially as business requirements change over time). The workarounds people invent to avoid circularity literally always result in a codebase that is harder to understand and maintain, rather than easier.

> It forces you to separate your concerns properly.

Nah. It's not separation of concerns, it's separation of implementation. Two functions that in every other way shape and form deal directly with the same concepts, end up needing to be in separate modules purely because they differ in the functionality they import. And if later their imports change, they may need to be moved again. Which means implementation details are leaking into your design, which makes code less discoverable (since you now need to know implementation details in order to reasonably predict where a given function might be defined).


> Circularity between modules is very common and natural

In theory, but in practice it isn't because there are very few language that do not see software design as being hierarchical. That stems primarily from most languages being based on a hierarchical filesystem, which imposes a hierarchical view of the world at the very core. Circular references in languages that are hierarchical end up being very awkward.

There are a small handful of languages that reject all things hierarchical, including the filesystem, but they are few and far between and most probably have never heard of them and they certainly aren't what you are going to find in production. For better or worse, we've settled on a hierarchical model.


> in practice it isn't because there are very few language that do not see software design as being hierarchical

It has been possible since the days of C (via forward declarations / header files) for two compilation units to call functionality in each other circularly. Java and many other languages have followed suit. I don't buy the argument that it is some sort of new or esoteric thing for a compiler to allow this.


Is there a reason you decided to reply after only reading the first sentence? This off-topic straw man you have imaged doesn't exist.

You're going to have to elaborate in words what your actual problem with my comment is. It's not clear to me why you believe I only read the first sentence, nor in what way I am addressing a strawman. It is perhaps you who have misunderstood my point, rather than the other way around. But, again, you've provided not enough details for me to ascertain this.

Your point is understood, and nobody would disagree with it, but your point is towards a straw man. Nobody ever in the history of computing has made this "argument" you have imagined. If you honestly believe that you didn't make it up arbitrarily, where did you get it from?

The argument I mentioned:

> I don't buy the argument that it is some sort of new or esoteric thing for a compiler to allow this

Where I got it from:

> there are very few language that do not see software design as being hierarchical

> Circular references in languages that are hierarchical end up being very awkward.

> There are a small handful of languages that reject all things hierarchical, including the filesystem, but they are few and far between and most probably have never heard of them and they certainly aren't what you are going to find in production

My point being that it is not "awkward" at all for a language to allow circular references between modules. It is only awkward in languages that make it awkward (like Go does). In C (a language over 50 years old) and Java (one of the most widely used languages in the world) it's the most straightforward and natural thing in the world. So it's certainly not correct to say that it is "awkward" in all languages except ones nobody has ever heard of (aka "esoteric", the term I used in my comment).

So yeah I'm still not fully following how I failed to address your comment, or attacked a strawman. Based on what you're saying now, it's clear that you feel I have misunderstood the point you were trying to make. But I can say that it certainly was not based on a single sentence, or a lack of reading on my part. Probably just a confusion of terminology.

So, what did you actually mean?


> Where I got it from:

Which must mean that you only read the first sentence and dreamt up the rest, or didn't read it at all, else you'd know there was nothing said about circular imports being esoteric. In fact, the comment you originally replied to wouldn't work if they were esoteric.

> It is only awkward in languages that make it awkward (like Go does).

How could it be awkward in Go? It doesn't support circular references. It cannot be awkward – it isn't possible at all! This was already told in earlier comments, so how did you manage to get here not knowing that other than by not reading the comments?


> there was nothing said about circular imports being esoteric

If you aren't saying that circular imports are not common and natural, then don't reply to the statement "circularity between modules is very common and natural" with the statement "In theory, but in practice it isn't"

I have no quarrel with you. If there has been a misunderstanding, then simply correct it and elaborate. That could have been a 2-second interaction - I would have replied "oh, my mistake, I misunderstood what you were saying" and this conversation would be over.

On the other hand, if your goal is just to look smart or superior, or make me look dumb because you believe I'm incapable of reading, then feel free. I have better things to do with my time.

I won't be replying further.

> How could it be awkward in Go? It doesn't support circular references. It cannot be awkward – it isn't possible at all!

There are workarounds. That was the whole point of the beginning of this entire comment chain - me pointing out that these workarounds are worse quality code than if the compiler just supported doing things in a more straightforward way.


> If you aren't saying that circular imports are not common and natural...

Like said, in theory they are natural. In practice they usually aren't, because the languages people use are usually designed around the idea of being hierarchical. That is true of C, and especially true of Java which doubles down on the concept. As before – the part you seemed to not read – circular imports in those languages is possible, but awkward due to their hierarchical view of the world.

> There are workarounds.

You're right that you can achieve a similar effect by hacking up the use of go:linkname, but you're completely bypassing the import system to do that. I don't think that is reasonably considered circular imports. Besides, that approach is not really awkward. That approach is foolish, if not plain stupid.


I prefer extremely fast compile times.

Other languages (Pascal, Ocaml, maybe Zig) have proven that it's possible to implement a very fast compiler that emits efficient machine code while not dumbing down the language to complete brain death.

Add Common Lisp to the list. And even Scala has fast compile times today.

It's not fast if it fails every time you commented a line because a variable becomes unused though.

[The following is intended as language-agnostic.]

It’s useful to distinguish between interface and implementation dependencies. I agree that there shouldn’t be circular interface dependencies between modules. The absence of circular interface dependencies allows separate compilation of modules. It also means that at least in principle, the implementations can be made non-circular (can be refactored to non-circular without breaking any of the existing interfaces). But it’s often okay for the implementation of A to depend on the interface of B, and at the same time the implementation of B to depend on the interface of A, as long as there is no mutual dependency between the interfaces of A and B.


> In my opinion, not allowing circular dependencies is a great design choice for building large programs.

I have a hobby project with maybe 20 packages involved. Circular dependencies were getting harder and harder to solve.

What worked in this situation was to separate the whole hairball into two layers. The packages in the app layer could import packages from the utilities layer, but not vice-versa.

This introduced enough structure to simplify the removal of circularities and prevent new outbreaks.


But, don't you know you can't have a utils package either?

A package must implement one functionality, and it must be clear from the short import name what that functionality is.

A package is also the only way to enforce visibility (and often, with that, other useful properties like immutability).

So packages can't be too big, or too generic, but they can't also be too small, or too specific.

I also have 2 (or 3?) layers, and a utils package, and the packages are huge, and yet I need various cheats to allow cyclic dependencies, and have some repetition with other tricks to avoid some of it.

It's the real world, and guess what, the very well designed standard library does… all of the above too.


> But, don't you know you can't have a utils package either?

Oh, I know. What I have is over half a dozen such packages named "*utils".


What do you mean by "can't"?

"Can not" means "not possible", but it's clearly possible.


I obviously meant "can't" not RFC 2119 "MUST NOT".

I could list more sources, but I'll just leave you with these:

https://go.dev/blog/package-names

https://google.github.io/styleguide/go/best-practices#util-p...

https://dave.cheney.net/2019/01/08/avoid-package-names-like-...

The Google style guide is worth a read through, as the subsequent point recognizes the difficulties around package size. It can make sense to have a package with a single function, and it can almost make sense to stuff your entire program/library in a single package.

The two rules work that exacerbate this tension are precisely visibility and no cyclic dependencies.

The perfect is the enemy of the good. Forbidding cyclic dependencies is one such case, IMO.


I think Go (as a better C) addressed one of C's weaknesses - only being able to return one value.

The C ABI is quite capable of it provided you dress it up into a struct, you just can't do it without defining that useless one off struct.

IMHO Go fixed that C wart.

If you look at the Go calling convention, multiple return values look perfectly natural. The parameters of a function are passed on the stack and the return values are returned immediately after on the stack . That's why naming your return values is just like naming your parameters - they are both local stack based variables.

So I would argue that multiple return values are great.

Maybe you should be able to treat all the return arguments as a tuple (eg Python). That would make certain things neater, but I think it is a different argument.


> The parameters of a function are passed on the stack and the return values are returned immediately after on the stack. That's why naming your return values is just like naming your parameters - they are both local stack based variables.

Why should the user of a language need to know anything about how values are passed to/returned from a function? Passing them on the stack, in registers, on the heap are definitely in the 'implementation detail' camp.

And named return values seems to me to be just bookkeeping and (admittedly a good form of) user convenience.

That being said, I do like the way python does it with tuples and automagic tuple unpacking. Even the C-API makes it super easy to do multiple return values.


The geometry will reduce the accuracy of the fix though as all the satellites will be in the same 8 degrees of the sky.

I wish they had said in the article what the accuracy is!


Is 8 degrees the angular size of the Earth from the moon? Aren't GPS satellites in relatively high orbit, so it could potentially be a larger patch of sky.


It’s the satellites’ size.

The satellites are 20 Mm high above the ground so their spread is 53 Mm, which is 4.4 times the diameter of the Earth (12 Mm). So yes, the angular size of the satellite cloud (7.896°) is quite a bit larger than the Earth (1.785°) from the moon PoV.


Although since the GNSS satellites use directional antennae pointing at Earth, this experiment only picked up signals where the satellites are on the other side of Earth and close enough to its edge for some of that directional signal to leak past the edge of Earth and get to the Moon. So, the satellites that are nearly 4° away from the centre of Earth cannot be detected because they are beaming their signal nowhere near the Moon, and the detectable angular size is much less than 7.896°.


I hadn’t realized it, but you are right.

- Orbit height: 20,200 km

- Earths diameter: 12,760 km

https://www.gps.gov/systems/gps/space/

https://science.nasa.gov/earth/facts/


After going through all the appliances in my Grandad's house and re-wiring them to have the correct mains plug pinout I can understand the reason for that law!

It is actually quite difficult to wire up a UK mains plug really well!


Having mended a few toasters in my time I salute this effort. Cheap toasters are very difficult to take apart and mend. The toasting mechanism on this one looks great.

Cheap toasters only last a few years before dying. Usually because someone jams it up then clumsily unjams it while damaging the element.

After going through a few toasters in quick succession I finally bought an expensive Dualit one. It's still going 25 years later. I changed the timer mechanism once which was a joy, and you can easily buy spare parts.

The Dualit cost over 10 times more than the cheap toasters though. I don't regret that purchase though and it has actually saved me money over the years and made much less landfill.

Funnily enough the toaster in this article looks quite like the Dualit. I don't suppose that is a coincidence!


Dualit "Classic" toasters are the only toasters I'm aware of with heating elements that can be replaced. Every cheap toaster I've owned has died from the wire in one of the heating elements burning out. The only two toasters I'd buy these days are a Dualit or a vintage Sunbeam Radiant Control toaster. Dualit wins for the modern slot widths though.


My Italian Milantoast has everything replaceable as well: https://www.milantoast.com/en/products/spare-parts/


For €400 I would hope so.

Not even a bagel mode (which is also useful for baguette-style bread)!


Having recently done a timer replacement on a Dualit, I think that might literally be a Dualit timer module. Looks identical in the packing box photo. It wouldn’t surprise me if the heating elements were the same as well (haven’t checked) though those are probably more commonly available as generic items.


They look the same to me: https://www.dualit.com/collections/toaster-spares

It seems slightly disingenuous to use the repair parts from a toaster on the market without crediting them.


I was given a Dualit Classic Newgen 4 slice toaster as a wedding gift. It was without a doubt the WORST toaster I have ever used. I've never been so annoyed by a product before. In fact, it annoyed me so much that I ended up returning it and replacing it with a cheap toaster that is 10% of the price and functions better.

I'll list the fatal flaws with it in descending order of importance:

1) Unlike basically every single other toaster on the market, it does not have a cage or other mechanism that closes on to the bread slices and keeps them an equal distance from the heating element. This results in at least one part of every single slice of bread getting burned to a crisp, and at least one part of every single slice not being toasted at all. After using this toaster for a week, I couldn't believe how any engineer at Dualit could release this. Do they even use their product? It is a catastrophic oversight.

2) The timer is an analogue mechanism, much like an egg timer. I found that there was an extremely thin margin in which the toast is toasted. Anything under that and it's not, anything over that and it's burned beyond recognition. I cannot even count the number of times the smoke alarm in my house went off because my toaster burned my bread to a crisp. Another catastrophic oversight.

3) Because the timer is analogue, when you turn it it makes a clicking noise as an egg timer does. This means it's very easy to mistake the toaster for being on, when in fact it's not. The number of times I went to make toast, only to realise a few minutes later that the toaster was unplugged for some reason and my bread was still bread is unreal. I'd then end up ruining my scrambled eggs by waiting another few mins for toast.

4) The toaster allows you to spin a dial to choose how many heating elements to use. This is a pain in the ass. It's so easy to forget about, until you go to pick up your toast and find out that only 1 and a half slices have been toasted of the 4 you put in there.

The sad thing is, the toaster looked awesome. We also have a Dualit kettle (which is great) and it matched. Unfortunately they prioritised aesthetics over function, and it shows. If you want a toaster that requires you to go through a checklist of switches to check before operating, then requires constant supervision to avoid burning your toast, and will still give you burnt sections of toast anyway despite all of that, I could not recommend a better candidate.


It sounds quite nice.

It is also about the same bitrate as RTTY which was invented in 1922 and is still in use by radio amateurs round the world.

Here is what that sounds like

https://youtu.be/wzkAeopX7P0?si=0m0urX7sDp6Jojqe

Not as musical but quite similar


The amateur radio community is chock full of innovation for low bandwidth weak signal decodable comm protocols.

There's also V.xx modem standards that are kinda dependent on the characteristics of the phone lines, but might work for audio at a distance?


ham optimizes for the wrong thing, imo. look at ft8: perfect for making contacts at low power with stations far, far away, but really only tuned to the particular task of making contacts.

you can package some text alongside, but fundamentally all amateur operators are looking for is a SYN / ACK with callsigns.


There's also JS8call which is a modified version of FT8 meant for actual communication. IIRC you can do some neat things with it, like relaying a message through another user if you don't have a direct path to the recipient.


As one of the accursed hams, I wonder what ggwave's propagation profile would be compared to RTTY / CW (Morse code) etc. Would be interesting to try it out.


RTTY is the sound of "satellites" in a lot of media.


Power = volts * amps

Volts is as supplied by the utility company.

Amps are monitored per rack and the usual data centre response to going over an amp limit is that a fuse blows or the data centre asks you for more money!

The only way you can decrease power used by a server is by throttling the CPUs.

The normal way of throttling CPUs is via the OS which requires cooperation.

I speculate this is possible via the lights out base band controller (which doesn't need the os to be involved), but I'm pretty sure you'd see that in /sys if it was.


I wrote one in 68000 assembly on my Atari ST which implemented the life rules using bitwise logic. That meant I could do 32 cells in parallel in a kind of primitive SIMD!

The most mind blowing algorithm has to be hashlife though. Well worth studying for those aha moments.


Haven't seen a cgi-bin for a long time!


I feel like this ship has sailed. SQL has been around for more than 50 years and everyone who needs to generate it has already put that extra `if` statement in to suppress trailing commas.

What annoys me far more often is the lack of support for trailing commas in JSON.


> lack of support for trailing commas in JSON

This, 100%! And the lack of comments.


JSON5 allows comments, it's been around since 2012. That said, JSON is not meant for humans / manual editing, and deciding to use it for configuration files was a mistake.


Thinking that a new, least sucking data format won’t be used for configuration was a bigger mistake. Like, yeah, I will exchange all my data in JSON now, but store configs in a good old XMLNS XSLT DTMF?


Store configs in the program if you can. Store in INI, TOML, or something similarly simple for humans if you must. Never use anything that requires matching syntax as the default (closing tags and matched brackets being the two main ones).

JSON is already strictly worse than XML though as it doesn’t support comments and multi-line strings in a sane way.


Thanks, but I tend to ignore this. Every time you, as a direct end-user, provide feedback on format or software, someone appears with a whole philosophy around why you shouldn't be like that and what you should do instead. And sometimes there's no "why" part even.

But I really don't need philosophy. I know what I want, and I want json with $subj, //comments and optional key quoting. Feels like some people just love making inconvenient things standard and/or teaching others how to live. (I mean the idea "json is not for X" here, not your comment)


even if it wasn't meant for humans and manual editing, it works reasonably well for these usecases


I'd say it even works better than many common configuration filetypes such as YAML or INI.


How am I supposed to make changes to this configuration file?


Funny I’m the exact opposite: I essentially never write JSON by hand, or add json content to repositories, so could not care less about the lack of trailing commas, I do semi routinely write or review SQL.


I agree. And JSON basically isn't meant to be hand-written, just easy to glance over, if you need to or do basic tests with. It's a serialization format. It's not a config format or anything like that. The idea of wanting to use it for that (when the config needs to be hand-written) should be a red flag for anyone. Why would you want to hand-write something where a key is denoted with double quotes?

And just to be clear: YAML is also not a config format and wasn't meant to be. YAML is for metadata style stuff that is supposed to be close to humans, heck the whole yes/no and "not so strict" typing parts.

If you want a config format you got many options: There is toml/ini and friends on one side and UCL/HCL/... on the other. Or if you want to go really simple, do something like Postgres, Tor, etc. do and just use space separated strings.


I've been bitten by trailing commas in my python+sql code repeatedly, it's an easy mistake to make when python itself has trailing commas (which I make deliberate use of).


I feel the same way about SQL too, it's set and difficult to shift. But I also look at PRQL longingly - https://prql-lang.org/


Have you seen the Google BQ pipe syntax? https://cloud.google.com/bigquery/docs/reference/standard-sq...

Feels like it does 75% of what PRQL does while still staying somewhat backwards compatible. Already works in BQ if you opt in.


Douglas Crockford made a commitment that JSON would never, ever get better. He promised us that it would always suck. He is as good as his word.

It has the small benefit that you never, ever have to worry about running into an old version of a JSON parser that bounces your fancy trailing commas.

Worth it? Not to me but Douglas Crockford does not care what I think. Or you either, apparently.


What extra "if"?

  ', '.join(fields)
;)


The closing tags should be optional too.

[{},{},,,,{},,

Should be fine. Now you can push things to the eof.


You probably want jsonlines. Being able to open a file in append mode without needing to parse the whole thing is great.

https://jsonlines.org/


I get why one wants strict parsing rules but i don't see the point of closing tags. It seems nice enough to have [say] logs in json. Why would one need to add closing tags before consuming the json elsewhere? Parsing the entire thing is a huge waste of time if it is large.

I can use csv or html ofc.


Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: