
Divided by a common syntax - userbinator
https://owenshepherd.net/posts/divided-by-a-common-syntax/
======
userbinator
In my experience, the C++ programmers I've worked with did tend to have a bit
of an obsession with abstraction. They're far more likely to build complex
systems to solve simple problems. I suspect it's a combination of OOP hype
(not as prevalent today, but still noticeable) and all the "magic" features
that attract such programmers, as it gives them enjoyment to work with complex
things.

On the other hand, C doesn't seem to attract the same type of programmers
because the language itself is relatively spartan. I have a theory that
simpler languages naturally encourage programmers to find simpler solutions
both for themselves and the machine (thus possibly being more efficient),
because more complex ones would involve more work. Asm is an extreme example,
where you have to basically write every instruction the machine will execute
--- and in such a scenario, even if it's mostly library calls, you're _not_
going to want to construct and destruct a dozen objects unless you absolutely,
unconditionally need to.

~~~
cgvgffyv
C++? Preposterous.

 _AbstractSingletonProxyFactoryBean_

QED.

------
TazeTSchnitzel
As a C programmer, I think I find C++'s dizzying abstractions scary because C,
and thus C++, is an unsafe language.

In C, I have to maintain careful discipline in memory management. In C++, the
same operators used for this in C will now sometimes do this for you, except
when they don't. And so I look at C++ code and I have no idea what it's doing,
and I don't know if it's safe.

In Python or whatever I needn't worry because there aren't any actual pointers
or heap corruptions or segfaults.

Mind you, I have very little experience writing C++ code, so maybe I'd get
used to it.

~~~
clappski
I think nowadays it's a bit of a misconception that C++ is unsafe regarding
memory; RAII essentially takes care of it for you when you talking about IO or
other resources, and under the circumstances that you really need a pointer
you can either use a reference or a smart pointer, which essentially just
delegates the management of RAII to the smart pointer object.

~~~
TazeTSchnitzel
So it's safe so long as you're using the safety features.

So, unsafe.

~~~
pjmlp
C++ is a car with safety belts. It is up to the person to buckle it, or not.

C is a car made in the days when having a safety belt wasn't even a concept,
so there is no option to buckle it.

~~~
Spivak
Maybe a better metaphor would be C is a car with a knife pointing out of the
steering wheel. The fact that it's dangerous makes the person drive safer and
more cautiously.

~~~
DerekL
It's a often repeated idea that the dangers of making mistakes with C code
will scare the programmers into writing correct code. This completely fails in
my experience. I've worked on large C projects that suffered a ton of leaks,
free-after-use, buffer overruns, etc.

~~~
user5994461
Correction: use-after-free

------
donatj
I know HN gets a lot of crap for having a ton of Go fanboys but the reasons
given here for liking C are the reasons I am in love with Go. No magic, no
overloading, very clear when I do this this happens, and very easy to read
code including in the standard library. It's a more friendly C.

~~~
shakna
I want to like Go.

But its insistence on $GOPATH is infuriating.

I have many, many projects in dozens of languages, organised in a way that
reflects both my development and deployment practices.

But Go wants to be different. And it wants to be separate.

It's a tiny knitpick, and there is much I like about Go, even the way it
handles exceptions. But it inconveniences me. And so, I guess I'll pout.

~~~
patates
I just set the $GOPATH per project and it stops being a big problem, and turns
into just an inconvenience.

If you have a ProjectX with the path $pathx, set $GOTPATH to $pathx and place
your source files in $pathx/src/projectx

I also hate that I have to do this though and I totally understand what you
mean. Just wanted to mention my favorite work-around to see if I get some
feedback on it being stupid/clever/ordinary.

~~~
shakna
It's what I've been recommended, but it really seems like a terrible hack that
shouldn't have to exist.

~~~
laumars
It's not really any different to supplying the source paths in C / C++ build
scripts. Or setting the Java variables in your preferred IDE. At the of the
day the compiler isn't going to know where to import external source files
from if you don't specify it somewhere. The GOPATH envvar just shortcuts the
need to use build flags but if you want to do away with system wide Go
variables then you can always write build scripts more akin to those we see in
some other languages.

~~~
shakna

        $(CC) $(FILES) -L$(LIBS) -o $(EXE)
    

Allows me to change and modify on a per project basis how to compile a
program. In fact, it allows me to change exactly how it's compiled or cross-
compiled.

I can use system libs if available, and if not, drag down the latest, build
that locally, and link locally.

Go isn't as flexible. It's opinionated.

There doesn't seem to be any benefit other than standards compliance in having
a GOPATH.

