
It's not what programming languages do, it's what they shepherd you to - ingve
https://nibblestew.blogspot.com/2020/03/its-not-what-programming-languages-do.html
======
c3534l
This is what people mean when they say Haskell is "opinionated."

Haskell shepherds you into separating out IO code from library code to such an
extent that literally any function that has an IO action taints the value
returned from that function, causing it to be an IO value, and trying to pass
that IO value into another function makes the return type of that function IO,
too. Parametric polymorphism is the default, too, so it also shepherds you
into writing general purpose code. Haskell is full of these little decisions
where it just won't let you do something because it's not "correct" code, and
they kind of don't care if that makes coding in it a fight against the
compiler.

Rust took that philosophy and applied it pointers. Every value has a lifetime
and an ownership which makes it quite hard to do things that aren't memory
safe.

Both Rust and Haskell wrap values that can fail in little boxes, and to get
them out you have to check which type of value it is, and in C# there's
nothing stopping you from returning null and not telling anyone that you can
return null, and just assuming people will check for null all the time.
Haskell has a philosophy of "make invalid code unrepresentable." The concept
of a value being in a box, rather than null being a possible value makes it
impossible to use that value without getting it out.

People who write Go love that concurrency is easy and Go fmt has enforced a
single canonical style. Building these sorts of things into the language goes
a long way in getting them adopted and becoming the norm.

I think we saw a rise of the easy, anything-goes, screw-performance scripting
languages. I think the next fashion seems to be in enforcing "correct" coding
style. They all have their place.

~~~
cjfd
The haskell situation sounds like generally a good thing but I am not sure I
would like it very much if this also applies to logging.... It does not sound
like great fun to have to change the signature of a function when it needs to
log something and then change it again if it no longer needs to.

~~~
js8
For logging, you can use unsafePerformIO. Of course, you would call it inside
a special function that can do logging. In fact, there are functions in
Debug.Trace that do exactly that (to standard output).

Similarly, I used unsafePerformIO (again put into a convenient function) to
save a checkpoint data in a large computation. The computation is defined
pure, but it calls the function to checkpoint, which does in fact IO under the
covers.

Remember, type safety is there to help you. As long as the function performing
the I/O doesn't affect the outcome of the computation, everything is safe.

~~~
ghostwriter
> As long as the function performing the I/O doesn't affect the outcome of the
> computation, everything is safe.

except it's not! Your IO action may not affect the outcome of the computation
but it may launch the missiles in background, which changes everything. The
less contrived example would be - "computation is fine and is not affected,
yet we have our [production cluster deleted / disk space run out / money sent
to wrong recepients] by the IO action".

~~~
nybble41
Despite the name, unsafePerformIO isn't automatically akin to undefined
behavior in C. It _can_ cause undefined behavior if misused, the most obvious
example being the creation of polymorphic I/O reference objects which act like
unsafeCoerce—but that would be affecting the outcome of the computation. If
the value returned from unsafePerformIO is a pure function of the inputs then
the only remaining risk is that any side effects may occur more than once or
not at all depending on how the pure code is evaluated. As long as you're okay
with that there isn't really any issue with using something like Debug.Trace
for its intended purpose, debugging.

There are better ways to handle _logging_ , of course—you generally want your
log entries to be deterministic, and the ability to produce log entries (as
opposed to arbitrary I/O actions) should be reflected in the types.

------
DubiousPusher
This is my main issue with C++. For a while my job was to get game engine
codebases running, integrate tools and move on. So I saw a lot of big C++
codebases. Nearly every one had the same bad behaviors. Tons of globals.
Configuring build options from code. Header mazes that made it clear people
didn't actually know what code their classes needed.

I then worked for awhile developing a fairly fresh C++ code base. The
programers I worked with were very willing to write maintainable code and
follow a standard and it was still really damn hard to keep things like header
hygene.

