Skip to content

Commit

Permalink
Find and replace straight quotes with smart quotes
Browse files Browse the repository at this point in the history
Remark and Smartypants used to do smart quote replacement automatically at HTML generation time. With Astro and MDX, supposedly the parser only sees 1 quote character at a time, zero context, and Smartypants becomes useless.

I did a bulk replace via a combination of `smartypants <file>` and `recode html..utf-8 <file>` and manual review.
  • Loading branch information
john-kurkowski committed May 4, 2024
1 parent 8ca0b1f commit 0015c2b
Show file tree
Hide file tree
Showing 12 changed files with 491 additions and 391 deletions.
5 changes: 1 addition & 4 deletions .stylelintrc.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
{
"extends": [
"stylelint-config-html",
"stylelint-config-standard"
],
"extends": ["stylelint-config-html", "stylelint-config-standard"],
"rules": {
"at-rule-no-unknown": [
true,
Expand Down
38 changes: 19 additions & 19 deletions src/content/posts/2011-10-06-avoid-git-first-drafts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,63 +16,63 @@ Here are
[debate](http://jasoncodes.com/posts/gup-git-rebase) on using `git pull` vs.
`git pull --rebase`.

I've decided for myself there are times for both, that is, there are times for
merge commits and not. There's no hard rule, but I use the former when I want to
Ive decided for myself there are times for both, that is, there are times for
merge commits and not. Theres no hard rule, but I use the former when I want to
demarcate a feature occurring over several commits. I use the latter if a series
of commits can be logically squashed to 1-2 commits before pushing; rebasing
here means a tighter, linear history that is easier to follow.

In both cases still I clean up and squash commits before pushing.

A StackOverflow devil's advocate submitted an answer with the rhetorical
A StackOverflow devils advocate submitted an answer with the rhetorical
question, why all this need to rewrite history to present it the way you wished
it had happened? Why not present it the way it really happened? In both cases
you ultimately get the completed feature. And letting the history be means less
jumping through hoops.

But I've always found I prefer to rewrite history to present it in a clearer,
But Ive always found I prefer to rewrite history to present it in a clearer,
more purposeful way. Code is read more often than it is written, and version
control can serve as an indicator as to not only when and how things were done,
but also *why*[^1]. If you publish history exactly as it was, you lose this
clarity.

A book author never publishes first drafts. Book readers never see the book with
rough around the edges. So readers puzzle less, altogether avoiding questions
like, what does the author mean here? Or, did he really mean to include this
sentence?
A book author never publishes first drafts. Book readers never see the book
with rough around the edges. So readers puzzle less, altogether avoiding
questions like, what does the author mean here? Or, did he really mean to
include this sentence?

No creator gets their art right the first time. Programmers are no exception.

If programmers were to always publish first drafts,

1. they'll break the build a lot more (or worse) and
2. when a reader reviews their code, whether it's `HEAD` or ancient history, the
reader may interpret the author's initial mistakes as being correct at the
time. Even if the reader's initial reaction is confusion, they may assume the
1. theyll break the build a lot more (or worse) and
2. when a reader reviews their code, whether its `HEAD` or ancient history, the
reader may interpret the authors initial mistakes as being correct at the
time. Even if the readers initial reaction is confusion, they may assume the
change was done for a good reason.

Assuming all code is intentional and correct can be a catastrophic attitude to
be sure, but unfortunately there isn't enough time to treat every foreign line
be sure, but unfortunately there isnt enough time to treat every foreign line
of code as hostile. You might as well make things easy as possible for future
readers, yourself included.

First, commit often in logical, functioning steps, to ease wrapping your head
around the change and later reapplying or reverting it. Keep up to date but
around the change and later reapplying or reverting it. Keep up to date but
erase superfluous merge commits. Then clean up and reorder code and history
before pushing. Make it as correct as possible and easy to follow.

See
also [Understanding the Git Workflow](http://sandofsky.com/blog/git-workflow.html),
See also
[Understanding the Git Workflow](http://sandofsky.com/blog/git-workflow.html),
explaining the beauty of unpublished vs. published history.

_**Update 2/1/2013:**
[This blog post on git-rebase](http://blog.izs.me/post/37650663670/git-rebase)
comes from a much wiser open source & Git wizard. It echoes my sentiments here,
but it shows rather than tells!_
comes from a much wiser open source and Git wizard. It echoes my sentiments
here, but it shows rather than tells!_

### Footnotes

[^1]:
Self-documenting code is the first, greatest step toward this, but over the
history of a large codebase, it's impossible to express every change at a
history of a large codebase, its impossible to express every change at a
single glance.
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ tags: [scala]
description: >-
This is an adaptation of an internal blog post I made for Gravity engineers.
To preface, Gravity likes to use 2 very predictable patterns throughout its
Scala codebase, which is very important for a TIMTOWTDI language like
Scala: the for-comprehension and ValidationNEL.
Scala codebase, which is very important for a TIMTOWTDI language like Scala:
the for-comprehension and ValidationNEL.
---

This is an adaptation of an internal blog post I made for
[Gravity](http://gravity.com) engineers. To preface, Gravity likes to use 2 very
predictable patterns throughout its Scala codebase, which is very important for
a TIMTOWTDI language like Scala.

1. The **for-comprehension:** it expresses so much of Gravity's business logic,
1. The **for-comprehension:** it expresses so much of Gravitys business logic,
so it is valuable to see its familiar syntax over and over.
2. **ValidationNEL:** it captures success and failure states better than
traditional imperative means.
Expand All @@ -30,7 +30,7 @@ There may be differences in the examples.)
---

Beyond ValidationNEL distinguishing success and failure cases, its other sell is
accumulation of failures. I think it's superior to throwing a single exception.
accumulation of failures. I think its superior to throwing a single exception.
From
[one Validation tutorial](http://www.lunatech-research.com/archives/2012/03/02/validation-scala):

Expand All @@ -40,10 +40,10 @@ From
> other ways, but that means riddling your program with try-catch blocks around
> every sub step, which clutters the code beyond recognition.
However in Gravity's typical usage of ValidationNELs, they don't accumulate
However in Gravitys typical usage of ValidationNELs, they dont accumulate
failures; they tend to produce exactly one failure, if any. This post will
explain why, what use-case it fulfills, and the magic to actually accumulate
failures, if that's what you want.
failures, if thats what you want.

Of its 2 use-cases for ValidationNELs,

Expand All @@ -56,17 +56,17 @@ Gravity tends toward #1. This is the easiest way to use ValidationNEL, since you
can use familiar syntax: the **for-comprehension**. However this tends to only
ever produce one failure.

Let's get one thing out of the way: if this is the case, why use a ValidationNEL
Lets get one thing out of the way: if this is the case, why use a ValidationNEL
over a Validation at all? ValidationNEL is preferable throughout the codebase
regardless of how many potential failures can occur because it plays nicer with
type inference. If you accidentally mix e.g. `Validation[String, Int]` with
`ValidationNEL[String, Int]` you'll get a `Validation[Object, Int]`. D'oh. It's
`ValidationNEL[String, Int]` youll get a `Validation[Object, Int]`. Doh. Its
also future-proof if you later decide to yield additional failures. On the flip
side, you must accept the wart of running `NonEmptyList.list.contains` to handle
specific errors; perhaps write a custom extractor.

So why are we practically restricted to one failure? A for-comprehenion is often
sugar for calls to `flatMap`[^1]. This could also be called fail-fast. Let's
sugar for calls to `flatMap`[^1]. This could also be called fail-fast. Lets
review `flatMap` behavior.

```scala
Expand Down Expand Up @@ -94,7 +94,7 @@ for {
```

So when `flatMap`ping over a series of Validations, as soon as one of them is a
`Failure`, you won't reach the others.
`Failure`, you wont reach the others.

```scala
val sumV: ValidationNEL[String, Int] = for {
Expand Down Expand Up @@ -199,7 +199,7 @@ if (fails.isEmpty) {
// Failure(NonEmptyList(Error))
```

Awkward, no? You'd have to copy & paste that block for every different
Awkward, no? You'd have to copy paste that block for every different
accumulation strategy. And tweak each slightly.[^2]

So, I've shown you how to accumulate a handful of Validations. If you're
Expand All @@ -208,7 +208,7 @@ more examples in
[A Tale of 3 Nightclubs](https://gist.github.com/oxbowlakes/970717). Take a look
for calls to `sequence` which essentially inverts a sequence of Validations into
a Validation of a sequence:
`Seq[ValidationNEL[E, A] => ValidationNEL[E, Seq[A]]`. It's beyond my current
`Seq[ValidationNEL[E, A] => ValidationNEL[E, Seq[A]]`. Its beyond my current
ability to explain the type lambda on `sequence` though, so good luck.

You can stop here with your newfound Validation knowledge if you like.
Expand All @@ -217,34 +217,34 @@ Otherwise, onto the more theoretical digression.
### Validation As Monad? Validation As Applicative Functor?

Back to `|@|`. Why another operator? Why this promise of Validations playing
nicely with for-comprehensions—syntax we already know—and yet we can't get
failure accumulation? Because of Scalaz's definition of Validation as a monad.
Though very convenient for much of Gravity's business logic, Validation defining
nicely with for-comprehensions—syntax we already know—and yet we cant get
failure accumulation? Because of Scalazs definition of Validation as a monad.
Though very convenient for much of Gravitys business logic, Validation defining
`flatMap` is an accident. Validation is primarily an _applicative functor_.

It's beyond this post's scope to fully explain the utility of monad and
applicative functor typeclasses, so I'll just say that monads and Scala
Its beyond this posts scope to fully explain the utility of monad and
applicative functor typeclasses, so Ill just say that monads and Scala
for-comprehensions go hand in hand. When I said Gravity expresses so much of its
business logic in for-comprehensions, that means our business logic is monadic.
If you've written a for-comprehension, or directly called `flatMap`, you've used
If youve written a for-comprehension, or directly called `flatMap`, youve used
a monad.

But Validation, being an applicative functor, has more power than `map` and
`flatMap`, that a for-comprehension can't harness. Borrowing from
`flatMap`, that a for-comprehension cant harness. Borrowing from
[this intro to monadic design](http://softwarejockey.wordpress.com/2012/04/30/a-taste-of-monadic-design/),
&#8220;&#8230; unlike a monad, an applicative functor can carry forward results
of previous computations.&#8221; Sounds like the accumulation we need.
“… unlike a monad, an applicative functor can carry forward results of previous
computations. Sounds like the accumulation we need.

Unfortunately there's no native Scala syntax for applicatives. You have to use
Unfortunately theres no native Scala syntax for applicatives. You have to use
the funny Scalaz `|@|` operator and its kin.

I didn't realize that Validation isn't supposed to be a monad until
I didnt realize that Validation isnt supposed to be a monad until
[Validation.flatMap was removed in a Scalaz 7 milestone](https://github.com/scalaz/scalaz/commit/061e23de4848e3f97595d5a9ba1920c8827ffe41).
The community has since brought it back for backwards-compatibility, so
Validation will continue to be targetable by for-comprehensions. But now you
know: it can viewed as a monad and an applicative functor, and they behave
differently (if you like to think in terms of these typeclasses, you know
Validation is similar to Either. Think of Scalaz's `flatMap`-carrying Validation
Validation is similar to Either. Think of Scalazs `flatMap`-carrying Validation
as a right-biased Either monad, as its `flatMap` would only deal with its
right-side/success). For more Validation potential though, use it as an
applicative functor with `|@|` and company.
Expand All @@ -253,10 +253,10 @@ applicative functor with `|@|` and company.

[^1]:
For-comprehensions compile to `flatMap`, `map`, `filter`, and/or `foreach`
calls. That's it.
calls. Thats it.

[^2]:
Reminds me of clinging to old, verbose, C-style
`for (int i = 0; i < length; i++) { ... }` blocks. Nowadays, you use `map`,
`for (int i = 0; i < length; i++) { }` blocks. Nowadays, you use `map`,
`filter`, and friends. Declarative over imperative. Separate what you want
from how you get it.
Loading

0 comments on commit 0015c2b

Please sign in to comment.