I have to circumvent how Go wants to work so that it can fit with the
standards I have that C, C++, C#, F#, Haskell, D, Nim, Red, Chicken, Python
all work with without being bashed into submission, they supply tools to make
it easier.

They don't have lead developers standing up and saying "All Python libraries
should be developed in this particular folder structure. Even if it makes
integrating C code more difficult."

That feels... Wrong.

So repeatedly setting GOPATH sucks, and can cause havoc if the envar isn't
released properly by, say, a failing build.

But, as I said to start with: This is a _tiny_ knitpick.

It means I come back and try Go, time and again.

It also means I spend more time wrestling Go into submission within my
automated testing and deployment structures, than writing code in it.

~~~
laumars
But again, you can do that with Go very easily. eg the -pkgdir flag in `go
build`.

You can also write your own wrapper script around Go build tools. Or even
bypass `go build` entirely and call the Go compiler in the exact same way
you'd described above (-o and -L flags included). Run `go build -n` against
your project to see what `go build` does (it's print only mode, no compiling
will happen) and you'll see it's really no different to what you were asking
for

So everything you want to do can and is supported by Go. It's just not it's
default behaviour so you needed to do a little digging to find it.

Personally though, I find the inconvenience of having to change GOPATH / GOOS
/ GOARCH to be significantly less annoying than having to write makefiles and
configure scripts to handle every platform I might want to target. For most
people `go build` abstracts away the pain of cross compiling and build scripts
quite nicely. In fact that was one of the biggest reasons I started using Go
in the first place (I wanted something I could rapidly code and compile on
AMD64 then rsync onto ARM devices back when I was experimenting with building
custom embedded devices for my car).

edit: just want to add that I'm not under any illusion that Go nor it's
tooling is perfect. I'm just saying Go can easily support your specific
workflow preferences even though superficially it appears otherwise.

~~~
shakna
> needed to do a little digging to find it.

I would love some documentation on it. The Go developers hostility to the
approach is hugely offputting.