When I go back to the language I can't believe how much time I spend dealing
with minor issues that stem from the bad habits it builds. For years I would
refuse to say any language was good or bad. Always I insisted you use the
right tool for the job. And there are some features of C++ that when you need
them you have to use that language or maybe C in its place. But the
shortcomings are unrelated to language's issues which largely seem to come
from a focus on backward comparability. And so even used in its right
application it seems incredibly flawed. And I pretty much believe it's a bad
language now.

Disclaimer: I learned to program with C++, I understand its power and for
years I loved the language. I also understand there are situations where
despite its shortcomings it is the right choice.

~~~
naikrovek
Why are globals considered bad? I'm seriously asking. I, too, have been told
hundreds of times over the course of my career, and I never questioned it. I
want to question it now, because I've never understood why people work SO HARD
to remove and avoid globals. I seriously doubt that the time and effort I've
seen spent on removing and avoiding globals has been time well spent. And I'm
quite sure that the effort spent on that is not comparable to the amount of
problems prevented by not having globals. There's just no way globals can be
dangerous enough to justify the size of globals-cleansing efforts I've seen.

Game development often has a very large global state, and game problems are
often inherently global state manipulation problems; you need globals in order
to even __have the game __in many cases.

~~~
bluejekyll
Global state is nearly impossible to test in any decent automated fashion.
When writing unit tests, globals are the bane of your existence.

If you’re relying on globals for passing data, they are also difficult to
reason about in multithreaded code.

There are means by which you can share data, that data if instantiated at the
code entry point, can be shared in such a way as to never need globals, and
rely on decent patterns for sharing between code points.

Yes, there is a trade off in adding parameters to functions, references in
classes, but these can be avoided by adopting patterns like inversion of
control, etc.

Basically globals are a bad pattern because they make it hard to test and hard
to reason about data access patterns.

~~~
naikrovek
I don't understand. If you have a good understanding of the code you're
writing, you won't put yourself into a position where globals cause problems
unless you're being very stupid, and if you do, normal use of the program will
detect those problems, right? Certainly bug reproduction steps and a debugger
will figure out what's going on.

You mentioned unit tests, and these are another thing I don't fully
understand. Obviously testing your code is important, and automated tests are
good. My beef with unit testing comes with the requirement that all methods
and functions have multiple tests each for success and failure conditions, and
that results in test code which outweighs tested code by several times. When
you discover that the architecture you've been putting together isn't going to
work, which is something that happens approximately 100% of the time if you're
doing anything real, you now have (say) 5,000 lines of code that needs rework,
and 50,000 lines of test code that need to be thrown away and rewritten.

That is A LOT of effort to shove onto yourself to avoid a few global
variables, to me. That's so much effort that many projects will just not make
the change and ship software that they know is insufficient, and then they'll
graft on whatever functionality can't be attained natively with the given
architectural decisions rather than redesign.

The ability to paralyze yourself with the weight of unit test code seems like
an extremely high price to pay to avoid some global variables.

~~~
NathanKP
> When you discover that the architecture you've been putting together isn't
> going to work

One of the underappreciated benefits of unit tests is it quickly teaches you
how to write good code. It turns out testable code is also code that tends to
be well architected and doesn't need to be rewritten. Basically writing tests
leads you to being a better programmer

~~~
naikrovek
I have never witnessed that in 15 years of working at places which write unit
tests. I've witnessed a LOT of unit tests which test nothing and manually
return the pass/fail result desired so the indicators stay green.

~~~
NathanKP
That's very unfortunate.

------
l0b0
That's a great metaphor for language smells! Some more anecdotes:

\- Python shepherds you into using list comprehensions, even when it's almost
always premature optimization and much harder to read than a loop. As a
language smell that's not bad, it's just the worst I could think of in a
post-v2 world. Luckily there's `black`, `flake8`, `isort` and `mypy`.

\- Bash shepherds you into using `ls` and backticks, useless `cat`s, treating
all text as ASCII, and premature portability in the form of "POSIX-ish".
Luckily `shellcheck` takes care of several common issues.

\- Lisp shepherds you into building your own language.

There's also tool shepherding:

\- IDEA shepherds you into refactoring all the time, since it's the only IDE
which does this anywhere near reliably enough. (At least in Java. In other
languages renaming something with a common name is almost guaranteed to also
rename unrelated stuff.)

