When Rich Hickey speaks, it’s a good idea to listen. He is able to see things
that many of the rest of us cannot, and he is able to analyze and explain his
thoughts with a precision that is rare even among programmers.
Little did I know that there was a problem with semantic versioning. It seemed
like a reasonable system, and I never gave it a second thought until I watched
this presentation where Rich exposed what the semantics of this system actually
Briefly, the semantics of x.y.z
- z changes - don’t care.
- y changes - don’t care.
- x changes - you’re screwed.
That’s all this system can tell you.
Furthermore, this system implicitly formalizes software breakage by saying if
you change x, you’re allowed to make backward incompatible changes.
However, do we have to break our software?
Rich points us toward another way.
0:58 This is not a talk about spec
It’s a talk about what spec is about
- Giving something to someone that they can use
- Making a commitment
- i.e. not taking it away later
is it a thing?
- from Latin cambire “to exchange, barter”
You could change a cow into wheat!
- c.f. Eurogames
One-sided change is… theft?
- Anti-social at least
Productivity and soul-crushing at worst?
We want our software (esp.
libraries/services to be
What will that mean for our consumers?
[image of versioned library dependency tree]
Artifacts don’t actually use other artifacts
There is nothing in the code about artifacts
7:45 Dependencies Redux
[image of versioned library dependency tree broken down by namespace]
[exposes opportunity for dead code elimination; don’t need library z]
Namespaces/packages don’t actually use other namespaces/packages
At least require/import appear in the code
Where is the mapping to artifacts? [no mapping of artifact to namespaces]
10:48 Dependency Truth (code)
[image of namespaces further broken down by function dependencies]
[shows hat lib X is not a true dependency]
13:45 Do deps Force Versioning?
“What you can do is let Semantic Versioning provide you
with a sane way to release and upgrade packages without
having to roll new versions of dependent packages”
Is that what happens?
Often, no, cascading version bumps
to let root ‘know’ about improvements to leaves, even if path nodes’ code unchanged
15:27 Names, Levels, Scopes, Contexts
fns depend on (call) fns by name
ns/packages requires/includes set up a context in which those calls can succeed
Artifact deps/poms set up a context in which those requires can succeed
fn name scopes include ns but not artifact
- artifact context is MAGIC
Why do we put things in our deps?
We need access to libs while developing
Maven chases transitive deps
Incorporated in artifact to communicate needs to our consumers
“we tested against x,y,z”
But, course-grained, implies too much
- doesn’t capture actual deps, just context
19:49 (Ex)changes in Software :important:declarations:
What is required?
fn - args
ns - var names
artifact ns/package names/paths
What is provided?
fn - ret (proc/service effect)
ns - vars/fns
artifact - namespaces/packages
22:11 Growing Your Software
- provide more
- require less
- bash bugs
24:14 Breaking Your Software
25:55 Change is Not a Thing
It’s one of two things
In the small (fns), spec can help us determine which, prevent breaking specs
As long as we don’t try to do something silly like versioning specs!
27:56 Recognizing Collections
You only ‘change’ a collection by adding/removing members
Adding = growth, removing = breakage
Namespaces - collections of vars/fns
Artifacts - collections of namespaces/packages
Don’t conflate levels!
- My family doesn’t change when I put on a hat
(Everything intersting happens at the leaves.)
29:52 “Semantic” Versioning “Semantics”
- “you don’t care”
- “you don’t care”
- “you’re screwed”
31:18 Even worse…
“you might be screwed”
Considered covering of change at all levels
- Good luck determining where
Might just as well change the name
32:26 Might as well change the name
32:28 MIGHT AS WELL CHANGE THE NAME
that’s not change,
That’s a new thing!
33:09 Which name?
33:39 Requiring More args? Providing Less on return?
i.e. incompatible spec
old-ns/foo-2 or new-ns/foo
N.B - the namespace is port of the name
- ns aliases can ease transition
36:14 Providing Fewer fns/vars?
37:20 Providing Fewer namespaces/packages?
but… that’s what MAJOR segment is for?
no, “any backwards incompatible changes”
i.e. too-broad “semantic”
The problem is artifact->namespaces magic means possibility of collision
- No ‘scope’ implicitly renaming children
[Rich was not sure what the right strategy for this situaion is.]
40:33 Doesn’t Doing the Right Thing Name-wise Make You Reluctant to Remove Things?
41:02 Breaking Changes are Broken
Don’t do it
Don’t try to figure out the best way to do it
Avoid breakage by turning it into accretion
- old and new can co-exist
42:25 So Maven is Broken?
Not really - how we use it may be broken
Maven central doesn’t let you ‘change’ artifacts
and never ‘breaks’, is not versioned!!
- no “I’m using maven central 1234567.0.0”
You’re always hapy to use latest maven central
it’s an accreting collection of immutable things
44:27 (insert rotten sandwich image here)
46:29 So SemVer is Broken?
It is, in part, about how to ship breakage
and the other ‘semantics’ are of little utility
Maybe chronological versioning?
Conveys more and supports some forms of relativism
48:47 What about Git?
Wonderful, immutable, truth-of-the-code system, widely adopted
Almost completely ignored by artifacts/versioning, even though code basis
Deserves a role
- but, SHAs vs order/causality/readability
50:37 It’s a Social Thing
We won’t be albe to tech ourselves out of this
We need to agree that treating each other well is important
51:18 Local dev vs Open dev
Incompatible churn acceptable in private
Slack is not standup
OS user base is open and unknown
54:18 Coding for Growth
Open specs and data formats are key
Specs are about what you can do, not about what you can’t
Prohibition turns growth into breakage, cascades
Always presume you might be handed more than what you need or know about
- ignore, or have policy for it
58:31 What about Iterative Development?
Alphas are OK
But maybe should be in artifactId
Or - incremental API ‘publishing’
Open source is not an excuse for indefinite public thrashing around
1:00:14 The Only Truth is Runtime
Deps/POMs are just suggestions
- can be full of ‘conflicts’
Someone needs to build a classpath
- that alone determines runtime context
Possibly a lib set that none of the components have ever run against
1:01:59 Testing is Runtime Dependent
Plus, you can’t test against an open set of consumers
Artifact release testing is inherently limited
We could be reifying artifact sets at a macro (e.g. app or multi-app) level
1:03:47 (Live Coding Demo)
1:04:00 What about Web Services?
Same problems, same mistakes
conflating collections w/contents
Web service is collection of ops
Accretion could prevent a lot of client/service version hell
1:07:04 We Need to Bring FP to the Library Ecosystem
Currently update-in-place, excused but not corrected by versioning
- dependency hell == mutability hell
Makes programming fragile
Libraries less useful
- Even undesirable, whatever their features
1:08:19 “This is Impossible/Impractical”
Many examples of decade+ compatibility
Compatibility a prerequisite of success?
1:09:47 What If We Never Broke Anything?
names become enduringly meaningful
orthogonal compatibility checking becomes possible
find-grained deps can be explored
use the latest with impunity
compose with impunity
1:10:54 Open Challenges
We can’t ‘see’ some changes
collection adds are easy
fn args/ret not visible in source
- specs can help
spec compatibility context-dependent
- provide vs require
repo->artifact->namespaces not first class
1:12:40 Opportunities for Clojure
Flexible (vs fragile/brittle) dep awareness
Explicit code->artifact/repo support
First-class, fine-grained ‘publish’ for APIs
- ditto deprecation
Testing based on fine-grained deps
- generative testing needs this
1:14:23 Exchange > Change
Grow your software
Give birth to variation
- Don’t break, accrete
Think of the -children- consumers
Move forward without burning bridges
Create a lib/service ecosystem of which we can all be proud