~~~
laumars
Go _is_ well documented though. For example the stuff I posted earlier is
available through `go help build`, the go command source files (eg
[https://golang.org/pkg/go/](https://golang.org/pkg/go/)) and an online man
page ([https://golang.org/cmd/go/#hdr-
Compile_packages_and_dependen...](https://golang.org/cmd/go/#hdr-
Compile_packages_and_dependencies)). I'd recommend you read the latter page
first.

If you don't mind me saying, maybe some of the hostility is because you keep
saying Go "cannot" do x, y or z without looking at said documentation first? I
know I've gotten terse responses from the community in the past when I've
asked questions that were already well explained in the docs. I agree it's not
nice being on the receiving end of such responses but I can also sympathise
with why the community get sick of answering basic questions that are already
available in a simple Google / DuckDuckGo search.

~~~
shakna
Not any personal hostility. Rather [0]:

> 1) ... The $GOPATH provides us with a place to store common, shared third
> party libs on our system, and our own projects.

> #1 is the correct and supported way. The other methods are actively
> discouraged. Andrew.

But, for example, the documentation for GOPATH makes no suggestion it is even
possible [1]:

> GOPATH must be set to get, build and install packages outside the standard
> Go tree.

> Each directory listed in GOPATH must have a prescribed structure:

And the compiler help says:

> Build adheres to certain conventions such as those described by 'go help
> gopath'.

It's exciting that I've been mislead about the ease of this, but I wouldn't
say it is well explained in the docs.

The team want one true way of building a go pkg.

[0] [https://groups.google.com/forum/#!topic/golang-
nuts/dxOFYPpJ...](https://groups.google.com/forum/#!topic/golang-
nuts/dxOFYPpJEXo)

[1] [https://golang.org/cmd/go/#hdr-
GOPATH_environment_variable](https://golang.org/cmd/go/#hdr-
GOPATH_environment_variable)

~~~
laumars
I don't really know how to respond because I found the answers you wanted by
looking briefly in the docs.

 _> But, for example, the documentation for GOPATH makes no suggestion it is
even possible [1]_

Well to be fair you were looking for information about compiler flags in the
documentation for the envvars; which perhaps wasn't the best systematic
approach to research your problem? But hopefully you now have the information
you need? Or at least better idea of how to find the answers you seek?

 _> The team want one true way of building a go pkg._

This is very true. However as long as your code follows the same standards it
doesn't really matter how you chose to organise your source files as it should
still compile on other people's systems using `go build` in the idiomatic way.
And for what it's worth, a lot of people don't follow the directory hierarchy
in the strictest way and/or use `go build/install`; choosing instead to use
their preferred vendoring system. So I doubt many people would have a problem
if you wanted to do your own thing just so long as the code is still
compatible with Go's standard build tools should you release your projects to
the world.

------
umanwizard
Tangent, but why is it inaccurate that C is described as "portable assembler"
?

That's a bit of an exaggeration, at worst -- yes, C is a bit more high-level
than a typical assembly language, but most C constructs can be mapped to a
typical processor's assembly language in an obvious way, something that isn't
true of any other language I know of.

~~~
junke
C was very close to the metal in the earliest versions (PDP-11). Since then,
CPU architectures evolved and even though the language evolved too, C
compilers somehow have more work to do to map the programmer's intent to the
actual hardware.

~~~
groovy2shoes
"C is a portable assembler for the PDP-11."

------
svisser
"Divided by a common syntax" also describes the relationship between Python 2
and Python 3.

~~~
themckman
I was kind of hoping it would be an article about all the Lisps and Schemes.

~~~
GFK_of_xmaspast
That would bring out of the woodwork all the people who disagree that the
lisps and schemes have a common syntax as well as all the people who disagree
that lisps have a syntax at all.

------
dap
> A C++ programmer, meanwhile, comes at things from a different perspective:
> code written by humans is buggy; [1] instead, as much as possible of the
> complexity should be shuffled to the compiler.

As though compilers aren't themselves written by humans.

When abstractions move into the language itself, it's harder to observe what's
going on when they do something strange (even if it's correct) than if they
were just some other library code in the same program.

------
nwatson
Any C programmer working at large scale will need to rely on third-party
libraries. That library code is an abstraction.

When I write C++ the language magic is "just another library". If I'm
uncomfortable I just step through those magic layers in a debugger and I'm
then happy, just like a C library user would step through compiled-from-source
third party stuff.

"But a C coder can read the third-party code!" But a C program already hides
the magic of function calls and stack frames ... C++ is just a few more layers
of abstractions just like that one!

~~~
lmm
> But a C program already hides the magic of function calls and stack frames
> ... C++ is just a few more layers of abstractions just like that one!

In principle yes, but there are good and bad abstractions (bitshifts that
print probably being in the latter category), and facilities that break
important symmetries of their surrounding code (exceptions).

~~~
gpderetta
Exactly, I mean, which language designer would reuse the the very common
stream input/output operators for such an uncommon bit manipulation operation!

Slightly less tongue-in-cheek, what sort of symmetries are broken by
exceptions?

~~~
lmm
> Slightly less tongue-in-cheek, what sort of symmetries are broken by
> exceptions?

They make it much harder to maintain single entry/exit in functions where you
want that - and, crucially, harder to _notice_ when a function isn't single
entry/exit. They also violate referential transparency, which means e.g.
caching the result of a repeated function call is no longer necessarily safe.

~~~
mrec
> caching the result of a repeated function call is no longer necessarily safe

Not seeing this one. Unpredictable exception-throwing only happens if the work
being done by your function isn't pure, i.e. it depends on something other
than the function parameters. And if that's the case then caching its result
is a bad idea anyway.

What am I missing here?

~~~
acomar
> Unpredictable exception-throwing only happens if the work being done by your
> function isn't pure, i.e. it depends on something other than the function
> parameters.

This isn't true. The canonical example is trying to grab the first element of
an empty list. You either check and catch the error or eat the exceptional
behavior, but some pure functions are just partial and aren't defined for all
inputs.

(Whether or not they throw a catch-able exception depends on
language/implementation and is really not relevant to my point.)

~~~
gpderetta
How's that unpredictable? It will always throw for all and only empty lis
arguments.

~~~
acomar
It's unpredictable in the sense that even with a compile-time check that the
function I'm calling is pure, I don't have any guarantee that it won't throw
on my particular inputs.

This comes up a lot in the Haskell community -- code outside IO is guaranteed
by the compiler to be pure, but you can still get hit with an asynchronous
exception because you called a partial function.

------
mbfg
I generally agree with the article's main point, however c still has macros
which kind of breaks those rules.

I remember back in 98 when first picking up Java, thinking, WHAT NO MACROS?,
but now i believe that was an incredibly good idea not having them. Of course
Java now has things like aspects, et. al, so you never can get away from
things if you really want them.

------
user5994461
> A C programmer might perhaps uncharitaby suggest that the number of bugs
> might be reduced by using a less complicated langauge, of course.

The only thing to know about C and C++. Don't use any of them unless there is
no choice.