\- Firefox shepherds you into using privacy extensions.

\- Chrome shepherds you into using Google extensions.

\- Android shepherds you into installing shedloads of apps you hardly ever
use.

\- *nixes other than Mac OS shepherd you into using the shell and distrusting
software by default.

\- Windows and Mac OS shepherd you into using GUIs for everything and trusting
software by default.

~~~
dgellow
> Windows and Mac OS shepherd you into using GUIs for everything and trusting
> software by default.

Not sure how familiar you are with modern Windows, but you can try powershell.
You can manage everything in Windows with it, and it’s a very cool shell
language with a lot of feature.

(It’s also open source and multi platform)

~~~
zro
Do you have any good sources for places to start learining powershell? I'm
pretty comfortable in bash, but powershell scares me.

~~~
l0b0
As someone who used PowerShell for a year and has used Bash for 10+,
PowerShell is much less scary than Bash. The scariest thing about PowerShell
is .NET, which (at least 8 years ago or so, so cue someone correcting that
below) had extensive but often low quality documentation, with undocumented
features you basically _had_ to use to write useful code, uselessly trivial
code examples (think "`0 + 0 == 0`" as an example of arithmetic) and some bad
names. That's not to say Bash is _better,_ just that they still had some way
to go.

------
savolai
The term for this in the field of human machine interaction is (perceived)
affordance, popularized by Norman.

This sounds like less active guidance than nudging or shepherding. Creating
affordances is still an active design choice though.

[https://en.wikipedia.org/wiki/Affordance](https://en.wikipedia.org/wiki/Affordance)

[http://johnnyholland.org/2010/04/perceived-affordances-
and-d...](http://johnnyholland.org/2010/04/perceived-affordances-and-
designing-for-task-flow/)

~~~
ricardobeat
Not sure the concept applies cleanly here. Affordance is about perceiving
possibilities from an _interface_ or environment.

For the subject of programming language idioms, the main factors are
restrictions or patterns the language offers, and how naturally they fit
within their context, not just perception by the user.

~~~
savolai
I’m curious, how do you make the distinction that
restrictions/patterns/context are not just as much perveived properties of the
interface to the device you are programming?

Are you perhaps perceiving perception as just visual perception? (See what I
did there? :)

------
fatso784
Paul Dourish reviews some work on this topic in his book Stuff of Bits, see
pages 8-9: [https://mitpress.mit.edu/books/stuff-
bits](https://mitpress.mit.edu/books/stuff-bits)

It's related to, but definitely not the same as, linguistic relativism.
Programming "language" might be a bit of a misnomer, because it creates a
false equivalency to natural language. Just as different subfields of
mathematics were created to solve different problems, so too were different
programming "languages" inspired by different subfields and their notations.
With that view, it's unsurprising that some ways of doing things highlight
certain methods of solving problems and obscure or impede others.

------
fileyfood500
Working in a Java/Kotlin environment, everyone always handles all null cases
when working in Kotlin, but they are frequently overlooked in the Java
applications. Many of the Java apps compensate with more levels of catch-all
exception handlers targetting unexpected NPEs. The only time we get NPEs in
Kotlin is when Kotlin allows them because of the Kotlin/Java interop problem.

Working with Javascript/Typescript, we need to rely on linters to enforce safe
practices in Javascript.

------
gameswithgo
Something i like about rust is it shepards you to fast running programs and
away from null pointer errors.

something i like about go is it shepards you to write code any other go
programmer can follow easily

something i dislike about c# is it has the tools to let you write very very
fast code but shepards you to use non devirtualized interfaces over heap
allocated classes tied together with linq overhead.

~~~
capableweb
> something i like about go is it shepards you to write code any other go
> programmer can follow easily

Sure, the syntax and indentation levels are all the same, but that's not
really the difficult parts of programming. The difficulty comes from
abstractions, indirections and other abstract things that Go, just as any
language, let's you do however you want.

There are of course codebases made in Go where the indirections makes no sense
and are hard to follow, just as in any language.

What Go shepards you into is to make really verbose code (well, compared to
most languages except Java I guess), where everything is explicit, unless
hidden by indirection. This is both a blessing and a curse.

~~~
tmountain
Go limits the number of available abstractions--yes at the cost of verbosity--
but compared to a language like C++, it's minuscule in its size. The end
result is that you can keep the entire language in your head, and you don't
have to go digging into the bowels of the internet to figure out how "turing
complete template metaprogramming" works since the last developer decide to
use that cool feature they just discovered.

~~~
capableweb
> Go limits the number of available abstractions

Again, it doesn't, as abstractions are not built from syntax of the language
but from the indirections developers create with the syntax provided. I agree
that the abstractions the standard library provides are smaller than in other
languages, but outside of that, anyone can create their own abstractions (as
it should be).

~~~
wtetzner
I would say Go doesn't limit the number of abstractions, it limits the number
of ways in which abstractions can be created. Or maybe better put, it doesn't
provide a very rich set of abstraction facilities.

------
btilly
It is the language plus the community. And not just the language.

As an example, there is nothing about Ruby that makes it more or less prone to
monkey-patching than many other dynamic languages. But once a certain number
of popular frameworks did that, there was no getting away from that. (Rails
even has a convention around where you put your monkey patches.)

~~~
pansa2
> there is nothing about Ruby that makes it more or less prone to monkey-
> patching than many other dynamic languages.

Python disallows making changes to fundamental types like `int` and `list`.
It’s not possible for a Python framework to support something like Rails’
`2.days.ago`.

Interestingly, I don’t think this was an explicit decision made when designing
Python - it’s just a side effect of the built-in types being written in C
rather than in Python itself.

~~~
switch007
> Python disallows making changes to fundamental types like `int` and `list`.

Not directly via Python but you can achieve if it you really want
[https://github.com/clarete/forbiddenfruit](https://github.com/clarete/forbiddenfruit)
:D

------
pietroppeter
I like the concept and I particularly like the way I feel nim shepherds me:

* I very rarely need to come up with a name for a function or other identifier. The correct name can be reused for multiple use cases thanks to the type system and prox overloading. * to spend a little time designing the interface before jumping in the code * but also to think what I really need to accomplish and get to it instead of building a grandiose architecture * to have consistent apis * to steer away from OOP * to rely on minimal dependencies and to be kind of minimal in general * to use the correct tool for the problem (macro are not easy to write and that’s good otherwise you will abuse them. Instead they are great to use) * to build main table code * ...

I would be interested in what other nimmers think are the good shepherding.

One might also think of what is bad shepherding of nim, although nothing comes
to mind at the moment.

~~~
beagle3
> * to use the correct tool for the problem (macro are not easy to write and
> that’s good otherwise you will abuse them. Instead they are great to use)

Just wanted to add: Nim has Macros (which are comparable to lisp forms) and
Templates, which are closer to C and Lisp macros and are much harder to abuse;
It also has built-in inlines and generics, which are essentially the prime use
for templates and macros in languages that lack those.

It also has stuff like operational transforms, which lets you state things
like "whenever you see x*4 and x is an integer, use x shl 2 itself", so that
you CAN apply your knowledge in the places where you know better than the
compiler, while still writing the code you meant to write (multiply by 4) and
not the assembly you wanted to generate.

Right tool for the right job is a very good description, which I don't think
many languages can claim - definitely not minimalist ones like K, nor kitchen
sink ones like C++ (no, an accidentally turing complete template expansion
system is NOT the right tool for compile time computation).

------
fsloth
The author is fairly insightful on various human behaviors in computing
systems.

You might enjoy his Mesonbuild build system[0], which has a full manual [1].
It's already used in bunch of high visibility projects [2].

[0] [https://mesonbuild.com/](https://mesonbuild.com/)

[1] [https://meson-manual.com/](https://meson-manual.com/)

[2] [https://mesonbuild.com/Users.html](https://mesonbuild.com/Users.html)

------
jbritton
When I program in C++, there are lots of things one must consider. Should this
be const, public/private, virtual, should I create a class, should I first
create an abstract base class, should I create a factory, should I implement
the PIMPL idiom, should this be a template function. The list of concerns is
nearly endless. When I write in Python I tend to mainly think about solving my
problem. In C++ I will naturally think more about performance and in Python
that concern comes only if something seems slow. I make no claims about which
is better, just that the language definitely affects me and the approach I
take.

------
muglug
This also can change over time. For example, 15 years ago PHP shepherded you
to include every file you were using explicitly, making it hard to reason
about a given project if you weren’t the creator.

A big effort ensued to change that — class autoloading became the standard,
and a large community arose around that standard.

Similarly, JavaScript shepherded you towards some bad practices that the
community has now found ample remedies for.

~~~
pitterpatter
> This also can change over time. For example, 15 years ago PHP shepherded you
> to include every file you were using explicitly, making it hard to reason
> about a given project if you weren’t the creator.

Huh, that seems backwards to me? Wouldn't the explicit approach make it more
obvious what scripts were relevant?

~~~
arthens
As someone who has written a lot of PHP and is still maintaining a few PHP
codebases... I don't miss the old ways at all. But I can see how the parent
comment can be misleading for someone who's not too familiar with PHP
(apologies if my assumption is wrong).

TL;DR in many ways modern PHP is more explicit than old PHP

The old way wasn't as explicit as the comment makes it. It was just painful.
Imports in PHP are global, so every file can use any function/class already
imported. You could explicitly define all dependencies at the top, but that's
not what the language shepherds you to. In reality most imports were implicit
(explicit in ANOTHER file), and you'd just import what you needed that hadn't
been already imported (I don't think I've ever worked in a codebase where
every file has all dependencies declared at the top). The old ways had so many
downsides:

\- moving things around could produce fatal errors just because the implicit
imports changed

\- any framework/cms using require instead of requice_once would limit your
ability to import files (including the same class twice creates a fatal error)

\- very poor support for IDEs

The modern way is quite pleasant to work with:

\- everything should be in a namespace

\- every used class should be declared at the top of the file (like Java)

\- great IDE support (auto imports, auto complete, click to go to definition)

Yes there's some extra complexity in the autoloader, but in my experience it's
negligible. If you use an IDE you might never even see it.

So yeah I don't think PHP got any less explicit. The opposite actually, modern
libraries/frameworks tend to be much easier to navigate.

~~~
muglug
Yeah, my explanation wasn't good. Yours is much better – thanks!

If anyone's curious what the old way looks like, have a look at WordPress's
codebase – lots of imports scattered around everywhere with no one single
class-loading system.

------
rienbdj
Yes! Another example.of this is C# and F#. Both build on the same .net base
and both are Turing complete languages with elements of OOP and FP, but wow,
the code that those communities write are completely different. They each
steer you in different directions.

------
drewm1980
Usually people say language X "encourages" you to do Y. or "biases", but
"shepherds" is a fine word too.

C, C++, and Rust shepherd the programmer to use array of structure memory
layout. (though it's usually not great for vectorization)

~~~
JoeCamel
Do you have examples of languages which don't encourage you to use array of
structures?

------
Random_ernest
An imo really nice but lengthy explanation of this is given by Robert Martin
here: [https://cleancoders.com/video-details/clean-code-
episode-0](https://cleancoders.com/video-details/clean-code-episode-0)

He calls this restriction by paradigms.

~~~
jt2190
Thank you for sharing this. This is a _much_ more thoughtful overview of the
topic.

------
LoveMortuus
Is shepherding present in every aspect of life?

My guess would be that it is and that shepherding is what we talk about when
we say that we can learn from every aspect of life.

If what I wrote is correct, than shepherding is the teacher of reality. But I
guess it's on us to decide when we've learned enough and move on.

I was at first wanting to ask if shepherding is present in video games as
well, but then I realized what shepherding could actually be.

SHEPHERDING; The part of an aspect that _can_ teach you something.

_can_, because it's up to you to decide if you'll learn anything.

Is shepherding always negative or can it be positive?

Also, if we would always strive to fix what shepherding teaches us, would that
mean that in infinite amount of time we would reach perfection?

And, last question I swear, is shepherding subjective or objective. Or is it
both?

~~~
m12k
Shepherding is when some behavior is encouraged by being made easy/rewarded,
or by other behavior being made harder/punished. So shepherding absolutely
happens in every aspect of life - systems move toward low energy states, water
flows downhill and living creatures tend to follow the path of least
resistance. When parents do it, we call it parenting. When governments or
companies do it (via taxes/subsidies or pricing), we call it nudging. When
groups do it, we call it socialization.

It's a really useful tool to analyze systems and organizations with - not by
looking at what they make possible, but also by what they encourage and
discourage - most of the time, the latter are what really matter (the average
case is usually what determines the long-term impact of something, not the
best or the worst case). When Netflix has auto-play at the end of a stream,
they shepherd you toward binging. When Animal Crossing has things you need to
wait wall-clock time for, they encourage you not to binge. When free-to-play
games come with loot boxes, they don't force you to do anything, but they
might still be shepherding gambling-like behavior.

------
kkdaemas
This is also why CMake, even Modern CMake, is so bad. You have to fight tooth
and nail for something even remotely maintainable.

------
chrisweekly
Title reminds me of "guide you to the pit of success" (ie, a slippery slope w
a positive ending), which IIRC I first encountered in a post by Zeit cofounder
G Rauch, writing about NextJS.

~~~
golergka
That's exactly what I thought about. I think this is the blog post that made
this phrase popular: [https://blog.codinghorror.com/falling-into-the-pit-of-
succes...](https://blog.codinghorror.com/falling-into-the-pit-of-success/)

~~~
qntmfred
This is exactly what I thought of while reading the OP article. I love this
metaphor and use it often

------
cafard
" Yet, in practice, many Perl scripts do XML (and HTML) manipulation with
regexes, which is brittle and "wrong" for lack of a better term. This is a
clear case of shepherding. Text manipulation in Perl is easy. Importing,
calling and using an XML parser is not."

Importing, calling, and using an XML parser is straightforward in Perl. The
same for HTML: think about what you want to process, what you want to ignore,
and write your callbacks accordingly.

~~~
fmakunbound
This struck me as clueless part of the article.

Perl coding was a significant portion of my career, and much of that involved
web services, involving XML. I've never come across code that attempted to
parse XML with regexes in Perl. It was always done with easy-to-import-and-
call XML DOM or SAX parsers.

------
djyde
You read the code. The computer program you.

~~~
collyw
There is probably more truth in this than people realise at first glance.

------
_bxg1
This is a very good take. I've had a whiff of this idea for a while now, but
never fully-formed it. As the author says, it's something that's been missing
in lots and lots of language and framework debates and has caused people to
talk past each other over and over. It's also a very helpful tool when
examining why the cultures around certain technologies are the way they are.

------
zzo38computer
I believe these things, and is why I think different programming languages are
good for different purposes and are why I use different programming languages
rather than only one kind. (For example, Haskell isn't a programming language
I use much although I do use it sometimes because sometimes what I make is
what it seems like good for to me.)

------
skywhopper
Haha, the idea that a "proper" scripting language is more portable than a
shell pipeline is amusing. Sure it is, if you can guarantee everyone has the
same version of Python, and all the right libraries at the right versions,
etc. But if you can do that, you can probably have them all install the GNU
userland on their Macs, too.

------
mickduprez
Be the shepherd, use Lisp :D

Seriously though, what other languages other than Lisp (all the mainstream
ones at least) give you the freedom to change the language and/or create DSL's
with the same ease? And you can still do your 'bare metal' in C if you really
really need to and bring it in.

~~~
_bxg1
I actually think the OP perfectly explains the core _problem_ with Lisp: Part
of the value of shepherding is getting everyone on the same page. When
everyone's their own shepherd, nobody is on the same page.

~~~
Fellshard
Javascript is this on steroids: It wants to be inspired by Lisp, by Self, by
Java, and the resulting melange smells like every paradigm while fitting none
of them. Trying to write applications in Javascript is trying to corral the
minds of hundreds of people who each had a different idea of what Javascript
is and what it does and what it wants _you_ to do. The resulting system is
doomed to total incoherence.

~~~
osdiab
What's mostly resulted in Javascript land is people using opinionated (and
often severely constrained) boilerplate generators like create-react-app, with
opinionated linters like Prettier (alongside transpiling Javascript from other
languages like TypeScript), that force a coding still despite the myriad ways
to achieve things - I've actually found that to mitigate a lot of these issues
well, but it can't deal with the boatload of "bad" advice online to wade
through.

~~~
Fellshard
Even with opinionated generators, what happens when you pull in a library that
comes from a very functional mindset, a second that treats JS like Smalltalk,
and a third that was thrown together by a novice and has become the de facto
standard for its purpose?

The issue doesn't just lie within the baseline of the code you write, but in
how many disjoint dialects that code must interact with and partially conform
or contort to in order to engage with.

------
DmitryOlshansky
Even broader look on this is which run-time properties the resulting _typical_
code has.

Most languages _tend_ to produce a distinct behavior patterns and properties,
such as (in no particular order):

\- multi-threaded / single-threaded / multi-process

\- frequency of memory safety bugs

\- broken locale / unicode handling

\- broken or non-standard network settings handling

\- only synchronous I/O calls (or in contrast only async I/O)

\- releasing memory back to OS (or not)

\- fragmenting heap over time (or not)

\- fast / slow text processing

\- fast / slow binary codecs

------
elevenoh
"shepherding: An invisible property of a programming language and its
ecosystem that drives people into solving problems in ways that are natural
for the programming language itself rather than ways that are considered
"better" in some sense"

Seems like most could lend a little more weight to: 'does this lang align with
how do you want to think about/represent the problems you're solving'

~~~
hinkley
The corollary for language and API designers: whatever you make easiest is
what people will do.

If there is a 'right' way to do something, make that the default or the
simplest calling pattern. If there is a new 'right' way, don't route the new
way directly through the old way. People will think they're cutting out the
middleman by keeping the old calling convention as long as possible.

~~~
toast0
> If there is a new 'right' way, don't route the new way directly through the
> old way. People will think they're cutting out the middleman by keeping the
> old calling convention as long as possible.

It's best if the old way becomes a shim onto the new way --- that shows that
the new way works, and encourages moving to the new way -- nobody wants to
keep a shim they don't need, and if the new way can do everything, the
compatability shim isn't needed.

------
okareaman
JavaScript shepards you into monkey patching

~~~
dlbucci
Shimming and polyfilling (i.e. ensuring standard functions are present and
what not) sure. But I think most codebases avoid monkey patching (i.e. adding
new functionality to built ins).

Maybe it's up for debate if the language itself guides people in that
direction, but no one seems to do it anymore, so I personally don't think it
does.

~~~
_bxg1
The new culture around modules actively shepherds people _away_ from monkey-
patching. Whether or not Webpack is considered "part of the language" for
these purposes is fairly academic, but yes, the reality is that people don't
really do that any more.

------
sjakobi
There's a similar blog post by Gabriel Gonzalez which focuses on the
incentives set in Haskell:

[http://www.haskellforall.com/2016/04/worst-practices-
should-...](http://www.haskellforall.com/2016/04/worst-practices-should-be-
hard.html)

------
black_thoughts
I would have liked to have seen more examples of what different languages
shepherd you to do.

Python, in my experience, shepherds you to import a lot of modules rather than
writing your own. There's so many helpful ones available and that's kind of a
reality shock if you switch to some other languages.

------
raleighm
I like this. The insight is true of tools generally, not only languages. It's
true at the feature/affordance level. Also at the tool level. "To a man with a
hammer everything looks like a nail."

------
PeterStuer
My take was always language quality is not about expressive power, because
it's fairly difficult to come up with a non Turing complete system anyways, it
is about what is easy to understand and modify.

~~~
collyw
Isn't it a balance?

VB6 code was easy to understand on one level, but its lack of expressiveness
lead to a lot more code. On the other hand when I look at Ruby code there is
too much syntactic sugar for a non-ruby programmers to understand it without
looking some thing sup.

------
DeathArrow
ZX Spectrum Basic shepherded you into using goto keyword which I've heard it's
the single most evil thing to do in software development.

~~~
onion2k
In Speccy Basic a GOTO jumped to a line number, so if you added code in that
changed your numbering the GOTO broke. That was managed by using numbers that
left space (10, 20, and so on), but it was still horrible in a big program.
Another issue was that you couldn't go back (that's what gosub was for). They
also made code hard to follow because the execution jumped around all over the
place, but that was less of a problem.

These days languages that have GOTO usually jump to a label so it's not quite
as bad. They're still likely to end up as spaghetti though.

And yes, I am old.

~~~
pansa2
Modern languages usually only support goto within a single function. This
significantly reduces the potential for spaghetti, compared to 8-bit BASICs
that allowed you to jump anywhere within a program.

------
jt2190
While this topic is fun, it smells like complete BS to me.

Why? Because the problem of people choosing the wrong tool ( _not_ the tool
"shepherding" the person into using it) is older than programming [1].

We can do better than to (once again) make up an elaborate way of blaming our
tools.

[1]
[https://en.wikipedia.org/wiki/Law_of_the_instrument](https://en.wikipedia.org/wiki/Law_of_the_instrument)

~~~
Chris2048
What is the right tool in each of these cases? If you have a PERL codebase, do
you take XML with a totally different language?

~~~
leejo
2004-2008 I worked at The Press Association - we were processing gigabytes of
XML a day, many of it in real time feeds pushed out to the likes of the BBC,
large newspaper publishers, and betting shops.

All of this was done with Perl. None of it was done with regular expressions -
it was done through XML::LibXML, and thus libxml. Whenever I hear other perl
devs complain about XML processing my answer is always the same - just use
XML::LibXML.

Choosing a different language might gain some advantages, but if you still
choose the wrong libraries you're still going to have pain.

~~~
Chris2048
But if "choose the right tool" includes "use the right libraries", how does it
differ from "write good code"? At that point arguing that you should use the
right tool is arguing about being disciplined?

------
RMPR
Typo

> shepherding An invisible property of a progamming language

------
JoeAltmaier
If all you have is a hammer, everything looks like a nail.

~~~
lsh
My favourite when working in teams is: "use the right tool for the right job",
which presupposes you know many tools and have experience working with them
... and that the shared set of tool knowledge in the team is more than just a
hammer and a nail.

------
oyebenny
Facts.

------
eyegor
I agree with all the points brought up in this article except for this
curveball:

> Turing complete configuration languages shepherd you into writing complex
> logic with them

Everything discussed in this post is a consequence of language design or best
practices. I don't think shoving complex logic into yaml files would be
considered either. This is more a possibility as a result of Turing
completeness, I don't think the language design has anything to do with it.
All the other "shepherding" examples are clearly intentional choices by the
language designers.

~~~
jdfellow
I think that the author of the article specifically speaking of configuration
DSLs that were intended to be Turing-incomplete but somehow grew full Turing-
completeness and therefore became bastard languages. YAML as a config DSL is
sub-par (but I'll take it over JSON any day), and truly declarative, Turing-
incomplete config languages are great, as is just using a fully-featured
scripting language such as a Lisp or Python, but the middle-ground with a
custom config language that is also Turing-complete is usually just bad.

Not sure I have any examples of what that might be. Perhaps nginx? or apache?
Lots of complex software have very complex config languages that might
accidentally be Turing-complete, but only fools would actually use them like
that.

~~~
ekimekim
Often the turing completeness comes not from the config _format_ but from the
semantics. A good example of this is Logstash config, where your config is an
array of steps to apply, each of which can filter things in certain ways based
on certain conditions, and before you know it you've used 100 lines to badly
and buggily implement a 5-line script in a real language.

