├── .gitignore ├── LEGAL ├── Makefile ├── README.org ├── rkt ├── .dir-locals.el ├── make-css.rkt ├── make-new-post.rkt ├── make-non-post-html.rkt ├── make-post-cache.rkt ├── make-post-html.rkt ├── make-preview.rkt ├── make-sitemap.rkt ├── make-tag-feed.rkt ├── make-tag-index.rkt ├── make-tag-list.rkt ├── page-xexpr.rkt ├── post.rkt ├── site.rkt ├── tag-posts.rkt ├── util.rkt └── xexpr.rkt └── src ├── non-posts ├── About.md └── error.md ├── posts ├── 2011 │ └── 12 │ │ └── domain-registrar-switch.md ├── 2012 │ ├── 02 │ │ └── google.md │ └── 08 │ │ └── ancient-history.md ├── 2013 │ ├── 10 │ │ └── interview-and-racketcon-talk.md │ ├── 11 │ │ └── markdown-parser-redesign.md │ ├── 12 │ │ └── racket-package-management.md │ ├── 01 │ │ ├── fear-of-macros.md │ │ └── series-a-round-human-worker-theme-park.md │ ├── 02 │ │ ├── clear-interrupts.md │ │ ├── compare-apples-to-oranges-with-the-nexus-4.md │ │ ├── computing-like-component-stereo-systems.md │ │ ├── fucking-suggested-post-why-web-apps-matter.md │ │ ├── greg-head-first-greg.md │ │ ├── my-chrome-extensions.md │ │ ├── walking-in-the-steps-of-soft-interrogation.md │ │ └── we-need-a-prior-art-database.md │ ├── 03 │ │ ├── feed-stats-in-frog-without-feedburner.md │ │ ├── frog-overview.md │ │ ├── host-your-own-web-apps.md │ │ ├── keyword-structs.md │ │ ├── killing-google-reader.md │ │ ├── live-with-frog.md │ │ ├── lull-while-i-prepare-to-change-tires.md │ │ ├── my-google-reader-successor.md │ │ ├── serve-static-files.md │ │ └── the-year-google-became-evil.md │ ├── 04 │ │ ├── a-guide-for-infrequent-contributors-to-racket.md │ │ ├── parameters-in-racket.md │ │ ├── planet-vs-the-new-package-system.md │ │ └── roger-ebert-not-engines.md │ ├── 05 │ │ ├── chromebook-pixel.md │ │ ├── feeds2gmail.md │ │ └── threading-macro.md │ ├── 06 │ │ ├── a-case-with-fall-through.md │ │ └── you-have-something-to-hide.md │ ├── 07 │ │ ├── skim-or-sink.md │ │ └── using-travis-ci-for-racket-projects.md │ └── 08 │ │ ├── spoiler-alert-give-google-power-of-attorney.md │ │ ├── understanding-and-using-c-pointers.md │ │ └── using-call-input-url.md ├── 2014 │ ├── 10 │ │ ├── applicable-symbols.md │ │ ├── hacker-school-day-15.md │ │ ├── hands-on-with-clojure-day-2.md │ │ ├── hands-on-with-clojure-day-3.md │ │ ├── hands-on-with-clojure-day-4.md │ │ ├── hands-on-with-clojure-day-5.md │ │ ├── hands-on-with-clojure.md │ │ └── why-macros.md │ ├── 11 │ │ ├── github-dropped-pygments.md │ │ ├── hacker-school-week-6.md │ │ ├── hands-on-with-haskell.md │ │ └── racket-workflow.md │ ├── 12 │ │ └── blogging-catch-up.md │ ├── 01 │ │ ├── syntax-loc-and-unit-tests.md │ │ └── using-syntax-loc.md │ ├── 06 │ │ ├── destructuring-lists-with-match.md │ │ ├── does-your-racket-project-need-a-makefile.md │ │ ├── fallback-when-required-function-not-available.md │ │ ├── file-and-line-in-racket.md │ │ └── racket-cookbook.md │ └── 09 │ │ └── written-in-racket.md ├── 2015 │ ├── 07 │ │ └── keyword-structs-revisited.md │ └── 08 │ │ └── at-expressions.md ├── 2017 │ ├── 02 │ │ └── emacs-themes.md │ ├── 03 │ │ └── please-scroll.md │ └── 04 │ │ └── racket-makefiles.md ├── 2018 │ ├── 10 │ │ └── racket-mode.md │ ├── 11 │ │ └── thread-names.md │ └── 05 │ │ ├── extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js.md │ │ └── extramaze-llc-using-system-fonts-not-google-fonts.md ├── 2019 │ ├── 04 │ │ ├── exploding-frog.md │ │ └── supporting-multi-in.md │ ├── 06 │ │ └── linux-laptop.md │ └── 07 │ │ └── future-of-racket.md └── 2020 │ └── 02 │ ├── the-big-switcheroo.md │ └── using-drracket-check-syntax-in-racket-mode.md └── static ├── .well-known └── keybase.txt ├── CNAME ├── favicon.ico └── img ├── 1x1.gif ├── check-syntax.gif ├── chrome-extensions.png ├── feed.svg ├── google-nexus-4.png ├── grumpy-regexp-parser.png ├── navbar-logo.jpg ├── racket-mode-step-debugger.gif ├── suggested-post.png └── the-fuck-was-that-is-this.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | compiled/ 3 | www 4 | -------------------------------------------------------------------------------- /LEGAL: -------------------------------------------------------------------------------- 1 | I am showing work-in-progress. 2 | 3 | As a result, this repo currently includes a mix of material, ranging 4 | from blog post prose to programming source code. 5 | 6 | 1. A few programming source files -- for example Makefile and rkt/*rkt 7 | -- include an Apache 2.0 license statement in the file. 8 | 9 | 2. All remaining files -- including but not limited to src/* -- are 10 | provided with NO LICENSE. Every such file is: 11 | 12 | Copyright (c) 2011-2019 by Greg Hendershott. All rights reserved. 13 | 14 | In short: Unless a file contains an explicit Apache 2.0 license 15 | statement, no license or rights are granted. 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2019-2021 by Greg Hendershott. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | RACKET ?= racket 16 | RACO ?= $(RACKET) -l raco 17 | 18 | # Configure where are sources, build cache, and root of the web site 19 | # output. 20 | src := src 21 | cache := .cache 22 | www := www 23 | 24 | # Make normally "pulls" targets from sources, but we want to "push" 25 | # sources to targets. As a result, we need to build lists of sources 26 | # and from those build lists of targets. 27 | 28 | post-sources := $(shell find $(src)/posts -type f) 29 | post-caches := $(patsubst $(src)/posts/%.md,$(cache)/%.rktd,$(post-sources)) 30 | post-htmls := $(patsubst $(cache)/%.rktd,$(www)/%.html,$(post-caches)) 31 | 32 | tag-caches := $(wildcard $(cache)/tags/*) 33 | tag-htmls := $(patsubst %,$(www)/tags/%.html,$(notdir $(tag-caches))) 34 | tag-atom-feeds := $(patsubst %,$(www)/feeds/%.atom.xml,$(notdir $(tag-caches))) 35 | tag-rss-feeds := $(patsubst %,$(www)/feeds/%.rss.xml,$(notdir $(tag-caches))) 36 | 37 | non-post-sources := $(wildcard $(src)/non-posts/*.md) 38 | non-post-htmls := $(patsubst %.md,$(www)/%.html,$(notdir $(non-post-sources))) 39 | 40 | # Racket commands 41 | # 42 | # Note: For now these are in rkt subdir. Someday move the generic 43 | # pieces to a "tadpole" package? 44 | make-post-cache := $(RACKET) rkt/make-post-cache.rkt 45 | make-post-html := $(RACKET) rkt/make-post-html.rkt 46 | make-non-post-html := $(RACKET) rkt/make-non-post-html.rkt 47 | make-tag-index := $(RACKET) rkt/make-tag-index.rkt 48 | make-tag-list := $(RACKET) rkt/make-tag-list.rkt 49 | make-tag-feed := $(RACKET) rkt/make-tag-feed.rkt 50 | make-sitemap := $(RACKET) rkt/make-sitemap.rkt 51 | make-css := $(RACKET) rkt/make-css.rkt 52 | new-post := $(RACKET) rkt/make-new-post.rkt 53 | preview := $(RACKET) rkt/make-preview.rkt 54 | 55 | .PHONY: rkt 56 | rkt: 57 | (cd rkt; $(RACO) make *.rkt; $(RACO) test -x .) 58 | 59 | ###################################################################### 60 | 61 | .PHONY: all clean new serve preview 62 | 63 | all: 64 | make cache 65 | make www 66 | 67 | clean: clean-cache clean-www 68 | 69 | new: 70 | $(new-post) $(src)/posts 71 | 72 | serve: $(www)/index.html all 73 | $(preview) $< 74 | 75 | preview: $(www)/index.html all 76 | $(preview) $< "browser" 77 | 78 | ###################################################################### 79 | # Stage 1 80 | 81 | .PHONY: cache clean-cache 82 | 83 | cache: $(post-caches) 84 | 85 | clean-cache: 86 | -rm -rf $(cache) 87 | 88 | $(cache)/%.rktd: $(src)/posts/%.md 89 | $(make-post-cache) $< $(abspath $(cache)/tags) $@ 90 | 91 | ###################################################################### 92 | # Stage 2 93 | 94 | .PHONY: www clean-www 95 | 96 | www: htmls feeds static sitemap 97 | 98 | clean-www: clean-htmls clean-feeds clean-sitemap 99 | 100 | # HTMLs 101 | 102 | .PHONY: hmtls clean-htmls 103 | 104 | htmls: $(post-htmls) $(tag-htmls) $(non-post-htmls) \ 105 | $(www)/tags/index.html $(www)/index.html $(www)/main.css \ 106 | rkt/page-xexpr.rkt rkt/site.rkt 107 | 108 | clean-htmls: 109 | -rm $(post-htmls) 110 | -rm $(tag-htmls) 111 | -rm $(non-post-htmls) 112 | -rm $(www)/index.html 113 | -rm $(www)/tags/index.html 114 | -rmdir $(www)/tags 115 | -rm $(www)/main.css 116 | 117 | $(www)/%.html: $(cache)/%.rktd rkt/page-xexpr.rkt 118 | $(make-post-html) $< $(www) $@ 119 | 120 | $(www)/%.html: $(src)/non-posts/%.md rkt/make-non-post-html.rkt 121 | $(make-non-post-html) $< $(www) $@ 122 | 123 | $(www)/tags/%.html: $(cache)/tags/% rkt/page-xexpr.rkt 124 | $(make-tag-index) $< $(www) $@ 125 | 126 | $(www)/tags/index.html: $(tag-caches) rkt/page-xexpr.rkt rkt/make-tag-list.rkt 127 | $(make-tag-list) $(cache)/tags/ $(www) $@ 128 | 129 | $(www)/index.html: $(www)/tags/all.html rkt/page-xexpr.rkt rkt/make-tag-feed.rkt 130 | cp $< $@ 131 | 132 | $(www)/main.css: rkt/make-css.rkt 133 | $(make-css) $@ 134 | 135 | # Feeds 136 | 137 | .PHONY: feeds clean-feeds 138 | 139 | feeds: $(tag-atom-feeds) $(tag-rss-feeds) 140 | 141 | clean-feeds: 142 | -rm $(www)/feeds/*.xml 143 | -rmdir $(www)/feeds 144 | 145 | $(www)/feeds/%.atom.xml: $(cache)/tags/% 146 | $(make-tag-feed) $< atom $(www) $@ 147 | 148 | $(www)/feeds/%.rss.xml: $(cache)/tags/% 149 | $(make-tag-feed) $< rss $(www) $@ 150 | 151 | # Static assets 152 | 153 | .PHONY: static 154 | 155 | # Note the $(src)/static/. to copy dotfiles, too! 156 | static: 157 | cp -pr $(src)/static/. $(www)/ 158 | 159 | # Sitemap 160 | 161 | .PHONY: sitemap clean-sitemap 162 | 163 | sitemap: 164 | $(make-sitemap) $(www)/sitemap.txt 165 | 166 | clean-sitemap: 167 | rm $(www)/sitemap.txt 168 | 169 | 170 | ###################################################################### 171 | # GitHub Pages deploy 172 | 173 | # Just set $(www) to /home/greg/src/greghendershott.github.com to 174 | # build directly in that dir. Then commit and push in that repo. 175 | 176 | ###################################################################### 177 | # S3 bucket deploy 178 | 179 | aws := aws --profile greg 180 | dest := s3://www.greghendershott.com 181 | cfid := E2LPR1YW069SHG 182 | 183 | .PHONY: deploy 184 | 185 | deploy: 186 | $(aws) s3 sync --no-follow-symlinks $(www) $(dest) 187 | $(aws) cloudfront create-invalidation --distribution-id $(cfid) --paths "/*" > /dev/null 188 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | This is the source for my blog. 2 | 3 | Although I developed -- and still intend to maintain -- [[https://github.com/greghendershott/frog][Frog]], I wanted to experiment with a different approach: Explode it into little pieces driven by a ~Makefile~. 4 | 5 | My [[https://en.wikipedia.org/wiki/Greenspun's_tenth_rule][Greenspun's Tenth Rule]] riff: "Any sufficiently complicated static blog generator contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of ~make~." I can say that without being an asshole because I made one. 6 | 7 | Premise: If you're a programmer, your blog generator need not have configuration, customization, or templates. If you want it to work differently, you can change the code. 8 | 9 | * General 10 | The heart of this is the ~Makefile~. 11 | 12 | As with Frog, this uses two-stage, "compile and link" metaphor. 13 | 14 | ** "Compile" 15 | - Blog post source files ~$(src)/posts/*.md~ are "compiled" into ~$(cache)/*.rktd~ files. These files have some meta-data plus an ~xexpr~ representing the body of the post (e.g. the ~
~ element that will go in some containing page). 16 | - Each post tag appends the ~.rktd~ pathname to a ~$(cache)/tags/{tag-name}~. 17 | ** "Link" 18 | - Use ~$(cache)/*.rktd~ post bodies to generate complete ~.html~ post pages. 19 | - Use ~$(cache)/tags/{tag-name}~ to generate feeds and index page for each tag. 20 | - Generate ~$(src)/non-posts/*.md~. 21 | - Generate ~sitemap.txt~. 22 | - ~cp~ miscellaneous "assets" from ~$(src)/static/*~. 23 | ** Advantages 24 | - Changes to the overall page (e.g. updating a copyright in the footer) can be done without rebuilding the posts from scratch. This particularly helps when the posts are enhanced with things like syntax highlighting and automatic links to Racket docs, which take a bit longer to do. 25 | - As already mentioned, this allows a tags "database" to be made at the same time. 26 | 27 | * TO-DO 28 | 29 | ** TODO Maybe stop depending on frog/{enhance-body paths} 30 | - Split out from frog to new pkgs? 31 | - Also, the pygmentize stuff for frog spawns a long-running python process, which makes frog faster, but won't help us (in fact it may hurt a little). 32 | 33 | ** TODO Maybe create new example repo named "tadpole" 34 | - With whatever is left after doing the previous item. 35 | - Replace my real ~src~ with some example content (e.g. from Frog). 36 | - Share as a repo (not a package) in case anyone else wants to fork it and do what they want. 37 | -------------------------------------------------------------------------------- /rkt/.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil 2 | (indent-tabs-mode . nil) 3 | (require-final-newline . t) 4 | (show-trailing-whitespace . t)) 5 | (prog-mode 6 | (comment-column . 40) 7 | (fill-column . 70)) 8 | (makefile-mode 9 | (indent-tabs-mode . t)) 10 | (racket-mode 11 | ;; Better indentation for quoted xexprs and for at-exprs: 12 | (racket-indent-sequence-depth . 20) 13 | (racket-indent-curly-as-sequence . t))) 14 | -------------------------------------------------------------------------------- /rkt/make-new-post.rkt: -------------------------------------------------------------------------------- 1 | #lang at-exp racket/base 2 | 3 | ;; Copyright 2019 by Greg Hendershott. 4 | ;; 5 | ;; Licensed under the Apache License, Version 2.0 (the "License"); 6 | ;; you may not use this file except in compliance with the License. 7 | ;; You may obtain a copy of the License at 8 | ;; 9 | ;; http://www.apache.org/licenses/LICENSE-2.0 10 | ;; 11 | ;; Unless required by applicable law or agreed to in writing, software 12 | ;; distributed under the License is distributed on an "AS IS" BASIS, 13 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ;; See the License for the specific language governing permissions and 15 | ;; limitations under the License. 16 | 17 | (require racket/require 18 | (multi-in racket (date file format function match string)) 19 | (only-in frog/paths slug) 20 | "util.rkt") 21 | 22 | (module+ main (main)) 23 | 24 | (define (main) 25 | (match (current-command-line-arguments) 26 | [(vector posts-dir) 27 | (display "Title: ") 28 | (define title (read-line)) 29 | 30 | (match-define (struct* date ([year y] [month m] [day d])) (current-date)) 31 | (define year (~a y)) 32 | (define (~00 n) (~r n #:min-width 2 #:pad-string "0")) 33 | (define month (~00 m)) 34 | (define day (~00 d)) 35 | (define 8601-str (~a year "-" month "-" day "T00:00:00Z")) 36 | 37 | (define post-file (build-path posts-dir year month 38 | (~a (slug (string-downcase title)) ".md"))) 39 | (make-parent-directory* post-file) 40 | (call-with-output-file*/delete 41 | #:exists 'error post-file 42 | (λ (out) 43 | (displayln 44 | @~a{ Title: @title 45 | Date: @8601-str 46 | Tags: 47 | 48 | This is part of the blurb. 49 | 50 | 51 | 52 | This is the rest. 53 | } 54 | out))) 55 | (displayln @~a{Created @(path->complete-path post-file)})])) 56 | 57 | -------------------------------------------------------------------------------- /rkt/make-non-post-html.rkt: -------------------------------------------------------------------------------- 1 | #lang at-exp racket/base 2 | 3 | ;; Copyright 2019 by Greg Hendershott. 4 | ;; 5 | ;; Licensed under the Apache License, Version 2.0 (the "License"); 6 | ;; you may not use this file except in compliance with the License. 7 | ;; You may obtain a copy of the License at 8 | ;; 9 | ;; http://www.apache.org/licenses/LICENSE-2.0 10 | ;; 11 | ;; Unless required by applicable law or agreed to in writing, software 12 | ;; distributed under the License is distributed on an "AS IS" BASIS, 13 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ;; See the License for the specific language governing permissions and 15 | ;; limitations under the License. 16 | 17 | (require racket/require 18 | (multi-in racket (contract file format list match path port string)) 19 | (only-in markdown parse-markdown) 20 | threading 21 | "page-xexpr.rkt" 22 | "util.rkt" 23 | "xexpr.rkt") 24 | 25 | ;; Don't bother caching these. Directly from .md to .html. 26 | 27 | (module+ main (main)) 28 | 29 | (define (main) 30 | (match (current-command-line-arguments) 31 | [(vector non-post-source www output-html) 32 | (make-parent-directory* output-html) 33 | (call-with-output-file*/delete 34 | #:exists 'replace output-html 35 | (λ (out) 36 | (display "" out) 37 | (display (xexpr->string 38 | (non-post-xexpr (build-path non-post-source) 39 | (file->uri www output-html))) 40 | out)))])) 41 | 42 | (define (non-post-xexpr source-path page-path) 43 | (define contents (parse-markdown source-path)) 44 | (page-xexpr #:title (title contents source-path) 45 | #:keywords "" 46 | #:description "" 47 | #:page-path page-path 48 | #:atom-path "feeds/all.atom.xml" 49 | #:rss-path "feeds/all.rss.xml" 50 | #:contents contents)) 51 | 52 | (define (title xs path) 53 | (or (for/or ([x (in-list xs)]) 54 | (match x 55 | ;; First h1 header, if any 56 | [`(h1 (,_ ...) . ,els) 57 | (string-join (map xexpr->markdown els) "")] 58 | [_ #f])) 59 | ;; Else name of the source file 60 | (path->string 61 | (file-name-from-path 62 | (path-replace-extension path #""))))) 63 | -------------------------------------------------------------------------------- /rkt/make-post-html.rkt: -------------------------------------------------------------------------------- 1 | #lang at-exp racket/base 2 | 3 | ;; Copyright 2019 by Greg Hendershott. 4 | ;; 5 | ;; Licensed under the Apache License, Version 2.0 (the "License"); 6 | ;; you may not use this file except in compliance with the License. 7 | ;; You may obtain a copy of the License at 8 | ;; 9 | ;; http://www.apache.org/licenses/LICENSE-2.0 10 | ;; 11 | ;; Unless required by applicable law or agreed to in writing, software 12 | ;; distributed under the License is distributed on an "AS IS" BASIS, 13 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ;; See the License for the specific language governing permissions and 15 | ;; limitations under the License. 16 | 17 | (require racket/require 18 | (multi-in racket (contract file format list match path port string)) 19 | threading 20 | "post.rkt" 21 | "page-xexpr.rkt" 22 | "util.rkt" 23 | "xexpr.rkt") 24 | 25 | (module+ main (main)) 26 | 27 | (define (main) 28 | (match (current-command-line-arguments) 29 | [(vector rktd www output-html) 30 | (define the-post (call-with-input-file* rktd read)) 31 | (make-parent-directory* output-html) 32 | (call-with-output-file*/delete 33 | #:exists 'replace output-html 34 | (λ (out) 35 | (display "" out) 36 | (display (xexpr->string 37 | (post-xexpr the-post (file->uri www output-html))) 38 | out)))])) 39 | 40 | (define (post-xexpr the-post page-path) 41 | (match-define (post title date tags blurb more? body) the-post) 42 | (page-xexpr #:title title 43 | #:keywords (string-join tags) 44 | #:description (xexprs->description blurb) 45 | #:page-path page-path 46 | #:atom-path "feeds/all.atom.xml" 47 | #:rss-path "feeds/all.rss.xml" 48 | #:contents body)) 49 | -------------------------------------------------------------------------------- /rkt/make-preview.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ;; Copyright 2019 by Greg Hendershott. 4 | ;; 5 | ;; Licensed under the Apache License, Version 2.0 (the "License"); 6 | ;; you may not use this file except in compliance with the License. 7 | ;; You may obtain a copy of the License at 8 | ;; 9 | ;; http://www.apache.org/licenses/LICENSE-2.0 10 | ;; 11 | ;; Unless required by applicable law or agreed to in writing, software 12 | ;; distributed under the License is distributed on an "AS IS" BASIS, 13 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ;; See the License for the specific language governing permissions and 15 | ;; limitations under the License. 16 | 17 | (require racket/require 18 | (multi-in racket (match path)) 19 | (multi-in web-server (dispatchers/dispatch servlet-env))) 20 | 21 | (module+ main (main)) 22 | 23 | (define (main) 24 | (match (current-command-line-arguments) 25 | [(vector index.html) (preview index.html #:launch-browser? #f)] 26 | [(vector index.html "browser") (preview index.html #:launch-browser? #t)])) 27 | 28 | (define (preview index.html #:launch-browser? launch-browser?) 29 | (serve/servlet (lambda (_) (next-dispatcher)) 30 | #:servlet-path "/index.html" 31 | #:extra-files-paths (list (or (path-only index.html) 32 | (current-directory))) 33 | #:listen-ip "127.0.0.1" 34 | #:port 3000 35 | #:launch-browser? launch-browser?)) 36 | -------------------------------------------------------------------------------- /rkt/make-sitemap.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ;; Copyright 2019 by Greg Hendershott. 4 | ;; 5 | ;; Licensed under the Apache License, Version 2.0 (the "License"); 6 | ;; you may not use this file except in compliance with the License. 7 | ;; You may obtain a copy of the License at 8 | ;; 9 | ;; http://www.apache.org/licenses/LICENSE-2.0 10 | ;; 11 | ;; Unless required by applicable law or agreed to in writing, software 12 | ;; distributed under the License is distributed on an "AS IS" BASIS, 13 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ;; See the License for the specific language governing permissions and 15 | ;; limitations under the License. 16 | 17 | (require racket/require 18 | (multi-in racket (contract file format match path string)) 19 | "site.rkt" 20 | "tag-posts.rkt" 21 | "util.rkt") 22 | 23 | (module+ main (main)) 24 | 25 | (define (main) 26 | (match (current-command-line-arguments) 27 | [(vector sitemap.txt) (write-sitemap sitemap.txt)])) 28 | 29 | (define (write-sitemap sitemap.txt) 30 | (call-with-output-file*/delete 31 | #:exists 'replace sitemap.txt 32 | (λ (out) 33 | (for ([path (in-list (directory-list (path-only sitemap.txt)))] 34 | #:when (and (equal? #".html" (path-get-extension path)) 35 | (not (directory-exists? path)))) 36 | (displayln (full-uri (path->string path)) 37 | out))))) 38 | -------------------------------------------------------------------------------- /rkt/make-tag-index.rkt: -------------------------------------------------------------------------------- 1 | #lang at-exp racket/base 2 | 3 | ;; Copyright 2019 by Greg Hendershott. 4 | ;; 5 | ;; Licensed under the Apache License, Version 2.0 (the "License"); 6 | ;; you may not use this file except in compliance with the License. 7 | ;; You may obtain a copy of the License at 8 | ;; 9 | ;; http://www.apache.org/licenses/LICENSE-2.0 10 | ;; 11 | ;; Unless required by applicable law or agreed to in writing, software 12 | ;; distributed under the License is distributed on an "AS IS" BASIS, 13 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ;; See the License for the specific language governing permissions and 15 | ;; limitations under the License. 16 | 17 | (require racket/require 18 | (multi-in racket (contract file format match path)) 19 | (only-in "make-post-cache.rkt" datetime+tags->xexpr) 20 | "post.rkt" 21 | "page-xexpr.rkt" 22 | "tag-posts.rkt" 23 | "util.rkt" 24 | "xexpr.rkt") 25 | 26 | (module+ main (main)) 27 | 28 | (define abbreviate-after 10) 29 | 30 | (define (main) 31 | (match (current-command-line-arguments) 32 | [(vector tag-file www output-html) 33 | (define tag (path->string (file-name-from-path tag-file))) 34 | (define-values (newer-posts older-posts) 35 | (split-at* (tag-file->sorted-posts tag-file) 36 | abbreviate-after)) 37 | (make-directory* (path-only output-html)) 38 | (call-with-output-file*/delete 39 | #:exists 'replace output-html 40 | (λ (out) 41 | (display "" out) 42 | (display (xexpr->string 43 | (index-xexpr tag 44 | newer-posts 45 | older-posts 46 | (file->uri www output-html))) 47 | out)))])) 48 | 49 | (define/contract (index-xexpr tag newer-posts older-posts page-path) 50 | (-> string? 51 | (listof (cons/c path-string? post?)) 52 | (listof (cons/c path-string? post?)) 53 | path-string? 54 | xexpr/c) 55 | (define newer-articles 56 | (for/list ([the-post (in-list newer-posts)]) 57 | (match-define (cons rktd (post title datetime tags blurb more? body)) the-post) 58 | (define href (~a "/" (sans-top-dir 59 | (path-replace-extension rktd #".html")))) 60 | `(article ([class "index newer"]) 61 | (header () 62 | (h2 () (a ([href ,href]) ,title)) 63 | ,(datetime+tags->xexpr datetime tags)) 64 | ,@blurb 65 | ,@(if more? 66 | `((footer () (a ([href ,href]) (em () hellip "More" hellip)))) 67 | `())))) 68 | (define older-articles 69 | (for/list ([the-post (in-list older-posts)]) 70 | (match-define (cons rktd (post title datetime tags blurb more? body)) the-post) 71 | (define href (~a "/" (sans-top-dir 72 | (path-replace-extension rktd #".html")))) 73 | `(article ([class "index older"]) 74 | (header () 75 | (h2 () (a ([href ,href]) ,title)) 76 | ,(datetime+tags->xexpr datetime tags))))) 77 | (define articles (append newer-articles older-articles)) 78 | (define-values (page-title contents) 79 | (if (equal? tag "all") 80 | (values "Home: Greg Hendershott" 81 | articles) 82 | (values (~a "Posts tagged \"" tag "\"") 83 | (cons `(h1 () "Posts tagged " (em ,tag)) 84 | articles)))) 85 | (page-xexpr #:title page-title 86 | #:keywords tag 87 | #:description page-title 88 | #:page-path page-path 89 | #:atom-path (~a "feeds/" tag ".atom.xml") 90 | #:rss-path (~a "feeds/" tag ".rss.xml") 91 | #:contents contents)) 92 | -------------------------------------------------------------------------------- /rkt/make-tag-list.rkt: -------------------------------------------------------------------------------- 1 | #lang at-exp racket/base 2 | 3 | ;; Copyright 2019 by Greg Hendershott. 4 | ;; 5 | ;; Licensed under the Apache License, Version 2.0 (the "License"); 6 | ;; you may not use this file except in compliance with the License. 7 | ;; You may obtain a copy of the License at 8 | ;; 9 | ;; http://www.apache.org/licenses/LICENSE-2.0 10 | ;; 11 | ;; Unless required by applicable law or agreed to in writing, software 12 | ;; distributed under the License is distributed on an "AS IS" BASIS, 13 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ;; See the License for the specific language governing permissions and 15 | ;; limitations under the License. 16 | 17 | (require racket/require 18 | (multi-in racket (contract file format match path string)) 19 | (only-in "make-post-cache.rkt" datetime+tags->xexpr) 20 | "post.rkt" 21 | "page-xexpr.rkt" 22 | "util.rkt" 23 | "xexpr.rkt") 24 | 25 | (module+ main (main)) 26 | 27 | (define (main) 28 | (match (current-command-line-arguments) 29 | [(vector tag-caches-path www output-html) 30 | (define tags (sort (for*/list ([path (in-directory tag-caches-path)] 31 | [name (in-value (file-name-from-path path))] 32 | [str (in-value (path->string name))] 33 | #:when (not (equal? str "all"))) 34 | str) 35 | string-ci" out) 41 | (display (xexpr->string 42 | (tags-xexpr tags (file->uri www output-html))) 43 | out)))])) 44 | 45 | (define/contract (tags-xexpr tags page-path) 46 | (-> (listof string?) path-string? xexpr/c) 47 | (define links 48 | (for/list ([tag (in-list tags)]) 49 | `(li (a ([href ,(~a "/tags/" tag ".html")]) ,tag)))) 50 | (define contents 51 | `((ul ,@links))) 52 | (page-xexpr #:title "Tags" 53 | #:keywords (string-join tags ",") 54 | #:description "Tags" 55 | #:page-path page-path 56 | #:atom-path (~a "feeds/all.atom.xml") 57 | #:rss-path (~a "feeds/all.rss.xml") 58 | #:contents contents)) 59 | -------------------------------------------------------------------------------- /rkt/page-xexpr.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ;; Copyright 2019 by Greg Hendershott. 4 | ;; 5 | ;; Licensed under the Apache License, Version 2.0 (the "License"); 6 | ;; you may not use this file except in compliance with the License. 7 | ;; You may obtain a copy of the License at 8 | ;; 9 | ;; http://www.apache.org/licenses/LICENSE-2.0 10 | ;; 11 | ;; Unless required by applicable law or agreed to in writing, software 12 | ;; distributed under the License is distributed on an "AS IS" BASIS, 13 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ;; See the License for the specific language governing permissions and 15 | ;; limitations under the License. 16 | 17 | (require racket/require 18 | (multi-in racket (contract date format)) 19 | "site.rkt" 20 | "xexpr.rkt") 21 | 22 | (provide page-xexpr) 23 | 24 | (define/contract (page-xexpr #:title title 25 | #:description description 26 | #:keywords keywords 27 | #:page-path page-path 28 | #:atom-path atom-path 29 | #:rss-path rss-path 30 | #:contents contents 31 | #:heads [heads '()]) 32 | (->* (#:title string? 33 | #:description string? 34 | #:keywords string? 35 | #:page-path string? 36 | #:atom-path string? 37 | #:rss-path string? 38 | #:contents (listof xexpr/c)) 39 | (#:heads (listof xexpr/c)) 40 | xexpr/c) 41 | `(html ([lang "en"]) 42 | (head () 43 | (meta ([charset "utf-8"])) 44 | (title () ,title) 45 | (meta ([name "description"] [content ,description])) 46 | (meta ([name "author"] [content "Greg Hendershott"])) 47 | (meta ([name "keywords"] [content ,keywords])) 48 | (meta ([name "viewport"] [content "width=device-width, initial-scale=1.0"])) 49 | (link ([rel "icon"] [href "/favicon.ico"])) 50 | (link ([rel "canonical"] [href ,(full-uri page-path)])) 51 | (link ([rel "stylesheet"] [type "text/css"] [href "/main.css"])) 52 | (link ([rel "alternate"] 53 | [type "application/atom+xml"] 54 | [title "Atom Feed"] 55 | [href ,(full-uri atom-path)])) 56 | (link ([rel "alternate"] 57 | [type "application/rss+xml"] 58 | [title "RSS Feed"] 59 | [href ,(full-uri rss-path)])) 60 | (link ([rel "me"] 61 | [href "https://mastodon.social/@greghendershott"])) 62 | ,@heads) 63 | 64 | (body () 65 | (header ([class "site"]) 66 | (nav 67 | (ul 68 | (li (a ([href "/"]) "Greg Hendershott")) 69 | (li (a ([href "/tags/index.html"]) "Tags")) 70 | (li (a ([href "/About.html"]) "About")) 71 | (li (a ([href ,(~a "/" atom-path)]) 72 | (img ([src "/img/feed.svg"])) nbsp "Atom")) 73 | (li (a ([href ,(~a "/" rss-path)]) 74 | (img ([src "/img/feed.svg"])) nbsp "RSS"))))) 75 | 76 | (main ([class "site"]) ,@contents) 77 | 78 | (footer ([class "site"]) 79 | (p () 80 | "Copyright " copy " 2012" ndash ,(~a (date-year (current-date))) 81 | " by Greg Hendershott. All rights reserved.") 82 | (p () 83 | "Created using a Makefile, Racket, and 'tadpole'. " 84 | (a ([rel "me"] 85 | [href "https://mastodon.social/@greghendershott"]) 86 | "Mastodon")))))) 87 | -------------------------------------------------------------------------------- /rkt/post.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ;; Copyright 2019 by Greg Hendershott. 4 | ;; 5 | ;; Licensed under the Apache License, Version 2.0 (the "License"); 6 | ;; you may not use this file except in compliance with the License. 7 | ;; You may obtain a copy of the License at 8 | ;; 9 | ;; http://www.apache.org/licenses/LICENSE-2.0 10 | ;; 11 | ;; Unless required by applicable law or agreed to in writing, software 12 | ;; distributed under the License is distributed on an "AS IS" BASIS, 13 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ;; See the License for the specific language governing permissions and 15 | ;; limitations under the License. 16 | 17 | (provide (struct-out post)) 18 | 19 | (struct post 20 | (title ;string? 21 | datetime ;string? - 8601 datetime format 22 | tags ;(listof string?) 23 | blurb ;(listof xexpr/c) - the post summary 24 | more? ;boolean? - is `body` more than just `blurb`? 25 | body ;(listof xexpr/c) - the post full contents 26 | ) #:prefab) 27 | -------------------------------------------------------------------------------- /rkt/site.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ;; Copyright 2019 by Greg Hendershott. 4 | ;; 5 | ;; Licensed under the Apache License, Version 2.0 (the "License"); 6 | ;; you may not use this file except in compliance with the License. 7 | ;; You may obtain a copy of the License at 8 | ;; 9 | ;; http://www.apache.org/licenses/LICENSE-2.0 10 | ;; 11 | ;; Unless required by applicable law or agreed to in writing, software 12 | ;; distributed under the License is distributed on an "AS IS" BASIS, 13 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ;; See the License for the specific language governing permissions and 15 | ;; limitations under the License. 16 | 17 | (require racket/contract 18 | racket/format 19 | net/uri-codec) 20 | 21 | (provide author 22 | scheme 23 | host 24 | full-uri 25 | urn) 26 | 27 | (define (author) "Greg Hendershott") 28 | (define (scheme) "https") 29 | (define (host) "www.greghendershott.com") 30 | (define (host/urn) "urn:www-greghendershott-com") 31 | 32 | (define/contract (full-uri uri-path) 33 | (-> (and/c path-string? relative-path?) string?) 34 | (~a (scheme) "://" (host) "/" uri-path)) 35 | 36 | (define (urn uri-path) 37 | ;; Note that URNs have a more restricted syntax than URIs. 38 | (~a (host/urn) ":" (regexp-replace* #px"/" uri-path "-"))) 39 | -------------------------------------------------------------------------------- /rkt/tag-posts.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ;; Copyright 2019 by Greg Hendershott. 4 | ;; 5 | ;; Licensed under the Apache License, Version 2.0 (the "License"); 6 | ;; you may not use this file except in compliance with the License. 7 | ;; You may obtain a copy of the License at 8 | ;; 9 | ;; http://www.apache.org/licenses/LICENSE-2.0 10 | ;; 11 | ;; Unless required by applicable law or agreed to in writing, software 12 | ;; distributed under the License is distributed on an "AS IS" BASIS, 13 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ;; See the License for the specific language governing permissions and 15 | ;; limitations under the License. 16 | 17 | (require racket/require 18 | (multi-in racket (contract file format list match path string)) 19 | "post.rkt") 20 | 21 | (provide tag-file->sorted-posts) 22 | 23 | (define (tag-file->sorted-posts tag-file) 24 | (define rktds (remove-duplicates ;sucessive makes might append again 25 | (file->lines tag-file))) 26 | (define rktds+posts (for/list ([rktd (in-list rktds)]) 27 | (cons rktd (call-with-input-file* rktd read)))) 28 | (sort rktds+posts 29 | #:key (compose post-datetime cdr) 30 | string>?)) 31 | -------------------------------------------------------------------------------- /rkt/util.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ;; Copyright 2019 by Greg Hendershott. 4 | ;; 5 | ;; Licensed under the Apache License, Version 2.0 (the "License"); 6 | ;; you may not use this file except in compliance with the License. 7 | ;; You may obtain a copy of the License at 8 | ;; 9 | ;; http://www.apache.org/licenses/LICENSE-2.0 10 | ;; 11 | ;; Unless required by applicable law or agreed to in writing, software 12 | ;; distributed under the License is distributed on an "AS IS" BASIS, 13 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ;; See the License for the specific language governing permissions and 15 | ;; limitations under the License. 16 | 17 | (require racket/require 18 | (multi-in racket (contract format path))) 19 | 20 | (provide call-with-output-file*/delete 21 | sans-top-dir 22 | file->uri 23 | split-at* 24 | split-at*/list) 25 | 26 | (module+ test 27 | (require rackunit)) 28 | 29 | (define (delete-file* path) 30 | (when (file-exists? path) 31 | (delete-file path))) 32 | 33 | (define ((raise-after-deleting path) e) 34 | (delete-file* path) 35 | (raise e)) 36 | 37 | (define (call-with-output-file*/delete path 38 | proc 39 | #:mode [mode 'binary] 40 | #:exists [exists 'error]) 41 | (with-handlers ([exn? (raise-after-deleting path)]) 42 | (call-with-output-file* 43 | path 44 | #:mode mode 45 | #:exists exists 46 | proc))) 47 | 48 | ;; e.g. ".cache/something" => "something" 49 | (define/contract (sans-top-dir p) 50 | (-> (and/c path-string? relative-path?) 51 | (and/c path-string? relative-path? string?)) 52 | (path->string (apply build-path (cdr (explode-path p))))) 53 | 54 | (module+ test 55 | (check-equal? (sans-top-dir ".cache/path/to/foo.html") 56 | "path/to/foo.html") 57 | (check-equal? (sans-top-dir (build-path ".cache" "path" "to" "foo.html")) 58 | "path/to/foo.html")) 59 | 60 | (define/contract (file->uri www-root file-path) 61 | (-> path-string? path-string? 62 | (and/c path-string? relative-path? string?)) 63 | (path->string (find-relative-path www-root file-path))) 64 | 65 | (module+ test 66 | ;; Where relative 67 | (check-equal? (file->uri "www" 68 | "www/path/to/foo.html") 69 | "path/to/foo.html") 70 | (check-equal? (file->uri (build-path "www") 71 | (build-path "www" "path" "to" "foo.html")) 72 | "path/to/foo.html") 73 | ;; Where absolute 74 | (check-equal? (file->uri "/home/greg/src/www" 75 | "/home/greg/src/www/path/to/foo.html") 76 | "path/to/foo.html") 77 | (check-equal? (file->uri (build-path "/home" "greg" "src" "www") 78 | (build-path "/home" "greg" "src" "www" "path" "to" "foo.html")) 79 | "path/to/foo.html")) 80 | 81 | ;; Like racket/list split-at, but when n is greater than length of xs, 82 | ;; returns empty list(s) instead of raising exn. 83 | (define (split-at* xs n) 84 | (let loop ([xs xs] [n n] [pfx '()]) 85 | (cond [(zero? n) (values (reverse pfx) xs)] 86 | [(pair? xs) (loop (cdr xs) (sub1 n) (cons (car xs) pfx))] 87 | [else (values (reverse pfx) xs)]))) 88 | 89 | (define (split-at*/list xs n) 90 | (call-with-values (λ () (split-at* xs n)) list)) 91 | 92 | (module+ test 93 | (check-equal? (split-at*/list (list) 0) 94 | (list (list) (list))) 95 | (check-equal? (split-at*/list (list 0 1 2 3 4) 2) 96 | (list (list 0 1) (list 2 3 4))) 97 | (check-equal? (split-at*/list (list 0 1 2 3 4) 10) 98 | (list (list 0 1 2 3 4) (list)))) 99 | -------------------------------------------------------------------------------- /rkt/xexpr.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ;; Copyright 2019 by Greg Hendershott. 4 | ;; 5 | ;; Licensed under the Apache License, Version 2.0 (the "License"); 6 | ;; you may not use this file except in compliance with the License. 7 | ;; You may obtain a copy of the License at 8 | ;; 9 | ;; http://www.apache.org/licenses/LICENSE-2.0 10 | ;; 11 | ;; Unless required by applicable law or agreed to in writing, software 12 | ;; distributed under the License is distributed on an "AS IS" BASIS, 13 | ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ;; See the License for the specific language governing permissions and 15 | ;; limitations under the License. 16 | 17 | (require racket/require 18 | (multi-in racket (contract format function match string)) 19 | threading 20 | (only-in markdown xexpr->string) ;not the one from xml 21 | (only-in xml xexpr/c valid-char?)) 22 | 23 | (provide xexpr/c 24 | xexpr->string 25 | xexprs->description 26 | xexpr->markdown) 27 | 28 | (module+ test 29 | (require rackunit)) 30 | 31 | (define/contract (xexprs->description xs [max-len 255]) 32 | (->* ((listof xexpr/c)) (exact-nonnegative-integer?) string?) 33 | (define s (~> (string-join (map (curryr xexpr->markdown " ") xs) "") 34 | kill-newlines)) 35 | (define len (string-length s)) 36 | (define sub (substring s 0 (min max-len len))) 37 | (define esc (escape-double-quotes sub)) 38 | (cond [(< len (string-length sub)) esc] 39 | [else (~a esc "...")])) 40 | 41 | (define (substring* s from upto) 42 | (substring s from (min upto (string-length s)))) 43 | 44 | (module+ test 45 | (check-equal? 46 | (xexprs->description '((h1 ([class "foo"]) "A heading") 47 | (p "A " (em "paragraph") " of some stuff.") 48 | (p "A " (em "paragraph") " of some stuff.")) 49 | 50) 50 | "A heading: A _paragraph_ of some stuff. A _paragra...") 51 | (check-equal? 52 | (xexprs->description '((h1 ([class "foo"]) "A heading") 53 | (img ([src "blah"])) 54 | (p "A " (em "paragraph") " of \"some\" stuff.")) 55 | 50) 56 | "A heading: A _paragraph_ of "some" stuff....")) 57 | 58 | ;; Not full markdown, just a "lite" variant for human readers only. 59 | (define/contract (xexpr->markdown x [block-suffix ""]) 60 | (->* (xexpr/c) (string?) string?) 61 | (define (heading? s) 62 | (memq s '(h1 h2 h3 h4 h5 h6 h7 h8 h9))) 63 | (define (block? s) 64 | (memq s '(p div li))) 65 | (define (->s es) ;convert entities to string 66 | (apply ~a (map (curryr xexpr->markdown block-suffix) es))) 67 | (define (normalize x) ;; ensure xexpr has explicit attributes 68 | (match x 69 | [`(,(? symbol? tag) ([,(? symbol? ks) ,(? string? vs)] ...) . ,es) x] 70 | [`(,(? symbol? tag) . ,es) `(,tag () ,@es)] 71 | [_ x])) 72 | (match (normalize x) 73 | [`(em ,_ . ,es) (~a "_" (->s es) "_")] 74 | [`(strong ,_ . ,es) (~a "**" (->s es) "**")] 75 | [`(code ,_ . ,es) (~a "`" (->s es) "`")] 76 | [`(,(? heading?) ,_ . ,es) (~a (->s es) ": ")] 77 | [`(,(? block?) ,_ . ,es) (~a (->s es) block-suffix)] 78 | [`(,(? symbol?) ,_ . ,es) (~a (->s es))] 79 | [(? string? s) s] 80 | ['ndash "-"] 81 | ['mdash "--"] 82 | ['amp "&"] 83 | [(or 'lsquo 'rsquo) "'"] 84 | [(or 'ldquo 'rdquo 'quot) "\""] 85 | [(? valid-char? i) (string (integer->char i))] 86 | [_ ""])) ;; ignore others 87 | 88 | (module+ test 89 | (check-equal? (xexpr->markdown '(em "italic")) 90 | "_italic_") 91 | (check-equal? (xexpr->markdown '(em ([class "foo"]) "italic")) 92 | "_italic_") 93 | (check-equal? (xexpr->markdown '(strong "bold")) 94 | "**bold**") 95 | (check-equal? (xexpr->markdown '(strong ([class "foo"]) "bold")) 96 | "**bold**") 97 | (check-equal? (xexpr->markdown '(em "italic " (strong "bold") " italic")) 98 | "_italic **bold** italic_") 99 | (check-equal? (xexpr->markdown '(p "I am some " (em "italic") " text")) 100 | "I am some _italic_ text") 101 | (check-equal? (xexpr->markdown '(p ([class "foo"]) 102 | "I am some " (em "italic") " text")) 103 | "I am some _italic_ text") 104 | (check-equal? (xexpr->markdown '(p "M" 'amp "Ms" 'mdash "gotta love 'em")) 105 | "M&Ms--gotta love 'em") 106 | (check-equal? (xexpr->markdown '(div (p "Hi.") (p "Hi.")) "\n") 107 | "Hi.\nHi.\n\n") 108 | (check-equal? (xexpr->markdown '(p "Hi" #x20 "there")) 109 | "Hi there") 110 | (check-equal? (xexpr->markdown `(p () "A " ,(char->integer #\λ) " char")) 111 | "A λ char")) 112 | 113 | (define (escape-double-quotes s) 114 | (regexp-replace* #rx"\"" s "\\"")) ;need to escape `&` in replace str 115 | 116 | (module+ test 117 | (check-equal? (escape-double-quotes "A \"double quote\" in the string.") 118 | "A "double quote" in the string.")) 119 | 120 | (define (kill-newlines s) 121 | (~> (regexp-replace* "\n+" s " ") 122 | (string-trim #:left? #t 123 | #:right? #t 124 | #:repeat? #t))) 125 | 126 | (module+ test 127 | (check-equal? (kill-newlines "\nHi.\n") 128 | "Hi.") 129 | (check-equal? (kill-newlines "\nHi\nthere.\n") 130 | "Hi there.") 131 | (check-equal? (kill-newlines "\nPara\n1.\n\nPara\n2.\n") 132 | "Para 1. Para 2.")) 133 | -------------------------------------------------------------------------------- /src/non-posts/About.md: -------------------------------------------------------------------------------- 1 | # About me 2 | 3 | Greg Hendershott 6 | 7 | My interest in music led me to an interest in computer programming, 8 | which led me to an interest in business. 9 | 10 | I started the music software company Cakewalk in 1987 as a bootstrap. 11 | I grew it out of cash flow until 2003, when Roland Corporation became 12 | an investor. As I approached the 25th anniversary in 2012, 13 | I sold my remaining stake. I wanted to explore new things. 14 | 15 | I spent a few years enjoying hands-on programming again, particularly 16 | open source projects using [Racket]. 17 | 18 | I did a batch at Hacker School (now called [Recurse Center]) where I 19 | worked on projects using Clojure, Haskell, and Python. 20 | 21 | I worked at Droit Financial Technologies where I used Clojure and 22 | Datomic. 23 | 24 | I founded Extramaze LLC. One motivation was to launch a small [search 25 | engine for music gear deals][deals], later closed due to lack of 26 | interest. Another motivation was to provide consulting services, which 27 | are still available! 28 | 29 | [Racket]: https://www.racket-lang.org 30 | [Recurse Center]: https://www.recurse.com 31 | [deals]: /2018/05/extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js.html 32 | 33 | ## Elsewhere 34 | 35 | - [Mastodon](https://mastodon.social/@greghendershott) 36 | - [GitHub](https://github.com/greghendershott) 37 | -------------------------------------------------------------------------------- /src/non-posts/error.md: -------------------------------------------------------------------------------- 1 | # Oops 2 | 3 | There seems to be a problem. 4 | -------------------------------------------------------------------------------- /src/posts/2011/12/domain-registrar-switch.md: -------------------------------------------------------------------------------- 1 | Title: Domain registrar switch 2 | Date: 2011-12-27T18:13:00 3 | Tags: technology 4 | 5 | My domain wasn't working for ~24 hours due to my switching from 6 | GoDaddy to Namecheap. Although it's possible to make that kind of 7 | switch with minimal downtime, in my case: 8 | 9 | 1. It was an impulse decision on my part. (Finally got some time to 10 | reflect on GoDaddy's support for SOPA. As well as the elephant 11 | killing and the tacky "babe marketing". SOPA was strike three.) 12 | 13 | 2. GoDaddy 14 | [seemed to be dragging their heels releasing transfers](http://community.namecheap.com/blog/2011/12/26/godaddy-transfer-update/). 15 | 16 | 3. Namecheap seemed to be struggling with an unexpected number of 17 | transfers, inconveniently over the holiday weekend. (In fairness to 18 | GoDaddy, maybe their delay was at least _partly_ due to the same 19 | reason.) 20 | 21 | Fortunately it's not as if greghendershott.com is a hot destination 22 | for millions of fans. Or dozens. 23 | 24 | Also, I wanted to mention that I did a couple live chats with 25 | Namecheap support and they were very helpful and quick. 26 | 27 | -------------------------------------------------------------------------------- /src/posts/2012/02/google.md: -------------------------------------------------------------------------------- 1 | Title: Google+ 2 | Date: 2012-02-16T19:42:00 3 | Tags: blog 4 | 5 | In case you hadn't noticed, I'm not exactly posting here, much. 6 | 7 | This is not a firehose of information. 8 | 9 | This is not a bountiful font of wisdom from which you can aspire 10 | merely to glean a glimmering glimpse of insight. 11 | 12 | This is not a haystack containing needles such as the secrets to 13 | wealth, popularity, and whiter teeth. 14 | 15 | This blog will not cause Comcast to send you a warning that you are 16 | about to exceed your 250 GB of unlimited data. 17 | 18 | 19 | Why? Simply because (a) I don't have that much to say, and (b) what I 20 | do have to say, I'm saying on Google+. That is my de facto "blog", 21 | for now. 22 | 23 | See you [there](https://plus.google.com/u/0/107023078912536369392/posts). 24 | 25 | -------------------------------------------------------------------------------- /src/posts/2012/08/ancient-history.md: -------------------------------------------------------------------------------- 1 | Title: Ancient history 2 | Date: 2012-08-15T07:00:00 3 | Tags: life, nostalgia 4 | 5 | I played trombone and piano in high school. I played electronic 6 | keyboards (including a 7 | [Moog Prodigy](http://en.wikipedia.org/wiki/Moog_Prodigy), my first 8 | synth) in a couple bands. I wasn't a very good musician but I enjoyed 9 | it. 10 | 11 | I went to [Oberlin College](http://www.oberlin.edu) 1982-1986. I did a 12 | major in philosophy and a minor in religion. Although the Oberlin 13 | Conservatory didn't offer a major in electronic or computer music back 14 | then, I did a major's worth of those courses. I took just two computer 15 | science courses: assembly language and Pascal. That turned out to be 16 | good preparation for teaching myself C later. 17 | 18 | 19 | 20 | In projects for computer music, I loved 21 | [Turbo Pascal](http://en.wikipedia.org/wiki/Turbo_Pascal). It's 22 | remarkable that, all these years and MHz and GB later, virtually no 23 | programming environment is faster. 24 | 25 | ![Compaq Portable](http://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Compaq_portable.jpg/250px-Compaq_portable.jpg "Compaq Portable"). 26 | 27 | I used Turbo Pascal first on the school's 28 | [Osborne](http://en.wikipedia.org/wiki/Osborne_Computer_Corporation) 29 | [CP/M](http://en.wikipedia.org/wiki/CP/M) PCs, and later on my own 30 | [Compaq Portable](http://en.wikipedia.org/wiki/Compaq_portable) with 31 | MS-DOS and a Plus Development 32 | [Hard Card](http://en.wikipedia.org/wiki/Hardcard). The latter let you 33 | add a real hard disk drive, with a whopping 10 MB of capacity. (That's 34 | not a typo: megabytes, not gigabytes.) 35 | 36 | I graduated in 1986 and moved to Boston because some friends from 37 | Oberlin were headed back there. I procrastinated getting a real job, 38 | doing a little temp work. I decided to write a MIDI sequencer, 39 | starting over this time in C. 40 | 41 | Around April 1987 I decided to take out an ad in Electronic Musician 42 | magazine and see if I could sell a few copies of the software, which 43 | was called Cakewalk. (I named the "company" Twelve Tone Systems. Later 44 | we renamed the company to Cakewalk, too.) 45 | 46 | Where did the name Cakewalk come from? Just before the deadline for 47 | the first ad, I discovered the original name ("Opus", if I recall 48 | correctly) was already used. During a last-minute search through 49 | Schirmer's dictionary of musical terms, I noticed "Cakewalk". I liked 50 | the connotation of ease-of-use, which matched my goal for the 51 | software. I liked that it was a simple, compound English word everyone 52 | would know how to spell and pronounce. 53 | -------------------------------------------------------------------------------- /src/posts/2013/01/fear-of-macros.md: -------------------------------------------------------------------------------- 1 | Title: Fear of Macros 2 | Date: 2013-01-14T13:25:00 3 | Tags: macros, Racket, software 4 | 5 | I learned Racket after 25 years of mostly using C and C++. 6 | 7 | Some psychic whiplash resulted. 8 | 9 | "All the parentheses" was actually not a big deal. Instead, the first 10 | mind warp was functional programming. Before long I wrapped my brain 11 | around it, and went on to become comfortable and effective with many 12 | other aspects and features of Racket. 13 | 14 | But two final frontiers remained: Macros and continuations. 15 | 16 | 17 | 18 | I found that simple macros were easy and understandable, plus there 19 | were many good tutorials available. But the moment I stepped past 20 | routine pattern-matching, I kind of fell off a cliff into a 21 | terminology soup. 22 | 23 | How do I write a non-trivial macro? It felt like the instructions to 24 | draw an owl. 25 | 26 |
27 | How to draw an owl: 1. Draw some circles. 2. Draw the rest of the fucking owl.
28 | 29 | 30 | I marinaded myself in material, hoping it would eventually sink in 31 | after enough re-readings. I even found myself using trial and error, 32 | rather than having a clear mental model what was going on. Gah. 33 | 34 | Just as the shapes slowly started to emerge from the fog, I wrote: 35 | 36 | Fear of Macros 37 | 38 | -------------------------------------------------------------------------------- /src/posts/2013/01/series-a-round-human-worker-theme-park.md: -------------------------------------------------------------------------------- 1 | Title: Series A round: Human worker theme park 2 | Date: 2013-01-24T13:17:00 3 | Tags: technology 4 | 5 | I am seeking funding for a new venture: A theme park featuring "human 6 | workers". 7 | 8 | The theme park will be similar to 9 | [Old Sturbridge Village](http://www.osv.org/) or 10 | [Plimoth Plantation](http://www.plimoth.org/), but focusing on the 11 | last half of the 20th century. 12 | 13 | 14 | 15 | Visitors will be able to see humans performing a variety of quaint 16 | tasks, such as: 17 | 18 | - Cooking and cleaning 19 | - Serving food 20 | - Constructing buildings 21 | - Paralegal work such as reviewing evidence 22 | - Writing and testing software programs 23 | - Helping humans with health issues (a so-called "doctor" or 24 | "physician") 25 | 26 | Upon admission, visitors receive a small stack of paper known as 27 | "money" or "currency". They give this to the cast members, as a way to 28 | participate in the illusion that the activities have a financial 29 | value. (Historically, this paper would be exchanged among humans for 30 | various kinds of goods and services, in what was known as "the 31 | economy".) 32 | 33 | This theme park gives visitors a way to experience the quaint period 34 | where most humans did work for money and were categorized as 35 | "middle-class". 36 | 37 | Guest services will include typical amenities such as battery-changing 38 | stations. 39 | 40 | Please look forward to my Series A round proposal in the coming 41 | months. I will try to post something to AngelList, too. 43 | 44 | Thank you. 45 | 46 | _Inspiration_: 47 | [Recession, tech kill middle-class jobs](http://bigstory.ap.org/article/ap-impact-recession-tech-kill-middle-class-jobs), 48 | which I found via 49 | [Chris Nahr's Google+ post](https://plus.google.com/u/0/116001262578265367252/posts/1JqRPi26hQY). 50 | -------------------------------------------------------------------------------- /src/posts/2013/02/clear-interrupts.md: -------------------------------------------------------------------------------- 1 | Title: Clear interrupts 2 | Date: 2013-02-12T07:00:00 3 | Tags: life, nostalgia, technology 4 | 5 | For some reason [CLI](https://en.wikipedia.org/wiki/Interrupt_flag) 6 | popped into my head the other day. 7 | 8 | `CLI` is the 808x instruction to clear maskable interrupts. If you're 9 | writing a routine to service a hardware interrupt, you do a `CLI` early 10 | in your routine — to prevent _another_ hardware interrupt 11 | from causing your routine to be re-entered. Neglecting this invites 12 | the most delightful form of bug, the intermittent bug. 13 | 14 | ![Roland MPU-401](https://upload.wikimedia.org/wikipedia/commons/0/06/Roland_MPU-401.jpg "Roland MPU-401") 15 | 16 | 17 | 18 | I spent a lot of time struggling with this stuff early in my coding 19 | career, writing a MIDI sequencer. The 20 | [Roland MPU-401](https://en.wikipedia.org/wiki/MPU-401) MIDI interface 21 | would hit IRQ 8 when some MIDI bytes arrived, or when a timer 22 | ticked. I remember eagerly reading BYTE magazine articles and learning 23 | how write ISRs (interrupt service routines) by trial and error. Many 24 | trials and many errors. There was no internet search much less 25 | StackOverflow.com back then. (Also, we lived in a shoebox in the 26 | middle of the road.) 27 | 28 | In real life we have interruptions. The worst is when you're 29 | interrupted, then that interruption is interrupted. And so on. People 30 | don't have stacks they can pop instantly to return to their prior 31 | context. Instead of popping a stack, the process is akin to flailing 32 | around with Google search, throwing around keywords and trying to 33 | sniff out the right track. 34 | 35 | It's too bad there's no `CLI` instruction in real life. 36 | -------------------------------------------------------------------------------- /src/posts/2013/02/compare-apples-to-oranges-with-the-nexus-4.md: -------------------------------------------------------------------------------- 1 | Title: Compare apples to oranges with the Nexus 4 2 | Date: 2013-02-05T07:00:00 3 | Tags: technology, Android 4 | 5 | About a month ago I got my Nexus 4. My observations follow. 6 | 7 | ![Google Nexus 4](/img/google-nexus-4.png "Google Nexus 4") 8 | 9 | 10 | 11 | # Pure Android experience 12 | 13 | Most Android phones are adulterated with stuff added by the phone 14 | hardware manufacturer and/or the mobile carrier. 15 | 16 | The Nexus 4 is the pure Android experience as Google intended. This 17 | lets you compare apples to oranges: the Platonic ideals of iOS 18 | vs. Android. 19 | 20 | 21 | Android 4.2 on Nexus 4 is polished and smooth. Although I haven't used 22 | an iPhone, I'm pretty familiar with iOS from using an iPad. Both iOS6 23 | and Android 4.2 are fast, mature mobile OSs. 24 | 25 | My opinion? Android 4.2 is more [zazzy](http://youtu.be/mUy5NX0VmA4). 26 | 27 | # Google Now 28 | 29 | I took it on a 3-cities-in-1-week trip to the West Coast. Google Now 30 | was surprisingly smart. I got "cards" about travel time to 31 | appointments and airports, flights, nearby sightseeing and photo 32 | opportunities, the local time back home, and so on. Pretty neat. 33 | 34 | Although slightly creepy, it's using information I've already chosen 35 | to give Gmail and Google Calendar, plus location information I 36 | knowingly opted to share. I feel a little better knowing that Google 37 | pioneered 38 | [transparency reports](https://www.eff.org/deeplinks/2012/11/google-transparency-report-shows-rising-trend-government-surveillance). 39 | 40 | # Logistics 41 | 42 | There was a four week wait for my order to be fulfilled. In December 43 | 2012, nearly all of the devices on Google Play store had multi-week 44 | waits or were "out of stock" entirely: Nexus 4, Nexus 7, both 45 | Chromebooks, even accessories like the bumper for the Nexus 4. 46 | 47 | Although this is a good problem to have, it is a problem. For the 48 | first time, Google created a coherent, appealing set of hardware 49 | products. It's generating excitement. And ... they can't fill 50 | orders. I don't get the impression this is an intentional tactic 51 | ("scarcity makes people want it more"). Hopefully with more experience 52 | and volume they will show more confidence, as well as command more 53 | mindshare with their supply chain. 54 | -------------------------------------------------------------------------------- /src/posts/2013/02/computing-like-component-stereo-systems.md: -------------------------------------------------------------------------------- 1 | Title: Computing like component stereo systems 2 | Date: 2013-02-01T10:16:00 3 | Tags: technology 4 | 5 | Which of these do you use, and in what proportion? 6 | 7 | - Phone 8 | - Tablet 9 | - Laptop 10 | - Desktop 11 | 12 | 13 | 14 | [Noel Borthwick](http://plus.google.com/100031147479819116208) 15 | [asked this](https://plus.google.com/u/0/100031147479819116208/posts/UVavfNSH62y) 16 | and the resulting discussion was interesting. Here's a longer version 17 | of a comment I posted. 18 | 19 | Noel said he uses mostly tablet and desktop, and barely uses phone. 20 | 21 | Personally I'm more like 90% laptop, 5% tablet, and 5% phone (MacBook 22 | Pro Retina, iPad, and Nexus 4, respectively.) That's while 23 | stationary. While traveling it's more like 50% laptop and 50% 24 | phone. I find the tablet to be neither here nor there: Not as 25 | powerful as the laptop, and not as small as the phone. (Although 26 | I do bring my eInk Kindle, because it's incredibly light and is 27 | powered by magic faeries (i.e. the battery lasts for weeks), so it's 28 | kind of a no-brainer.) 29 | 30 | Anyway, I could see decomposing the pieces. Remember component stereo 31 | systems? Oh, you're not old enough or hi-fi enough? Well, 32 | people would buy amplifier, tuner, turntable, cassette player, CD 33 | player, and speakers as separate units. Connected by a mess of wires. 34 | 35 | What I want is component computing, but without the mess of 36 | wires. This would be in contrast to purchasing various all-in-ones 37 | (phone, tablet, laptop) that have unnecessarily redundant 38 | functionality. 39 | 40 | 1. A phone-size thing with your radios, main CPU(s), local storage, 41 | and a small screen. 42 | 2. A tablet-size touch screen ("mini", big, or one of each). 43 | 3. A physical keyboard. 44 | 4. Goggles (ala Google Glass). 45 | 46 | They connect wirelessly. You can mix/match depending on your 47 | situation. Also you can connect to big-ass displays. 48 | 49 | 1-3 already exist; 4, soon. 50 | 51 | Apple could sell this vision and make it "just work". On the other 52 | hand, what I describe is lower-margin than what Apple does today. For 53 | instance 2 would be closer to a simple touch-screen that uses 1's 54 | CPU/storage, as opposed to a full-on iPad or Nexus 7/10. Pursuing a 55 | lower-margin version of something isn't usually Apple's game plan, so 56 | it's hard for me to imagine them doing this, at least not yet. 57 | 58 | Of course 2+3 has been tried, most recently by Microsoft with 59 | Surface. But not really: I mean a "dumb" tablet not a smart one. 60 | Plus, Microsoft is so far behind with market share on phones, I don't 61 | think they could pull this off today. 62 | 63 | Other companies tend to be primarily in just one business, and the 64 | resulting hybrid is accordingly wonky. For example the Motorola Atrix 65 | felt like a phone with an optional keyboard tacked on, and didn't even 66 | attempt to integrate with a bigger touch-screen. Motorola saw the 67 | world through phone-colored glasses. Most other companies wear tinted 68 | lenses, too. 69 | 70 | In conclusion, this seems like an obvious and desirable direction. I 71 | just don't know if there are players today who are both willing and 72 | able to pull it off. Specifically I think Apple is able, but not 73 | willing. 74 | -------------------------------------------------------------------------------- /src/posts/2013/02/fucking-suggested-post-why-web-apps-matter.md: -------------------------------------------------------------------------------- 1 | Title: Fucking "Suggested Post" (why web apps matter) 2 | Date: 2013-02-20T18:55:00 3 | Tags: software, technology 4 | 5 | 6 | So [speaking of AdBlock](/2013/02/my-chrome-extensions.html), 7 | sometimes I forget it's there. 8 | 9 | I was using the Facebook app for iPad today. Which I hardly ever do. And I'm getting a lot of this in my feed: 10 | 11 | ![Suggested Post](/img/suggested-post.png) 12 | 13 | And I'm all like: 14 | 15 | ![The fuck was that... the fuck is this?](/img/the-fuck-was-that-is-this.gif) 16 | 17 | And I'm clicking "report spam" on each of the little fuckers. Tap, tap, please bugger off. Tap, tap, suck my balls and bugger off. And so on. 18 | 19 | 20 | 21 | Later it dawns on me: It's because I'm using Facebook's iPad app. 22 | Back in Chrome, the Facebook web app has been trying to show me 23 | the little fuckers delightful suggested posts, but 24 | AdBock has been quietly removing them. 25 | 26 | The point? I can control this with a web app in a browser. I can use 27 | extensions, take charge, and bend it to my will. But with someone's 28 | precious mobile app? They're in control. 29 | 30 | I hope and believe the web app will gradually prevail on mobile, as 31 | it's been doing on the laptop/desktop. How many of us check email with 32 | web apps these days? Almost all of us. OK, sure, you there in the suit 33 | still using Outlook; give your IT department a couple more years to 34 | catch up, and that will change. Oh, and you there using gnus in Emacs 35 | on a circa 2001 ThinkPad? I like how you roll, but you're the corner 36 | case, and you already understand and agree with the spirit of what I'm 37 | talking about: Web apps are hackable. Even if most people don't 38 | know how to hack them, they know how to use extensions written by 39 | people who do. 40 | 41 | This is important. 42 | 43 | Yes, yes, I know. "Only native apps can access ____ feature on the 44 | device." That's temporary. That's because the phone OS makers are 45 | unwilling or unable to let browser apps do this. So far. 46 | 47 | I believe this will happen. I sure as hell hope it will. I'm 48 | not eager to be dragged back into the equivalent of telemarketing 49 | calls during dinner. And it's not just about annoyance. The 50 | hackability of web apps is about empowering individual users with 51 | special needs of all kinds. 52 | -------------------------------------------------------------------------------- /src/posts/2013/02/greg-head-first-greg.md: -------------------------------------------------------------------------------- 1 | Title: greg^.head --> (first greg) 2 | Date: 2013-02-26T07:00:00 3 | Tags: software 4 | 5 | A college classmate's nickname for me, after we learned linked lists in Pascal: 6 | 7 | ```pascal 8 | greg^.head 9 | ``` 10 | 11 | Updated, for many years it would have been: 12 | 13 | ```c 14 | greg->head 15 | ``` 16 | 17 | Not long ago I did quite a bit of: 18 | 19 | ```sql 20 | SELECT TOP 1 part 21 | FROM greg 22 | SORT BY height 23 | ``` 24 | 25 | Nowadays: 26 | 27 | ```racket 28 | (first greg) 29 | ``` 30 | 31 | 32 |
33 | Answers:
C/C++.
SQL.
Lisp/Scheme/Racket.
34 | -------------------------------------------------------------------------------- /src/posts/2013/02/my-chrome-extensions.md: -------------------------------------------------------------------------------- 1 | Title: My Chrome extensions 2 | Date: 2013-02-14T07:00:00 3 | Tags: software, technology 4 | 5 | My [Chrome](http://www.google.com/chrome) extensions these days, from 6 | left to right... 7 | 8 | ![Chrome Extensions](/img/chrome-extensions.png) 9 | 10 | 11 | 12 | ### Xmarks 13 | 14 | [Xmarks](https://chrome.google.com/webstore/detail/xmarks-bookmark-sync/ajpgkpeckebdhofmmjfgcjjiiejpodla?utm_source=chrome-ntp-icon) 15 | to sync bookmarks. (But _not_ passwords; I use LastPass for that.) 16 | 17 | ### AdBlock 18 | 19 | [AdBlock](https://chrome.google.com/webstore/detail/adblock/gighmmpiobklfepjocnamgkkbiglidom?utm_source=chrome-ntp-icon) to, well, block ads. 20 | 21 | When someone jokes about the latest bizarre ad that Facebook showed them, I'm like: Ads? 22 | 23 | Note: I do unblock ads for specific sites I want to support. 24 | 25 | ### LastPass 26 | 27 | [LastPass](https://chrome.google.com/webstore/detail/lastpass/hdokiejnpimakedhajhdlcegeplioahd?utm_source=chrome-ntp-icon) 28 | to manage passwords. 29 | 30 | If I were forced to choose just one, it would have to be 31 | LastPass. It's the only realistic way to Do the Right Thing with 32 | passwords. Such as creating sufficiently strong passwords. And not 33 | reusing the same password across multiple web sites. 34 | 35 | It's also the only sane way to use such passwords on mobile devices. 36 | 37 | It's available for all major browsers, for free. To also use on your 38 | mobile devices you pay a fee per month. 39 | 40 | You can set it up to use 41 | [two-factor authentication](http://helpdesk.lastpass.com/security-options/#Multifactor+Authentication+Options): 42 | something you _have_ (e.g. your phone) as well as something you _know_ 43 | (the password). 44 | 45 | ### Music Plus for Google Play Music 46 | 47 | [Music Plus for Google Play Music](https://chrome.google.com/webstore/detail/music-plus-for-google-pla/ipfnecmlncaiipncipkgijboddcdmego?utm_source=chrome-ntp-icon) 48 | to make [Google Play Music](https://play.google.com/store/music) 49 | spiffier and scrobble to [Last.fm](http://last.fm/). 50 | 51 | 52 | Although I'm mostly using [Rdio](http://www.rdio.com/) these days to discover music, what I'd already purchased is stored in Google Play Music. 53 | 54 | ### Ghostery 55 | 56 | [Ghostery](https://chrome.google.com/webstore/detail/ghostery/mlomiejdfkolichcflejclcbmpeaniij?utm_source=chrome-ntp-icon) 57 | to block a variety of "trackers". My most-recent addition. 58 | 59 | 60 | # tl;dr 61 | 62 | The must-have extension is LastPass. 63 | -------------------------------------------------------------------------------- /src/posts/2013/02/walking-in-the-steps-of-soft-interrogation.md: -------------------------------------------------------------------------------- 1 | Title: Walking in the steps of soft interrogation 2 | Date: 2013-02-19T07:00:00 3 | Tags: life, politics 4 | 5 | For many years I visited [Frankfurt Musikmesse][]. Roughly speaking, 6 | Musikmesse is Europe's equivalent of the [NAMM show][] in the US. 7 | 8 | Folks from my company would stay north of Frankfurt at the 9 | [Mövenpick Hotel][] in Oberursel. It was more affordable and very 10 | comfortable. The front desk staff remained the same for many 11 | years. They got to know us--the obviously American, odd musician 12 | types--and were very friendly. 13 | 14 | A couple years ago, I'd scheduled to fly home on Sunday, in case I 15 | needed to have more meetings on Saturday. The meetings never 16 | materialized, and I took it as a free day. The April weather was 17 | unseasonably warm. I walked into Oberursel's small "downtown" and 18 | explored it like I never had before. At some point I ended up 19 | wandering into paths that led through parks and open fields. It was 20 | very beautiful. 21 | 22 | 23 | 24 | Today I read the BBC article about Obergefreiter Hanns Scharff, 25 | [the WWII interrogator who used kindness over violence][Scharff]. In 26 | short, he got amazing results via his "firm conviction that 27 | interrogation could succeed without treating prisoners in an inhumane 28 | manner." As the BBC says, this is a timely topic: 29 | 30 | > Scharff died in California 20 years ago but his legacy remains. At 31 | > first his name did not figure in the sharp and bitter public exchanges 32 | > about the morality of waterboarding and so-called "enhanced 33 | > interrogation techniques" that were sanctioned by President George 34 | > Bush and inflicted on alleged terrorists detained in Abu Ghraib 35 | > prison, Guantanamo Bay and secret CIA detention centre. 36 | 37 | What did he do instead? 38 | 39 | > He pretended to be a prisoner's best pal. Masquerading as a nice 40 | > guy, Scharff arranged for special treats outside the confines of 41 | > Dulag Luft. He arranged for one prisoner to enjoy a brief flight in 42 | > a German fighter plane, prisoners were treated to slap-up feeds 43 | > with German fliers, granted medical treatment and even permitted to 44 | > go on an outing to the local zoo. 45 | > 46 | > Typically, after extracting a precautionary undertaking that he would 47 | > not use the opportunity to make a bid to escape, a prisoner could 48 | > enjoy a visit to Oberursel forest, with Scharff acting as chaperone 49 | > and guide. 50 | > 51 | > Rambling along woodland paths the two men chatted about the flora and 52 | > fauna and engaged in small talk, including for example, musing about 53 | > British and US social activities or customs. 54 | 55 | Wait, what? Oberursel? 56 | 57 | It turns out I may have unknowingly been walking on the same paths as 58 | did Scharff and his prisoners over 50 years ago. 59 | 60 | Others should walk that path. Well, the metaphorical path: Not only is 61 | torture unethical, it's [less effective][] than smarter interrogation 62 | approaches. 63 | 64 | [Frankfurt Musikmesse]: http://musik.messefrankfurt.com/frankfurt/en/besucher/willkommen.html 65 | [NAMM show]: http://www.namm.org/thenammshow/ 66 | [Mövenpick Hotel]: http://www.moevenpick-hotels.com/en/europe/germany/frankfurt/hotel-frankfurt-oberursel/overview/ 67 | [Scharff]: http://www.bbc.co.uk/history/0/19923902 68 | [less effective]: http://www.international.ucla.edu/article.asp?parentid=107697 69 | -------------------------------------------------------------------------------- /src/posts/2013/02/we-need-a-prior-art-database.md: -------------------------------------------------------------------------------- 1 | Title: We need a prior art database 2 | Date: 2013-02-07T07:00:00 3 | Tags: patents, politics, software, technology 4 | 5 | Last week I visited 6 | [Electronic Frontier Foundation](https://www.eff.org/) in San 7 | Francisco. Mark Cuban and Minecraft creator Markus Persson had 8 | recently funded an additional staff attorney position, 9 | [the "Mark Cuban Chair to Eliminate Stupid Patents"](https://www.eff.org/mention/eff-creates-mark-cuban-chair-eliminate-stupid-patents). 10 | 11 | Considering this after I got home, I had an idea about an additional 12 | angle from which to attack this problem. 13 | 14 | What if there were a sort of "prior art database"? Where people could 15 | submit and find information about first-use or discovery of 16 | techniques--whether patented or not? 17 | 18 | 19 | 20 | # Benefits: 21 | 22 | Engineers/inventors have an alternative to the patent process. This 23 | alternative allows them to receive recognition, which for many people 24 | is the primary motivation. And although they would forgo licensing (or 25 | trolling) revenue, there are other financial rewards that can result 26 | from a demonstration of expertise. 27 | 28 | Companies have a time- and cost-effective alternative to "defensive" 29 | patents. 30 | 31 | It's a helpful resource for contesting stupid patents. 32 | 33 | # Implementation: 34 | 35 | Instead of a database _per se_, probably this would work better as 36 | something like a wiki or 37 | [StackOverflow.com](http://stackoverflow.com/). 38 | 39 | 40 | Perhaps Google could sponsor/spearhead, as they're presumably both 41 | willing and able. 42 | 43 | Perhaps the [USPTO](http://www.uspto.gov/) could sponsor/host. If 44 | sufficient people chose this alternative, it could appreciably 45 | decrease the application backlog and work overload. 46 | 47 | Is the concept of "approval" needed? Maybe not. People could file an 48 | electronic affidavit, or even just make a mere to-their-knowledge 49 | statement. 50 | 51 | If some form of approbation _were_ needed, it could be 52 | crowd-sourced. Think of the voting and reputation system of 53 | StackOverflow.com. 54 | 55 | In fact, this seems like a natural evolution for Stack Exchange. 56 | 57 | As I said above, this is not "the" answer. The patent system is 58 | horribly dysfunctional, especially for software patents. The problem 59 | needs attacking from multiple angles. Reforms like five year 60 | expirations for software patents are needed and should be 61 | implemented. In addition, I think some sort of go-to resource for 62 | prior art could help. 63 | -------------------------------------------------------------------------------- /src/posts/2013/03/feed-stats-in-frog-without-feedburner.md: -------------------------------------------------------------------------------- 1 | Title: Feed stats in Frog (without FeedBurner) 2 | Date: 2013-03-19T09:11:43 3 | Tags: Frog, Google, Atom, RSS 4 | 5 | In light of Google [shutting down Google Reader][] and 6 | [removing feed-following UI in Chrome][], it probably can't be long 7 | until they shut down FeedBurner, too. 8 | 9 | Although I'm using [Google Analytics][] for this blog, I'm not using 10 | FeedBurner. But imagining what feed readership stats I might want, I 11 | came up with a short list, and thought about how to get them without 12 | FeedBurner. 13 | 14 | 15 | 16 | 1. "Subscribers": Tell me how many people have chosen to subscribe to 17 | the Atom or RSS feed. How often they bother to read it is another 18 | matter. 19 | 20 | 2. "Readers on web site": Tell me how many people are clicking through 21 | from the feed to read the full post on the original web site. 22 | 23 | 3. "Readers, feed-only": Tell me how many people opened the feed item, 24 | possibly read it, but did _not_ click through to the orignal web 25 | site. 26 | 27 | So the excercise is, how to do this _without_ FeedBurner. 28 | 29 | I [just added][7c0ed36] tentative stuff to [Frog][] for items 2 and 3. 30 | 31 | 32 | ## Readers clicking through to this site 33 | 34 | Some people will start to read an item in the feed, then click through 35 | to the main web site to finish reading it. (People are more likely to 36 | do this if you've set your blog to show only above-the-jump summaries, 37 | with "continue reading" links.) 38 | 39 | Since this is page views on our web site, this can be handled by 40 | Google Analytics (or similar, if Google shuts _that_ down). The only 41 | trick is to distinguish viewers who got here from the feed, as opposed 42 | to from somewhere else. 43 | 44 | Well, that's what the GA `utm_xxx` [query parameters][] are for. In the 45 | feed, we'll decorate the URIs that lead back to the original blog 46 | post. 47 | 48 | http://example.com/path/to/thing.html 49 | 50 | becomes 51 | 52 | http://example.com/path/to/thing.html? 53 | utm_source=feed& 54 | utm_medium=feed 55 | 56 | This should let us view stats for `path/to/thing.html` in Google 57 | Analytics, that came via the feed. 58 | 59 | In Frog I'm also adding a `utm_campaign` parameter, and distinguishing 60 | the RSS and Atom feeds (just because I'm curious). 61 | 62 | 63 | ## Readers staying in their feed reader 64 | 65 | Some people will solely read items in your feed, and not click 66 | through to your web site. (This is especially likely if you're showing 67 | full posts in your feed.) How to count reads? 68 | 69 | The answer here seems to be the tried-and-true "image bug". 70 | 71 | Each feed item's contents will get the following image element added 72 | automatically: 73 | 74 | ```html 75 | 38 | 39 | The satisfactory answer is it's OK provided you can 40 | [export your data][how-to-geek]. Many Google services let you do this, 41 | including Google Reader. (Google's new Keep service doesn't, 42 | yet. Until it does, you might want to steer clear, for example.) 43 | 44 | I say the merely "satisfactory" answer because it's not ideal. There 45 | is still that feeling that "someone else is in control of my 46 | data". That leads to issues of trust, as well as longevity. And even 47 | if you can take your data away, you need to deal with such 48 | schlepping. We wanted to avoid that, with web apps. 49 | 50 | Is some hybrid possible--a way to get the best of both worlds? 51 | 52 | It's not clear to me yet. But let's start here: What if people could 53 | run their own web apps on their own Amazon EC2 instances? You can do 54 | this today, using prebuilt images. 55 | 56 | Unfortunately it's too complicated for an individual. (Remember, we 57 | prefer web apps because it gets us _out_ of playing IT 58 | ourselves). Also for an individual even a small EC2 instance is too 59 | much for one web app; it will be severely under-utilized. Which is 60 | another way to say it won't be cost-effective. 61 | 62 | But what if there were something more granular than entire EC2 63 | instances? And what if it could be _managed_ for me, but _I_ clearly 64 | _own_ it? 65 | 66 | [Google Reader shutdown]: http://googlereader.blogspot.com/2013/03/powering-down-google-reader.html 67 | [web apps]: http://www.greghendershott.com/2013/02/fucking-suggested-post-why-web-apps-matter.html 68 | [Synchronization sucks]: http://tiamat.tsotech.com/synchronization-sucks 69 | [how-to-geek]: http://www.howtogeek.com/141754/what-google-readers-shutdown-teaches-us-about-web-apps/ 70 | 71 | -------------------------------------------------------------------------------- /src/posts/2013/03/killing-google-reader.md: -------------------------------------------------------------------------------- 1 | Title: Killing Google Reader 2 | Date: 2013-03-14T09:17:48 3 | Tags: Google, RSS, Atom 4 | 5 | Last night I learned the news that 6 | [Google is killing Google Reader July 1][announce]. 7 | 8 | This morning I'm feeling [like this](https://youtu.be/A25VgNZDQ08). 9 | 10 | 11 | 12 | 13 | 14 | ## Google killed feed readers 15 | 16 | Google Reader was so good that it sucked all the oxygen out of the 17 | market for feed readers, especially in the form of desktop apps. Since 18 | then, innovation in this area has stagnated. 19 | 20 | Glass half full, this could prompt a flurry of activity and 21 | interesting new options. 22 | 23 | ## Google Reader, the service 24 | 25 | At the same time, Google Reader became a service upon which many other 26 | apps and services depended, and with which they synchronized. 27 | 28 | Suddenly they have to scramble to do something else--or decide to kill 29 | their projects, too. Like [FeedDemon][] just announced. 30 | 31 | Glass half empty, there will be more of this and it will make the 32 | concept of RSS and Atom feeds weaker, not stronger. 33 | 34 | ## Reeder for Mac: Alternative? 35 | 36 | Last night I tried [Reeder][] for Mac. On the plus side, it can work 37 | quite similarly to Google Reader. Out of the box you can use shortcut 38 | keys like `J` and `K` to navigate within a feed. I changed its other 39 | defaults to make it work even closer to GReader: `SHIFT+J` and 40 | `SHIFT+K` to navigate between feeds, and `V` to open the original page 41 | in a real browser. 42 | 43 | Although it will take time to adjust, it might be fine. 44 | 45 | A couple caveats: 46 | 47 | - It's not free. (Although $4.99 seems pretty reasonable for what it 48 | does. Especially if you care enough to get mad about Google Reader 49 | going away.) 50 | 51 | - It's not in the browser. It just feels weird to me to juggle two 52 | different apps. Could/should this a reader built into the browser as 53 | an extension for Chrome or Firefox? 54 | 55 | - If you're using Chrome OS, what the hell do you do? If I had just 56 | bought an expensive [Chromebook Pixel][], I'd be pretty upset today. 57 | Suddenly killing useful web apps doesn't help Google make the case 58 | for web apps. 59 | 60 | Lifehacker has a post about [more alternatives][]. 61 | 62 | ## Google's relationship with tech press and bloggers 63 | 64 | I remember the era around 2005, when LifeHacker was writing about 65 | Gmail and Google Reader. Here were these cool new web apps, more 66 | powerful than you might have guessed. And here were all these geeks 67 | eagerly embracing them. 68 | 69 | I suspect this played a big role in Google's generally positive image 70 | and mindshare. 71 | 72 | Something like Google Reader is particularly useful and appealing to 73 | precisely the kind of people who write about the tech industry. I'm 74 | curious what effect this will have--when Google takes away a key part 75 | of those folks' daily lives, and effectively gives them the 76 | finger. Will this turn out to be penny wise and pound foolish? 77 | 78 | ## Summary 79 | 80 | Anyway, those are my initial thoughts. I hope we get the 81 | glass-half-full version. 82 | 83 | [announce]: http://googlereader.blogspot.com/2013/03/powering-down-google-reader.html 84 | [FeedDemon]: http://nick.typepad.com/blog/2013/03/the-end-of-feeddemon.html 85 | [Reeder]: http://reederapp.com/ 86 | [Chromebook Pixel]: http://www.google.com/intl/en/chrome/devices/chromebook-pixel/ 87 | [more alternatives]: http://lifehacker.com/5990456/google-reader-is-getting-shut-down-here-are-the-best-alternatives 88 | -------------------------------------------------------------------------------- /src/posts/2013/03/live-with-frog.md: -------------------------------------------------------------------------------- 1 | Title: Live with Frog 2 | Date: 2013-03-10T19:14:44 3 | Tags: blog, Frog, Racket, software 4 | 5 | OK, I have my site generated using Frog and pushed it to GitHub Pages 6 | for . 7 | 8 | I want to tweak the CSS. It's vanilla Bootstrap. Most importantly, I 9 | wish the headings were a few points smaller and the body text a few 10 | points bigger. 11 | 12 | The other remaining item is to do the 301 redirect from Blogger to 13 | here. It turns out that this should be ridiculously easy to do with 14 | Amazon S3. You can make 15 | [a bucket that does nothing but perform 301 redirects][1]. I've 16 | created such a bucket already, and the redirects work fine. I simply 17 | need to update my DNS, for which I'm using Amazon Route 53. 18 | 19 | [1]: http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html 20 | -------------------------------------------------------------------------------- /src/posts/2013/03/lull-while-i-prepare-to-change-tires.md: -------------------------------------------------------------------------------- 1 | Title: Lull while I prepare to change tires 2 | Date: 2013-03-08T16:27:00 3 | Tags: software, Racket, blog, Frog 4 | 5 | I'd been trying to stick to a roughly Tuesday and Thursday schedule 6 | for posting here. 7 | 8 | I haven't this week because I've been trying to work up a replacement 9 | for using Blogger. 10 | 11 | Basically, I want to write posts in simple Markdown, and generate the 12 | blog statically. To be hosted on GitHub or S3 or whatever. And I want 13 | it to use Bootstrap so I don't have to reinvent that wheel. 14 | 15 | 16 | 17 | I realize a bunch of tools purport to do this. The ones I've looked at 18 | so far, such as Octopress, require installing a bunch of Ruby 19 | hoo-hah. I'm a a Racket guy. And I recently wrote a Markdown parser in 20 | Racket (with something else in mind, not this project). 21 | 22 | As a result, I've been spending the last couple days working on the 23 | static site generator. 24 | 25 | My working name for it is Frog. As in frozen blog. 26 | 27 | The gist of it has some together pretty quickly in Racket, which is a 28 | joy to use. 29 | 30 | The bulk of the work remaining is: 31 | 32 | 1. Migrate old posts over. That should be pretty easy. 33 | 34 | 2. Decide how much I care about URI compatibility and SEO "link 35 | juice". Specifically, is it OK if 36 | `blog.greghendershott.com/2013/03/some-title.html` changes to 37 | `www.greghendershott.com/2013-03-08-some-title.html`? Or do I want 38 | them exacto same? Obviously the latter will be harder and require 39 | something to do 301 redirects due to the subdomain change. Maybe 40 | I'll decide I don't care enough, given how new this blog is and the 41 | "link equity" is probably not so great. We'll see. 42 | -------------------------------------------------------------------------------- /src/posts/2013/03/my-google-reader-successor.md: -------------------------------------------------------------------------------- 1 | Title: My Google Reader successor 2 | Date: 2013-03-29T13:01:50 3 | Tags: Google, Atom, RSS 4 | 5 | Just a brief update about what I've settled on as my replacement for 6 | Google Reader. I'm using Rss2Email, following the instructions in 7 | [Turning Gmail into Google Reader](http://wcm1.web.rice.edu/turning-gmail-into-google-reader.html). 8 | 9 | I tried Feedly, NewsBlur, and Reeder. Each of them wasn't bad, but 10 | each felt "heavy" compared to Google Reader. So W. Caleb McDaniel's 11 | post really clicked with me. 12 | 13 | 14 | 15 | I did end up trimming 350 feeds down to 50. What I cut: 16 | 17 | 1. Old/dead feeds. 18 | 19 | 2. Big name feeds. 20 | 21 | By the latter, I mean that I don't need feeds to help me keep up with 22 | fire hose sites like Wired, TechCrunch, and so on. I can follow them 23 | in Twitter. If I miss a specific article, no big deal. Also, I will 24 | encounter a lot of that stuff on something like Hacker News. 25 | 26 | Instead, I'm using feeds for the "long tail" stuff: Sites that post 27 | infrequently, and/or that are are smaller. And sites where I may want 28 | to read every post (or at least glance at every one). 29 | 30 | The experience in Gmail is pretty darn close to GReader. Same 31 | navigation keys. Can star things. Can forward in email. On the one 32 | hand, this makes me empathize with Google wanting to kill Reader. But 33 | on the other hand, it makes me feel, why didn't they add feed reeding 34 | _into_ Gmail? Why the desire to deprecate RSS and Atom feeds? 35 | Grumble, grumble. 36 | 37 | So overall it's actually quite similar. Differences in the experience? 38 | 39 | One small nuisance is the lack of a `V` key to go to the original web 40 | page. I have to mouse up and click the link. 41 | 42 | A broader difference is that, even though it's Gmail and I'm far from 43 | running out of space, I don't want a lot of feed stuff to accumulate 44 | there. It probably doesn't matter, I could periodically just nuke all 45 | the mails labeled Feeds, but there's some "psychic weight". But as I 46 | explained, I'm being more selective in the feeds I follow. 47 | 48 | A tip: On Mac OS X, I tried both methods of sending mail--connecting 49 | to my GMail account, or, using `sendmail`. The latter worked better, 50 | because the display name of the From email address is the name of the 51 | feed (it looks like Caleb's screen shot). 52 | 53 | For the future: I have `cron` running this hourly on my Mac Book. That 54 | works just fine at home. But on the road, it won't be pushing emails 55 | to my phone if the laptop isn't running. So I'll probably move this to 56 | a server, such as a micro EC2 instance that I'm using for something 57 | else. The only gotchas there? Might need to send the email using 58 | Amazon SES since EC2 instances are usually blacklisted for email 59 | sending. Plus, adding or removing feeds will require `ssh`-ing into 60 | that server. Although not horrible, not super user friendly. 61 | 62 | > **UPDATE:** I ended up replacing rss2email with [feeds2gmail, which is similar but has a few advantages](http://www.greghendershott.com/2013/05/feeds2gmail.html). 63 | 64 | -------------------------------------------------------------------------------- /src/posts/2013/03/serve-static-files.md: -------------------------------------------------------------------------------- 1 | Title: Serve static files 2 | Date: 2013-03-22T12:00:00 3 | Tags: Racket 4 | 5 | I wanted [Frog][] to provide a "preview" feature: Launch a local web 6 | server with a version of the site, and open a web browser. 7 | 8 | This local web server simply needs to serve static files. No 9 | server-side applications. (Not even features you'd likely want in a 10 | production static file server like gzip compression or `If-Modified` 11 | handling.) It just needs to start quickly, and preferably not be a 12 | lot of work to code. 13 | 14 | 15 | 16 | At first glance, the Racket web server's [serve/servlet][] function 17 | has a somewhat overwhelming set of options--approximately 25 keyword 18 | arguments. Fortunately only a few apply to this situation. If you want 19 | to start a web server that _only_ serves static files, it's simply 20 | this: 21 | 22 | ```racket 23 | (require web-server/servlet-env 24 | web-server/http 25 | web-server/dispatchers/dispatch) 26 | (serve/servlet (lambda (_) (next-dispatcher)) 27 | #:servlet-path "/" 28 | #:extra-files-paths (list path/to/files) 29 | #:port 8080 30 | #:launch-browser? #t) 31 | ``` 32 | 33 | Easy. 34 | 35 | - - - 36 | 37 | Just as I was finishing this post, Jay McCarthy pointed out to me that 38 | the docs have [an example of a more direct way][serve]: 39 | 40 | ```racket 41 | (require web-server/web-server) 42 | (serve #:dispatch (files:make #:url->path (make-url->path path/to/files) 43 | #:path->mime-type (lambda (_) 44 | #"application/octet-stream")) 45 | #:port 8080) 46 | ``` 47 | 48 | This handles requests more directly, as well as not requiring modules 49 | that are hardly utilized, since we're not using any servlets. 50 | 51 | Just keep in mind a few wrinkles: 52 | 53 | - `serve` returns immediately with a procedure you call to shut down 54 | the server. 55 | 56 | - You'll need to implement your own version of `serve/servlet` 57 | conveniences: 58 | 59 | - Print messages like, `Your Web application is running at 60 | http://localhost:8080/.` `Stop this program at any time to terminate 61 | the Web Server.`. 62 | 63 | - Call [send-url][] to launch the browser. 64 | 65 | - Wait for the user to press `CTRL-C`, and call the shutdown 66 | procedure given to you by `serve`. 67 | 68 | For example, [here's][repo] how `serve/servlet` does these things. 69 | 70 | Using `serve` is more efficient and flexible. You can use it for a 71 | variety of web server scenarios. On the other hand it looks like 72 | `serve/servlet` is probably more convenient for the specific use case 73 | of "preview this in a local web server and browser". 74 | 75 | 76 | [Frog]: https://github.com/greghendershott/frog 77 | [serve/servlet]: http://docs.racket-lang.org/web-server/run.html#%28def._%28%28lib._web-server%2Fservlet-env..rkt%29._serve%2Fservlet%29%29 78 | [serve]: http://docs.racket-lang.org/web-server-internal/web-server.html#%28def._%28%28lib._web-server%2Fweb-server..rkt%29._serve%29%29 79 | [send-url]: http://docs.racket-lang.org/net/sendurl.html#%28def._%28%28lib._net%2Fsendurl..rkt%29._send-url%29%29 80 | [repo]: https://github.com/plt/racket/blob/611e8d0d17ce096ef624ef442d632f954a783e7b/collects/web-server/servlet-dispatch.rkt#L114-L160 81 | -------------------------------------------------------------------------------- /src/posts/2013/03/the-year-google-became-evil.md: -------------------------------------------------------------------------------- 1 | Title: The year Google became "Evil"? 2 | Date: 2013-03-15T09:39:56 3 | Tags: Google 4 | 5 | Via 6 | [Daring Fireball](http://daringfireball.net/linked/2013/03/14/google-destroyer) 7 | I found 8 | [Google, destroyer of ecosystems](http://corte.si/posts/socialmedia/rip-google-reader.html). Aldo 9 | Cortesi writes: 10 | 11 | > The truth is this: Google destroyed the RSS feed reader ecosystem with 12 | > a subsidized product, stifling its competitors and killing 13 | > innovation. It then neglected Google Reader itself for years, after it 14 | > had effectively become the only player. Today it does further damage 15 | > by buggering up the already beleaguered links between publishers and 16 | > readers. It would have been better for the Internet if Reader had 17 | > never been at all. 18 | 19 | What struck me is the obvious search-and-replace: 20 | 21 | 22 | 23 | > The truth is this: Microsoft destroyed the browser market with a 24 | > subsidized product, stifling its competitors and killing 25 | > innovation. It then neglected Internet Explorer itself for years, 26 | > after it had effectively become the only player. It would have been 27 | > better for the Internet if Internet Explorer had never been at all. 28 | 29 | Will we look back on 2013 as the year that Google became "Evil"? When 30 | "do no evil" became a quaint artifact of corporate mythology? 31 | 32 | I realize plenty of people have probably been saying Google is evil 33 | for some time already. And some won't say it for a long time, if 34 | ever. Just like with Microsoft. 35 | 36 | It's weird timing. Couple days ago, on Wednesday, I sign up for my 37 | first-ever Google I/O conference. Later that evening, Google announces 38 | "spring cleaning", killing Google Reader and CalDAV. Thursday everyone 39 | forgets it's Pi Day; how sad. I'm spending time looking at Google 40 | Reader alternatives. I'm downloading Thunderbird and starting to 41 | question my continued use of Gmail. My cherished Gmail! 42 | 43 | Is this how it starts, step by step? Someday will we look back on this 44 | as the inflection point? 45 | -------------------------------------------------------------------------------- /src/posts/2013/04/planet-vs-the-new-package-system.md: -------------------------------------------------------------------------------- 1 | Title: Planet vs. the new package system 2 | Date: 2013-04-11T13:58:19 3 | Tags: Racket 4 | 5 | Recently I've shared some new libraries using the new package manager, 6 | but _not_ uploaded them to Planet. 7 | 8 | What about my existing Planet libraries? Yesterday Danny Yoo pointed 9 | out a bug in the Dynamo module of my Amazon AWS library on Planet. 10 | 11 | The bug wasn't present in my GitHub repo--I'd neglected to go through 12 | the steps of making a new version and uploading to Planet. 13 | 14 | By contrast, with the new package manger, it would have been 15 | automatically up-to-date. 16 | 17 | 18 | 19 | So I did two things: 20 | 21 | 1. Updated the `aws` and `http` packages on Planet to fix that bug. 22 | 23 | 2. Decided to use the new package manager instead, going forward. 24 | 25 | I suppose I'll update a Planet library if there's some really ugly 26 | bug. But when adding functionality to existing libraries, or for new 27 | ones, I plan to use _only_ the package manager. I'm not enthusiastic 28 | about juggling both. 29 | 30 | ## The new package manager 31 | 32 | I really like the new package manager, because overall it's a 33 | "lighter" and faster experience. I like the way it delegates to other 34 | systems such as GitHub (or whatever you prefer). 35 | 36 | My only two quibbles aren't very serious: 37 | 38 | 1. Making subdirs. A "package" is "one or more collections". The "or 39 | more" part means you need to move the collection into a subdir. So 40 | most of what you had in `project` gets moved into a new subdir, 41 | `project/project`. (What's left up top is basically just the 42 | `README.md`, your `.gitignore`, and an `info.rkt` for the package 43 | manager.) Although this is a small nuisance (and so is doing stuff 44 | like `cd ../../foo/foo` at the command line) it's not _that_ big a 45 | deal in my opinion. 46 | 47 | 2. One thing I like about Planet it that it hosts the Scribble 48 | documentation for a library, if any. Reading the doc for a lib--or 49 | indeed seeing if it has any doc at all--helps me decide how much to 50 | rely on a lib. The new package manager doesn't host docs like 51 | Planet, because it doesn't host files--it points to something like 52 | GitHub which has the files. However, you can do 53 | 54 | `scribble --markdown manual.scrbl` 55 | 56 | in the latest version of Racket to generate a Markdown format file. 57 | GitHub will display that `manual.md` file nicely. There's your 58 | hosted doc. With some caveats: It's one file, which may be unwieldy 59 | for huge libraries. You don't get the extensive hyperlinking you 60 | get in Scribble HTML output. Even so, it conveniently addresses the 61 | question, how to host documentation.[^1] 62 | 63 | ## Going forward 64 | 65 | The status quo situation with Planet and the new package manager is 66 | fine. Technically the package manager is still in beta. 67 | 68 | At some point, the existence of both will probably become a 69 | liability. Eventually it would be more beneficial to have a simple, 70 | clear message for folks who want to contribute as well as use 71 | libraries. 72 | 73 | [^1]: Of course you could also generate the usual HTML files and host 74 | them using GitHub Pages for your project. Personally, I find it awward 75 | to juggle the special `gh-pages` branch. But it might be worth it for 76 | some projects. 77 | -------------------------------------------------------------------------------- /src/posts/2013/04/roger-ebert-not-engines.md: -------------------------------------------------------------------------------- 1 | Title: Roger Ebert, not engines 2 | Date: 2013-04-05T11:14:08 3 | Tags: technology 4 | 5 | I've owned a couple TiVos over the years. The first thing I do on 6 | setup? Turn off the suggestions. 7 | 8 | The HBO series _Mind of the Married Man_ joked about this a decade 9 | ago: 10 | ["My TiVo Thinks I'm Gay"](https://www.youtube.com/watch?v=PoUJvAQg7KI). 11 | 12 |
13 | 14 | Although I love NetFlix I also find their recommendations 15 | underwhelming. 16 | 17 | About the only film recommendations I ever find valuable are from a 18 | friend or someone like 19 | [Roger Ebert](https://en.wikipedia.org/wiki/Roger_Ebert), who sadly 20 | just passed away yesterday. 21 | 22 | Why? Precisely because the recommendations aren't limited to what I 23 | _already_ like. 24 | 25 | 26 | 27 | This is the fundamental flaw with TiVo suggestions, NetFlix 28 | recommendations, 29 | [Facebook's fucking "Suggested Posts"](/2013/02/fucking-suggested-post-why-web-apps-matter.html), 30 | Twitter promoted tweets, Amazon's 31 | you-may-have-clicked-on-something-like-this, and so on. 32 | 33 | The problem isn't the recommendation engines, even though they 34 | [could and should be improved](https://en.wikipedia.org/wiki/Netflix_Prize). 35 | The problem is the premise of the whole model: That I want more of the 36 | same. That I want to live in a bubble. 37 | 38 | I'm not claiming to be a special snowflake, floating above the 39 | masses. I think _everyone_ wants some level of novelty. People may 40 | differ in the amount and frequency of novelty. But for most mentally 41 | healthy folks, not wanting to be bored out of our skulls is a fairly 42 | basic need. Speaking of Roger Ebert, here is one of the many awesome 43 | things he said: 44 | 45 | > What I believe is that all clear-minded people should remain two 46 | things throughout their lifetimes: curious and teachable. 47 | 48 | The other thing is, people go through phases. It may well be the case 49 | that, for a month, I want to inhale everything related to a given 50 | topic or genre. During that window, the recommendation engines might 51 | be more helpful. Which is why it's worthwhile to improve them. Just 52 | don't use them as the 24/7/365 model to predict what I'll like. 53 | 54 | I've realized this is another reason why I'm disappointed about Google 55 | giving the finger to Atom/RSS feeds. They seem to have the idea that 56 | they can choose better than me, what I'll want to read. It appears 57 | they want to join the giving-me-dubious-suggestions party. And they 58 | want me to join the party, too--and want it so badly they're taking 59 | away my LA privileges. 60 | 61 | Supposedly "you are what you eat". You're also what you read and 62 | watch. That's why I want feeds. I want to feed myself. I don't want to 63 | live in Google's (or Facebook's or whomever's) Monsanto-inspired 64 | monoculture. Not even in a supposedly "personalized" monoculture. 65 | 66 | "Oh, but you can still visit whatever web site you wish." Yeah, but as 67 | The Dead Kennedys said, give me convenience or give me death. The 68 | point isn't possibility, it's feasibility. With limited time, feeds 69 | are a way to keep up with a variety of sites. And although I don't 70 | _hate_ typing or pasting URIs in the browser bar, I'm in the 71 | minority. Plus even I don't want to type the URI when it means banging 72 | glass on a mobile device. 73 | 74 | So in a computing future where we're increasingly swiping and tappping 75 | -- or 76 | [glancing and blinking](https://en.wikipedia.org/wiki/Google_Glass) -- 77 | the question matters more, not less. What's the source of this stuff? 78 | Are we eating what we choose, or what's chosen for us using a model 79 | with a flawed premise about who we are? 80 | -------------------------------------------------------------------------------- /src/posts/2013/05/feeds2gmail.md: -------------------------------------------------------------------------------- 1 | Title: feeds2gmail 2 | Date: 2013-05-06T09:40:35 3 | Tags: Google, Racket, Atom, RSS 4 | 5 | Recently I wrote about my [my Google Reader successor][], using 6 | rss2email to push feeds to Gmail. 7 | 8 | In the month since, I was still running it on my laptop. To make it 9 | work best, it should run on a dedicated server. That way, it would 10 | push emails even if I'm away from my laptop, and I could read them on 11 | e.g. my phone. But before committing to setting this up on Amazon EC2, 12 | I wanted to be sure I liked the approach. 13 | 14 | 15 | 16 | Although I liked it, I missed having feeds in their own 17 | folders. Sometimes, I want to catch up with a specific blog, as 18 | opposed to a timeline mix of all of them. 19 | 20 | Also, I was continuing to have deliverability issues, including Gmail 21 | persisting in tagging some items as Spam. 22 | 23 | I stumbled across a 24 | [fork of rss2email that uses IMAP directly][]. Great idea! 25 | 26 | I had what I thought was an even better idea: Instead of doing this in 27 | Python, use Google Apps Script. This is a JavaScript environment that 28 | Google provides as its answer to macro languages in Microsoft Office 29 | products. It's surprising powerful. Among other things, you can 30 | schedule scripts to run. That would mean I wouldn't need a dedicated 31 | server to `cron` the feed updates. 32 | 33 | Also, I was excited for the opportunity to increase my experience with 34 | JavaScript. 35 | 36 | Unfortunately, although I got pretty far with this approach, I was 37 | completely blocked because [Google Apps Script can't access IMAP][]. And 38 | its own email APIs don't let you create a Gmail mail in a specific 39 | folder (i.e. with a specific Gmail label). 40 | 41 | I stewed over this for awhile. I looked at the rss2email source. And I 42 | decided, well shit, if I have to code this to run on my own server, I 43 | want to use Racket. 44 | 45 | So I came up with [feeds2gmail][]. It's similar to the 46 | [fork of rss2email that uses IMAP directly][], but written in 47 | Racket. 48 | 49 | One thing Racket doesn't have (that I could find) is a handy library 50 | to read feeds. Sometimes I've wanted a magic FFI to Python and/or Ruby 51 | libs. This was one of those times. 52 | 53 | I ended up coding Atom, RSS, and RDF feed parsing myself. It works for 54 | the feeds I've tried. I suspect this is the most fragile aspect that 55 | will need bug fixes. Partly because I may have made mistakes, and 56 | partly because the feeds have made mistakes. 57 | 58 | Meanwhile, it's working for me. I have an experience in Gmail that is 59 | _very_ close to Google Reader in the respects I care about. 60 | 61 | [my Google Reader successor]: http://www.greghendershott.com/2013/03/my-google-reader-successor.html 62 | [fork of rss2email that uses IMAP directly]: https://github.com/rcarmo/rss2email 63 | [Google Apps Script can't access IMAP]: http://stackoverflow.com/questions/16149899/how-to-create-a-gmail-message-with-a-specific-label 64 | [feeds2gmail]: https://github.com/greghendershott/feeds2gmail 65 | -------------------------------------------------------------------------------- /src/posts/2013/06/you-have-something-to-hide.md: -------------------------------------------------------------------------------- 1 | Title: You have something to hide 2 | Date: 2013-06-23T11:45:00 3 | Tags: politics 4 | 5 | You've committed felonies. 6 | 7 | Perhaps as many as [three per day](http://www.amazon.com/o/ASIN/1594035229). 8 | 9 | [No one is innocent](http://marginalrevolution.com/marginalrevolution/2013/06/no-one-is-innocent.html?): 10 | 11 | > If someone tracked you for a year are you confident that they would find no evidence of a crime? Remember, under the common law, _mens rea_, criminal intent, was a standard requirement for criminal prosecution but today that is typically no longer the case especially under federal criminal law. 12 | 13 | > Faced with the evidence of an non-intentional crime, most prosecutors, of course, would use their discretion and not threaten imprisonment. Evidence and discretion, however, are precisely the point. Today, no one is innocent and thus our freedom is maintained only by the high cost of evidence and the prosecutor’s discretion. 14 | 15 | 16 | 17 | This relates to a couple things in the news. One of course is NSA 18 | surveillance, which dramatically lowers the cost of evidence against 19 | you. Also relevant is a very recent SCOTUS decision, _Salinas 20 | v. Texas_, that 21 | [you no longer have the right to remain silent](http://www.slate.com/articles/news_and_politics/jurisprudence/2013/06/salinas_v_texas_right_to_remain_silent_supreme_court_right_to_remain_silent.html). 22 | 23 | You may have been taught as a child to follow the rules and everything 24 | would be OK. If you had a relatively privileged upbringing and 25 | reasonable parents and teachers, maybe that was mostly the case. 26 | 27 | But clearly you can't do that. It's impossible to know all the rules. 28 | If you did, you'd know it's impossible to follow all the rules. 29 | 30 | That's why matters of surveillance and evidence aren't just important 31 | for the people think of as "criminals". You, unfortunately, are one of 32 | the criminals. You just haven't been caught yet. 33 | 34 | --- 35 | 36 | Some have observed that the NSA is collecting roughly what Facebook or 37 | Google do. And they ask, what's the difference? 38 | 39 | One principled answer is that people consent to information collection 40 | by a company. In theory, they exchange this data for the consideration 41 | of using the service for free. The consent may not be deeply-informed, 42 | but it exists. With a company, there's a choice. With the government, 43 | there isn't.[^no choice] 44 | 45 | A practical answer is that Facebook and Google cannot legally use the 46 | information to confiscate and/or confine. If they did, it would be a 47 | form of blackmail and/or kidnapping. Whereas in the case of government 48 | it's not "blackmail", it's "prosecutorial discretion". 49 | 50 | And that's if you're relatively lucky enough to have a prosecutor --- 51 | to have specific charges filed in public. Even if the charges are 52 | inflated and multiplied (the reason why it's convenient for everyone 53 | to be guilty of multiple felonies), at least you get an opportunity to 54 | negotiate a deal or have your day in court. That is still a pretty 55 | horrible situation to be in, but like life in general, it's better 56 | than the alternative. 57 | 58 | ### Postscript 59 | 60 | - I am not a lawyer. 61 | 62 | - I have not been charged with a felony. This blog post is the result 63 | of reading and reflecting. 64 | 65 | - I want to thank everyone who reviewed pre-publication drafts of this 66 | post, including the fine, hard-working, dedicated members of 67 | intelligence services everywhere. 68 | 69 | --- 70 | 71 | [^no choice]: In the long run you may have some choice via voting. But until recently there has been no information and therefore no choice. Secrecy about near-term operational details is understandable, and with active oversight, fine. Secrecy about even the existence of operations, is a problem. 72 | -------------------------------------------------------------------------------- /src/posts/2013/07/skim-or-sink.md: -------------------------------------------------------------------------------- 1 | Title: Skim or sink 2 | Date: 2013-07-25T09:49:04 3 | Tags: software, Racket 4 | 5 | In my experience, the way to become a better programmer is 6 | to: 7 | 8 | - write lots of code 9 | - skim lots of reading material, and 10 | - defer answers until you have questions. 11 | 12 | To learn why you should skim this post, please read it 13 | carefully. 14 | 15 | 16 | 17 | On the [Racket][] mailing list, [someone asked][] the best 18 | way to learn advanced programming topics. Folks posted many 19 | great answers. I especially liked this advice from Neil Van 20 | Dyke: 21 | 22 | > You've read a lot already. I don't know how much practical programming 23 | > experience you have, but this reminds me to make a suggestion for anyone 24 | > reading this email who is learning programming and doing a lot of reading... 25 | > 26 | > If someone has access to a computer, then my suggestion at this point is 27 | > make sure that they are spending more time practicing programming than they 28 | > are spending on reading. 29 | > 30 | > By reading books and doing problem sets only, and reasoning about 31 | > programming in their head atop that, then someone might be able to 32 | > understand programming theory as a mathematician might. But if they want 33 | > intuition and insight into how to build and evolve sustainable systems in 34 | > the real world, then I'm not aware of any substitute for practical 35 | > experience in programming. 36 | 37 | I wanted to +1 that, so I started to write a reply. It grew 38 | long and I wandered a bit off topic. So I decided to make it 39 | into a blog post instead. 40 | 41 | --- 42 | 43 | What's the rule of thumb about becoming an "expert" -- it 44 | takes "10,000 hours" of practice, with gradually escalating 45 | difficulty? 46 | 47 | Does learning and study count as "practice"? 48 | 49 | Usually my most meaningful learning experience is when I 50 | have a question, first, and feel a personal need to learn 51 | the answer. The need might be practical and immediate. Or it 52 | might be a sense that I ought to know more about X at this 53 | stage. 54 | 55 | By contrast, with formal education and book learnin' I often 56 | get answers to questions I don't already have. As a result, 57 | although I learn something, I usually don't learn it as 58 | deeply. It feels like a solution in search of a problem. 59 | 60 | Ideally I'll go back and forth, alternating periods of 61 | hands-on experience with periods of reflection and 62 | study. Otherwise, if I get stuck too long in either mode, I 63 | plateau. Ten thousand hours of plateau doesn't help. 64 | 65 | Not to contradict the above, but sometimes I've found it 66 | beneficial to do a shallow survey above my current pay 67 | grade. I won't understand the material very well. I just 68 | dive in. I sort of squint, keep calm, and solider on. Some 69 | small residue lurks in my subconscious for months or 70 | years. Someday, when certain questions arise, something will 71 | click --- I'll be able to recognize that it's time to return 72 | and learn that topic for real. At which point I'm ready and 73 | motivated to learn it. 74 | 75 | I think this touches on a certain style of learning and 76 | memory that is an important survival skill for 77 | programmers. In the pre-internet days, I knew a programmer 78 | whose daughter sometimes watched him working. She was asked 79 | what her dad does. She explained, "He looks things up in 80 | books." 81 | 82 | Today the look-it-up technology has evolved. And the amount 83 | of information to be known has grown exponentially. But I 84 | think the coping strategy is the same. It's making use of 85 | the recognition half of memory (as opposed to the recall 86 | half). It's knowing _just_ enough about a topic, to be aware 87 | that it exists, and to recognize a situation where you 88 | should look up the details. 89 | 90 | That's why I recommend you skim. Surf. Browse. Whatever verb 91 | floats your boat. Because mainly you want to 92 | float. Something catch your eye? Sure, dive in. But not too 93 | long. Come back up for air, and keep moving along. 94 | 95 | If you're fresh out of school and accustomed to careful 96 | study, this will feel ... all _wrong_. Get over it. Adopt a 97 | slogan from (gasp) liberal arts colleges: This is about 98 | learning how to learn. And in the real world of programming, 99 | this is how you learn. In my experience. 100 | 101 | In conclusion, thank you for carefully reading this blog 102 | post. But next time, skim it. 103 | 104 | --- 105 | 106 | p.s. Mainly I'm talking about things like APIs, 107 | specifications, and languages: All you will ever need to 108 | know is impossible for a normal person to memorize and 109 | recall. Of course you could argue that the basic principles 110 | of computer science haven't changed much in 50+ years: 111 | Pioneering explorers like [Alonzo Church][], 112 | [Alan Turing][], [Alan Kay][][^1], [John McCarthy][], and 113 | [Douglas Engelbart][] mined that vein deeply. The rest of 114 | us? We smelt their ore and craft jewels. We prospect 115 | downstream, sifting for remaining nuggets. The core ideas 116 | are knowable, and worth more than a squinty skim. 117 | 118 | If you were to argue that, I'd agree. Even so, I'd argue 119 | that our understanding of those fundamentals will be deeper 120 | and richer --- less rote --- the greater our hands-on 121 | experience with gradually more-challenging programming. 122 | 123 | [Racket]: http://www.racket-lang.org/ 124 | [someone asked]: http://www.mail-archive.com/users@racket-lang.org/msg18688.html 125 | [Alan Turing]: https://en.wikipedia.org/wiki/Alan_Turing 126 | [John McCarthy]: https://en.wikipedia.org/wiki/John_McCarthy_%28computer_scientist%29 127 | [Douglas Engelbart]: https://en.wikipedia.org/wiki/Douglas_Engelbart 128 | [Alan Kay]: https://en.wikipedia.org/wiki/Alan_kay 129 | [Alonzo Church]: https://en.wikipedia.org/wiki/Alonzo_Church 130 | 131 | [^1]: a.k.a. The Three "Al"s 132 | -------------------------------------------------------------------------------- /src/posts/2013/08/spoiler-alert-give-google-power-of-attorney.md: -------------------------------------------------------------------------------- 1 | Title: Spoiler Alert: Just Give Google Power of Attorney 2 | Date: 2013-08-03T15:48:50 3 | Tags: Google, technology 4 | 5 | Robert Scoble's 6 | ["Google, the freaky line and why Moto X is a game-changer"][post] 7 | describes the significance of its always-on voice recognition. Under 8 | the heading, "the joy of context": 9 | 10 | > Moto X is just one in a string of products and services that will bring radical new functionality to users. Examples? Google Now, Google Glass, and the new Moto X phone that keeps the microphone open full-time. The Xbox One, coming this winter, will have a 3D sensor on it so sensitive it can see how fast your heart is beating just by watching your skin. 11 | > 12 | > These new contextual, sensor-based features are game changers and I’m hearing Google has a raft of other product announcements lined up that will turn on even more freaky features. Why? Because the more Google can get you to communicate with your phone, the more context it can slurp up. 13 | > 14 | > The more sensors it can turn on, or put on you, the more it can learn about your intent and your context. Today your phone doesn’t really know that you’re walking, running, skiing, shopping, driving, or biking, but in the future, Google will know that and will be able to build wild new kinds of systems that can serve you when doing each of those things. 15 | 16 | Naturally people have a variety of reactions for different reasons. 17 | You may find it yawningly predictable, or alarmingly freaky, or 18 | something in between. You might not mind Google slurping your data 19 | because you gave consent, but be creeped out by NSA contractors 20 | wrapping their lips around it uninvited. 21 | 22 | But at bottom what's the motivation behind this push for "context" and 23 | "intent"? 24 | 25 | 26 | 27 | Why does Google want such a complete picture of us? To increase our 28 | economic value to them. The better Google understands what we want --- 29 | and _when_ and _where_ we want it --- the more they can charge 30 | companies who want to sell us stuff. 31 | 32 | Simple example: If you sell pizza, you'll pay more to reach people who 33 | want pizza (a) now and (b) near you. Much more than you'll pay (if 34 | you're smart) to do traditional advertising. 35 | 36 | And to the extent this allows a company to form and sustain an ongoing 37 | relationship with you, it becomes even more valuable. 38 | 39 | Let's extrapolate, William Gibson style --- hold down the fast-forward 40 | button and skip to the end. (_SPOILER ALERT!_) 41 | 42 | If we keep moving the "freaky line", we quickly arrive at a basic 43 | question: Why even faff about with all this technology? Why not simply 44 | give Google power of attorney? That way they can decide on our behalf 45 | what we want, procure it for us, and deduct the money from our bank 46 | account. Done. Sorted. Issue closed with pushed commit. 47 | 48 | All this stuff with sensors, context, and intent is wonderful geekery. 49 | As technology for its own sake, it's awesomely awesome. But truly, 50 | it's a typically over-engineered solution to the problem of parting us 51 | from our money. 52 | 53 | Google, if you want to know us deeply --- know us, shall we say, 54 | biblically? Rather than passive-aggressively fap with sensors, be 55 | up-front and say you want to do the deed. Give us the power of 56 | attorney form and a blue ink pen: tell us to press hard, make three 57 | copies. What's that? You'd rather sample our DNA using the retractable 58 | swab in the phone microphone? Well...OK. You asked honestly. We won't 59 | judge you. 60 | 61 | 62 | --- 63 | 64 | In case it's not clear: 65 | 66 | 1. Am I being completely serious? No. But I'm not being completely un-serious, either. 67 | 68 | 2. Although Google isn't alone in this, they're a leader, so I think it's fair to use "Google" as an abbreviation. 69 | 70 | [post]: http://thenextweb.com/google/2013/08/02/google-the-freaky-line-and-why-moto-x-is-a-game-changer/ 71 | -------------------------------------------------------------------------------- /src/posts/2013/08/understanding-and-using-c-pointers.md: -------------------------------------------------------------------------------- 1 | Title: Understanding and Using C Pointers 2 | Date: 2013-08-27T09:14:03 3 | Tags: software 4 | 5 | ![A C programmer preparing to use pointers](https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Aviano_bomb_suit.jpg/220px-Aviano_bomb_suit.jpg) 6 | 7 | Someone[^someone] in my Google+ circles posted about 8 | [_Understanding and Using C Pointers_][book], a fairly recent (May, 9 | 2013) book by Richard Reese. 10 | 11 | 12 | 13 | Here's the actual summary from oreilly.com: 14 | 15 | > [![Understanding and Using C Pointers](https://akamaicovers.oreilly.com/images/0636920028000/cat.gif)](https://shop.oreilly.com/product/0636920028000.do) 16 | > 17 | > Improve your programming through a solid understanding of C pointers and memory management. With this practical book, you’ll learn how pointers provide the mechanism to dynamically manipulate memory, enhance support for data structures, and enable access to hardware. Author Richard Reese shows you how to use pointers with arrays, strings, structures, and functions, using memory models throughout the book. 18 | > 19 | > Difficult to master, pointers provide C with much flexibility and power—yet few resources are dedicated to this data type. This comprehensive book has the information you need, whether you’re a beginner or an experienced C or C++ programmer or developer. 20 | 21 | On the one hand, it makes me feel old to realize how many programmers 22 | have grown up not needing to learn this. On the other hand, it's neat 23 | that people still want to learn it and appreciate the value of knowing 24 | it. 25 | 26 | Even when using a higher-level language, there will be times when it's 27 | helpful to have a sense of what's actually going on at the lower 28 | levels. Programming bigger systems involves various levels of 29 | abstraction. Usually the trick is pretending that you _don't_ know how 30 | the lower levels work---avoiding the temptation to use details about 31 | lower levels and break the barrier of abstraction. However when things 32 | aren't performant, it helps to use your X-ray vision and look through 33 | the barriers. 34 | 35 | My first (and only) two CS classes were about Pascal and 36 | assembly. Those turned out to be perfect preparation for teaching 37 | myself C, which in some ways is a synthesis of both. 38 | 39 | --- 40 | 41 | One issue with C pointers is that people need to learn both new 42 | concepts and new notation. Anyone who doesn't get a headache the first 43 | time they see the notation for a function pointer is a weird space 44 | alien. Combine that notation with learning the idea of "a pointer to a 45 | function", and it's going to be difficult. 46 | 47 | I can vaguely recall that experience learning C. Much more recently I 48 | remember examples with, say, continuations and macros in Scheme and 49 | Racket. 50 | 51 | Maybe it would help to provide some training wheels that use "plain 52 | English" instead of "weird" or "concise" notation. Or maybe not. Maybe 53 | it's just hard and you need to slog through it. 54 | 55 | --- 56 | 57 | Anyway, here are my _imaginary_ review quotes for _any_ book about C 58 | pointers: 59 | 60 | - "Casts the subject in a whole new light." 61 | 62 | - "Thoroughly researched; references counted meticulously." 63 | 64 | - "Helped me get a handle on pointers, albeit indirectly." (a Windows 3.1[^win] reviewer) 65 | 66 | - "Great pointers to using pointers, albeit indirectly". (a Mac OS 9[^mac] reviewer) 67 | 68 | - "You will learn that pointers that dangle are bad, regardless of the angle." 69 | 70 | - "This comprehensive reference points to various arguments, some positive, some negative, some null and void." 71 | 72 | - "Didn't like it. Tossed in garbage. Hope collected soon." 73 | 74 | Again, these are _pretend_ reviews. The _real_ reviews are very 75 | positive and it looks like an excellent book that you should 76 | [buy][book][^link] immediately. 77 | 78 | [book]: https://shop.oreilly.com/product/0636920028000.do 79 | 80 | --- 81 | 82 | [^someone]: Although I want to give them credit, they shared the post privately so I don't want to presume to name them. 83 | 84 | [^win]: For movable allocated memory, older versions of Windows used opaque `HANDLE`s you could cash in for real pointers. 85 | 86 | [^mac]: Whereas Mac OS used pointers to pointers, if I recall correctly. 87 | 88 | [^link]: Not an affiliate link. 89 | -------------------------------------------------------------------------------- /src/posts/2013/08/using-call-input-url.md: -------------------------------------------------------------------------------- 1 | Title: Using call/input-url 2 | Date: 2013-08-03T22:28:56 3 | Tags: Racket, racket-cookbook 4 | 5 | When I learned Racket, one of the first things I wanted to try was 6 | doing HTTP requests. And Racket's `net/url` module is great. 7 | 8 | Racket was the first real Lisp/Scheme family language I ever learned. 9 | As a result I was focused on building blocks like ports, and assuming 10 | I would need to open and close them directly all the time. At that 11 | early stage, I also didn't really appreciate the value of higher-order 12 | functions. So I overlooked the value of `call/input-url`[racket]. I 13 | sometimes see other folks do the same, and wanted to write this short 14 | blog post. 15 | 16 | 17 | 18 | Let's say we want a function to take a `url?`, make an HTTP `GET` 19 | request, and return the response as a `string?`. For lack of a better 20 | name, we'll call our function `fetch`. 21 | 22 | First we need to `require` the `net/url` module: 23 | 24 | ```racket 25 | (require net/url) 26 | ``` 27 | 28 | I won't keep repeating that in the various examples that follow. 29 | 30 | Our first cut at `fetch` uses all the basic, obvious functions: 31 | 32 | ```racket 33 | (define (fetch url) 34 | (define in (get-pure-port url)) 35 | (define out (open-output-string)) 36 | (copy-port in out) 37 | (get-output-string out)) 38 | ``` 39 | 40 | We try using it like this: 41 | 42 | ```racket 43 | (fetch (string->url "http://www.racket-lang.org/")) 44 | ``` 45 | 46 | And it gives output like this: 47 | 48 | ```racket 49 | "\nstring`[racket], which does just that. So we can simplify: 59 | 60 | ```racket 61 | (define (fetch url) 62 | (define in (get-pure-port url)) 63 | (port->string in)) 64 | ``` 65 | 66 | Much better. Unfortunately, there's a problem. We don't close the 67 | `input-port?` returned from `get-pure-port`. Let's do that: 68 | 69 | ```racket 70 | (define (fetch url) 71 | (define in (get-pure-port url)) 72 | (begin0 73 | (port->string in) 74 | (close-input-port in))) 75 | ``` 76 | 77 | If you're unfamiliar with `begin0`[racket], it's like `begin`[racket] 78 | except it returns the value of the _first_ expression instead of the 79 | last one. `begin0`[racket] fits the pattern where you need to return a 80 | value from a resource then close/release the resource. 81 | 82 | OK, but, what if an exception were thrown sometime before we reached 83 | `close-input-port?`, for example some runtime error inside 84 | `port->string?` on line 4? The port would remain open. 85 | 86 | Well, we could wrap this in an exception handler: 87 | 88 | ```racket 89 | (define (fetch url) 90 | (define in (get-pure-port url)) 91 | (with-handlers ([exn:fail? (lambda (exn) 92 | (close-input-port in) 93 | (raise exn))]) 94 | (begin0 95 | (port->string in) 96 | (close-input-port in)))) 97 | ``` 98 | 99 | But this is really verbose. We're writing a lot of boilerplate code 100 | to handle exceptions, compared to the task we care about. 101 | 102 | Fortunately, Racket provides `call/input-url`[racket]. It takes three 103 | arguments: a `url?`, a connection function such as `get-pure-port`, 104 | and a "handler" function to do something with the opened 105 | `input-port?`. It guarantees that the input port will be closed, even 106 | if an exception is thrown. 107 | 108 | Using that: 109 | 110 | ```racket 111 | (define (fetch url) 112 | (call/input-url url 113 | get-pure-port 114 | (lambda (in) 115 | (port->string in)))) 116 | ``` 117 | 118 | The handler function takes an `input-port?` and returns `any` --- 119 | whatever you need it to -- which in turn is what `call/input-url` 120 | returns. 121 | 122 | Hey wait a second. I notice that `port->string` is just such a 123 | function: `(input-port? . -> . any)`. We don't need to wrap that in a 124 | `lambda`. We can supply it directly: 125 | 126 | ```racket 127 | (define (fetch url) 128 | (call/input-url url 129 | get-pure-port 130 | port->string)) 131 | ``` 132 | 133 | And that's the final version. This is a nice example of how using 134 | higher-order functions can result in elegant code with a clean 135 | separation of concerns. 136 | 137 | tl;dr: When you need to do an HTTP `GET` in Racket, you probably want 138 | to use `call/input-url`[racket]. It ensures the HTTP input port will 139 | be closed, and it nudges you into using higher-order functions. 140 | -------------------------------------------------------------------------------- /src/posts/2013/10/interview-and-racketcon-talk.md: -------------------------------------------------------------------------------- 1 | Title: Interview and RacketCon talk 2 | Date: 2013-10-10T19:03:12 3 | Tags: software 4 | 5 | I haven't posted here in awhile because I've been busy with a variety 6 | of things in September, including an interview for IEEE _Computer_ 7 | magazine and a talk about [Frog][] at RacketCon. And I'm writing this 8 | in Japan. 9 | 10 | 11 | 12 | ## Interview 13 | 14 | Brian Gaff interviews me on the topic, "So You Want to Start a 15 | Software Company?". The written interview appears in the October 2013 16 | issue of IEEE _Computer_ magazine. Although I don't have a URL to that 17 | yet[^1], Brian and I also did a follow-up podcast you can listen to 18 | [here][podcast]. 19 | 20 | Although I think the interview is mostly common-sense stuff, it might 21 | be useful to someone who is starting a company. 22 | 23 | ## RacketCon 24 | 25 | At RacketCon I gave a 10 minute talk about [Frog][]. The video is 26 | [here][video]. This was the third RacketCon and I think by far the 27 | best one. There was a really nice variety of talks, ranging from 28 | practical to PL/academic and everything in between. 29 | 30 | I also stopped by ICFP since it was held in Boston this year. Most of 31 | the presentations were waaaay over my head. I was one of the very few 32 | not associated with academia or corporate research. Although it 33 | wouldn't have been worthwhile (for me) to travel for it, it was 34 | interesting to see firsthand. 35 | 36 | ## Japan 37 | 38 | I'm typing this blog post in Japan. Two aspects to that. 39 | 40 | The trip to Japan is the first one I've done purely for fun, in many 41 | years. I had to travel here three or four times a year for work, for 42 | many years. But I couldn't take many --- or any --- free days on most 43 | of those trips. So I thought it would be fun to return as a 44 | "tourist". Or maybe I miss the extreme jet lag, in some variation of 45 | Stockholm Syndrome. Someone once said, "jet lag is the best drug". 46 | Although I'm not qualified to verify that claim empirically, there is 47 | something surreal about a 13 hour shift. On the other hand it also has 48 | its soul-sucking moments, as _Lost in Translation_ portrayed so well. 49 | 50 | The other aspect is that I'm typing this SSH-ing into a VPS, using a 51 | ChromeBook Pixel as the client. This is a bit of an experiment, which 52 | I'll write about more if it works well. 53 | 54 | 55 | 56 | [Frog]: https://github.com/greghendershott/frog 57 | [podcast]: https://www.youtube.com/watch?v=OlZ1pKcW8d4 58 | [video]: http://www.youtube.com/watch?v=5q-NZNGV0sY&list=PLXr4KViVC0qLyXpinlARzSDWaQTCzaGw3&index=5 59 | 60 | 61 | [^1]: I have a copy of the text and I'm entitled to repost it, which I'll do here if it gets stuck indefinitely behind a paywall. 62 | -------------------------------------------------------------------------------- /src/posts/2013/12/racket-package-management.md: -------------------------------------------------------------------------------- 1 | Title: Racket package management 2 | Date: 2013-12-02T00:00:00 3 | Tags: Racket 4 | 5 | 6 | Racket's new package manager is great. It debuted with Racket 7 | 5.3.5. Although officially still in beta, it was already good enough 8 | to use for real projects. Racket developers wanted people to use it 9 | for real projects, to get the experience needed to make it even 10 | better. 11 | 12 | Over many months, the Git `HEAD` version of Racket --- what would 13 | become Racket 6 --- gradually introduced a few new and changed features 14 | for package management. 15 | 16 | However, you might not want to use the newer features, yet. Not if you 17 | want your package to be usable by people still using Racket 5.3.5 or 18 | 5.3.6 --- or usable by other packages that wish to support such 19 | people. 20 | 21 | Fortunately, the older features are still supported in Racket 6, and 22 | it's not very difficult or inconvenient to use them. You just need to 23 | know what they are. 24 | 25 | 26 | 27 | # Why bother? 28 | 29 | Someday it will be easy to say, "Hey, my package only supports Racket 30 | 6 or newer". As I type this, it would be a bit strange to say that, 31 | because Racket 6 isn't released yet. Even after Racket 6 ships, 32 | there's an argument to be made for supporting folks still using the 33 | previous version of Racket. 34 | 35 | Admittedly each package has different requirements. Ultimately it's 36 | your decision. 37 | 38 | # How to deploy a package that works with Racket 5.3.x 39 | 40 | There are two areas to be aware of: Your `info.rkt`, and the package 41 | source you list on . Let's look at each. 42 | 43 | ## info.rkt 44 | 45 | Here's an example of my `info.rkt` file for Frog, which is compatible 46 | with 5.3.5, 5.3.6, and 6. 47 | 48 | ```racket 49 | #lang setup/infotab 50 | (define version "0.11") 51 | (define collection 'multi) 52 | (define deps '(("markdown" "0.9") 53 | "rackjure" 54 | "find-parent-dir")) 55 | ``` 56 | 57 | The key points: 58 | 59 | 1. Use `#lang setup/infotab` (_not_ `#lang info`). 60 | 61 | 2. Use `(define collection 'multi)` (_not_ a single collection package). 62 | 63 | This means you need to put your collection in a subdirectory of 64 | your package's root directory. If your package is named `foo`, 65 | you'll have: 66 | 67 | ``` 68 | foo/ 69 | .gitignore 70 | info.rkt # for your package 71 | README.md 72 | foo/ 73 | info.rkt # for the collection (if needed) 74 | main.rkt 75 | ... other source files ... 76 | ``` 77 | 78 | Is this a bit of a pain in the butt? Yes. In fact I was one of the 79 | people who asked to have single-collection packages as the new, 80 | simpler default. I'm happy it was added. I just can't use it, 81 | quite yet. Not until I'm ready to drop support for older versions 82 | of Racket. 83 | 84 | 3. To state a specific version of a package you depend on, use 85 | `("package" "version")` (_not_ the `#:version` keyword). 86 | 87 | Unfortunately even using the old form, `raco pkg update 88 | --auto-deps ` in 5.3.x won't work for your package. 89 | It will error when trying to remove a package it thinks is called 90 | `name version` instead of just `name`. As a result, you need to 91 | tell users that, to update, they should use `raco pkg remove 92 | ` followed by `raco pkg install `. The 93 | install _will_ handle and honor the version requirement, which is 94 | the most important thing you want to work. 95 | 96 | Remember, if you _were_ to use `#:version`, 5.3.x users couldn't 97 | even install your package in the first place. Instead, by sticking 98 | with the old way of specifying the dependency version, at least 99 | people can install your package. Then to update it, they simply 100 | need to remove then install again. It's two steps instead of one 101 | "update". Not a huge inconvenience. 102 | 103 | ## Package source 104 | 105 | When you list your package on , you should 106 | use the old form for GitHub URLs: 107 | 108 | github://github.com///[/] 109 | 110 | For example: 111 | 112 | github://github.com/greghendershott/frog/master 113 | 114 | Racket 5.3.5 and 5.3.6 do _not_ recognize the new `git:` variant. 115 | 116 | # Conclusion 117 | 118 | As you can see, it's not difficult to make a package compatible with 119 | the `raco pkg` in 5.3.5 and 5.3.6, as well as the upcoming version 6 120 | of Racket. Once you know how, it's easy. 121 | 122 | If you have any additional tips, or corrections, please comment below. 123 | -------------------------------------------------------------------------------- /src/posts/2014/01/syntax-loc-and-unit-tests.md: -------------------------------------------------------------------------------- 1 | Title: Using syntax/loc and unit test macros 2 | Date: 2014-01-31T18:21:56 3 | Tags: Racket, racket-cookbook, macros 4 | 5 | In my [previous post], I wrote about a nuance with `syntax/loc`, using 6 | the example of a macro that both `define`s and `provide`s a 7 | function. But why don't I back up, and look at a simpler example of 8 | why you'd want to use `syntax/loc`. The example is a simple macro you 9 | might often find yourself wanting, to reduce the tedium of writing 10 | unit test cases. 11 | 12 | [previous post]: http://www.greghendershott.com/2014/01/using-syntax-loc.html 13 | 14 | 15 | 16 | Let's say we have a function `my-function-that-increments`, with some 17 | unit tests. 18 | 19 | ```racket 20 | #lang racket 21 | 22 | (require rackunit) 23 | 24 | (define (my-function-that-increments x) 25 | (add1 x)) 26 | 27 | (check-equal? (my-function-that-increments 0) 28 | 1) 29 | (check-equal? (my-function-that-increments 1) 30 | 1) 31 | ``` 32 | 33 | The last test correctly fails[^1], and the rackunit error message points 34 | to the `check-equal?` on line 10: 35 | 36 | [^1]: For some definition of "correctly". 37 | 38 | ```racket 39 | -------------------- 40 | FAILURE 41 | actual: 2 42 | expected: 1 43 | name: check-equal? 44 | location: (# 10 0 151 62) 45 | expression: (check-equal? (my-function-that-increments 1) 1) 46 | 47 | ; Check failure 48 | -------------------- 49 | ``` 50 | 51 | Great. But let's say we have dozens of tests, and typing all the 52 | `check-equal?` and `my-function-that-increments` is wearisome. We 53 | think, "Aha, I can write a macro!" So we write: 54 | 55 | ```racket 56 | #lang racket 57 | 58 | (require rackunit) 59 | 60 | (define (my-function-that-increments x) 61 | (add1 x)) 62 | 63 | (define-syntax (chk stx) 64 | (syntax-case stx () 65 | [(_ input expected) 66 | #'(check-equal? (my-function-that-increments input) 67 | expected)])) 68 | 69 | (chk 1 0) 70 | (chk 2 1) 71 | ``` 72 | 73 | Much less tedious. Nice. 74 | 75 | But...hey wait. The rackunit error message points to line 11: 76 | 77 | ```racket 78 | -------------------- 79 | FAILURE 80 | actual: 3 81 | expected: 1 82 | name: check-equal? 83 | location: (# 11 7 166 80) 84 | expression: (check-equal? (my-function-that-increments 2) 1) 85 | 86 | ; Check failure 87 | -------------------- 88 | ``` 89 | 90 | Line 11 isn't where the failing `(chk 2 1)` is. It's _inside our 91 | macro_. Gah. We wanted to write this macro because we have dozens of 92 | unit tests...but we can't see which one of them failed? This whole 93 | idea of using a macro seems to have backfired. 94 | 95 | Fortunately, this is where `syntax/loc` helps. It lets us specify the 96 | source location for the syntax returned from our macro. 97 | 98 | The macro above uses `#'`, which is shorthand for `syntax`. First 99 | let's rewrite the macro using `syntax`: 100 | 101 | ```racket 102 | (define-syntax (chk stx) 103 | (syntax-case stx () 104 | [(_ input expected) 105 | (syntax (check-equal? (my-function-that-increments input) 106 | expected))])) 107 | ``` 108 | 109 | Of course this still has the source location problem. But, we change 110 | `syntax` to `syntax/loc`, supplying it the `stx` given to our macro: 111 | 112 | ```racket 113 | (define-syntax (chk stx) 114 | (syntax-case stx () 115 | [(_ input expected) 116 | (syntax/loc stx 117 | (check-equal? (my-function-that-increments input) 118 | expected))])) 119 | ``` 120 | 121 | Here's the full new sample, so that the line numbers work for this 122 | blog post: 123 | 124 | ```racket 125 | 126 | #lang racket 127 | 128 | (require rackunit) 129 | 130 | (define (my-function-that-increments x) 131 | (add1 x)) 132 | 133 | (define-syntax (chk stx) 134 | (syntax-case stx () 135 | [(_ input expected) 136 | (syntax/loc stx 137 | (check-equal? (my-function-that-increments input) 138 | expected))])) 139 | 140 | (chk 1 0) 141 | (chk 2 1) 142 | ``` 143 | 144 | And now rackunit has the correct source location to report, line 16: 145 | 146 | ```racket 147 | -------------------- 148 | FAILURE 149 | actual: 3 150 | expected: 1 151 | name: check-equal? 152 | location: (# 16 0 281 9) 153 | expression: (check-equal? (my-function-that-increments 2) 1) 154 | 155 | ; Check failure 156 | -------------------- 157 | ``` 158 | 159 | Hopefully this shows how `syntax/loc` can help with the sort of 160 | "casual" macro you might frequently want to write. 161 | -------------------------------------------------------------------------------- /src/posts/2014/01/using-syntax-loc.md: -------------------------------------------------------------------------------- 1 | Title: Using syntax/loc 2 | Date: 2014-01-23T08:16:16 3 | Tags: Racket, macros 4 | 5 | There's a nuance to [`syntax/loc`]. The documentation says, emphasis mine: 6 | 7 | > Like `syntax`, except that the **immediate** resulting syntax object takes its source-location information from the result of _stx-expr_ (which must produce a syntax object), unless the template is just a pattern variable, or both the source and position of _stx-expr_ are `#f`. 8 | 9 | What does "immediate" mean here? 10 | 11 | 12 | 13 | Let's back up. Say that we[^1] are writing a macro to both `define` and 14 | `provide` a function: 15 | 16 | ```racket 17 | #lang racket 18 | 19 | (require (for-syntax syntax/parse)) 20 | 21 | (define-syntax (defp stx) 22 | (syntax-parse stx 23 | [(_ (id:id arg:expr ...) body:expr ...+) 24 | #'(begin 25 | (define (id arg ...) 26 | body ...) 27 | (provide id))])) 28 | 29 | (defp (f x) 30 | (/ 1 x)) 31 | 32 | (f 1) 33 | ;; => 1 34 | 35 | ``` 36 | 37 | A macro must return one syntax object, but we want to return both a 38 | `define` and a `provide`. So we do the usual thing: We wrap them up in 39 | a `begin`. 40 | 41 | Everything fine so far. 42 | 43 | Now let's say we call `f` and it causes a runtime error: 44 | 45 | ```racket 46 | #lang racket 47 | 48 | (require (for-syntax syntax/parse)) 49 | 50 | (define-syntax (defp stx) 51 | (syntax-parse stx 52 | [(_ (id:id arg:expr ...) body:expr ...+) 53 | #'(begin 54 | (define (id arg ...) 55 | body ...) 56 | (provide id))])) 57 | 58 | (defp (f x) 59 | (/ 1 x)) 60 | 61 | (f 1) 62 | ; 1 63 | 64 | (f 0) 65 | ; /: division by zero 66 | ; Context: 67 | ; /tmp/derp.rkt:9:9 f 68 | ; derp [running body] 69 | ``` 70 | 71 | See the problem? The error message points to line 9 -- inside the 72 | `defp` macro. That's not helpful. We want it to say line 13 -- where 73 | we use `defp` to define the `f` function. 74 | 75 | Fortunately, we've heard of [`syntax/loc`]. Instead of specifying the 76 | macro template using `#'` a.k.a. `syntax`, we can use 77 | `syntax/loc`. Easy. So we update our macro: 78 | 79 | ```racket 80 | (define-syntax (defp stx) 81 | (syntax-parse stx 82 | [(_ (id:id arg:expr ...) body:expr ...+) 83 | (syntax/loc stx ;; <-- new 84 | (begin 85 | (define (id arg ...) 86 | body ...) 87 | (provide id)))])) 88 | ``` 89 | 90 | But that _still_ doesn't work. The error message still points to the 91 | macro. 92 | 93 | Huh. So we try other values, like `(syntax/loc id . . .)`. But still 94 | no joy. 95 | 96 | It turns out this is where that word "immediate" matters: `syntax/loc` 97 | is setting the source location for the _entire_ `(begin . . . )` 98 | syntax object -- but _not_ necessarily the pieces _inside_ it. That's 99 | why the `define` isn't getting the source location we're supplying. 100 | 101 | The solution? Use `syntax/loc` directly on the `define` piece: 102 | 103 | ```racket 104 | #lang racket 105 | 106 | (require (for-syntax syntax/parse)) 107 | 108 | (define-syntax (defp stx) 109 | (syntax-parse stx 110 | [(_ (id:id arg:expr ...) body:expr ...+) 111 | #`(begin 112 | #,(syntax/loc stx ;; <-- new 113 | (define (id arg ...) 114 | body ...)) 115 | (provide id))])) 116 | 117 | (defp (f x) 118 | (/ 1 x)) 119 | 120 | (f 1) 121 | ; 1 122 | 123 | (f 0) 124 | ; /: division by zero 125 | ; Context: 126 | ; /tmp/derp.rkt:14:0 f 127 | ; derp [running body] 128 | ``` 129 | 130 | And now the error message points to `f` on line 14. `\o/` 131 | 132 | --- 133 | 134 | There's also a `quasisyntax/loc`. 135 | 136 | Cheat sheet: 137 | 138 | 139 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 |
140 | DefaultSource location
Plain#' a.k.a. syntaxsyntax/loc
Quasi#` a.k.a. quasisyntaxquasisyntax/loc
154 | 155 | --- 156 | 157 | To anticipate a comment: Yes, I know. I ought to add this to 158 | [Fear of Macros], too. 159 | 160 | [^1]: The "we" in this blog post was "me" yesterday. Big thanks to Eric 161 | Dobson for pointing this out on #racket IRC. 162 | 163 | [`syntax/loc`]: http://docs.racket-lang.org/reference/stx-patterns.html#%28form._%28%28lib._racket%2Fprivate%2Fstxcase-scheme..rkt%29._syntax%2Floc%29%29 164 | 165 | [Fear of Macros]: http://www.greghendershott.com/2013/01/fear-of-macros.html 166 | -------------------------------------------------------------------------------- /src/posts/2014/06/destructuring-lists-with-match.md: -------------------------------------------------------------------------------- 1 | Title: Destructuring lists with match 2 | Date: 2014-06-22T22:20:56 3 | Tags: Racket 4 | 5 | Let's say you need to destructure a list with `match`, using a pattern 6 | that specifies a "rest" or "more" element. Be careful. You probably 7 | want to use `list*` not `list`. 8 | 9 | 10 | 11 | > Update 2014-06-25: Sam Tobin-Hochstadt pushed a [commit] that 12 | > changes this. Fast! Thanks! So if you're building from HEAD or using 13 | > a nightly build, this is now moot. However if you're writing code 14 | > for a library that may be used with Racket 6.0.1 or older, it 15 | > remains relevant. 16 | 17 | [commit]: https://github.com/plt/racket/commit/dcb5b09a14df4afd729a50b64c67c3c35f716da9 18 | 19 | ## The long version 20 | 21 | I answered a [question on Stack Overflow][question]. Basically, it was 22 | about how to select every 1st and 4th element from a list, repeatedly. 23 | My answer was equivalent to what we shall name `slow`: 24 | 25 | [question]: http://stackoverflow.com/questions/24354824/remove-elements-in-a-list-using-a-pattern 26 | 27 | ```racket 28 | (define (slow xs) 29 | (match xs 30 | [(list a _ _ d more ...) (list* a d (slow more))] 31 | [(cons a _) (list a)] 32 | [_ (list)])) 33 | ``` 34 | 35 | It passed all these tests: 36 | 37 | ```racket 38 | (define (test f) 39 | (local-require rackunit) 40 | ;; Your example: 41 | (check-equal? (f '(0 1 2 3 4 5 6 7 8 9 10)) '(0 3 4 7 8)) 42 | ;; Other tests: 43 | (check-equal? (f '()) '()) 44 | (check-equal? (f '(0)) '(0)) 45 | (check-equal? (f '(0 1)) '(0)) 46 | (check-equal? (f '(0 1 2)) '(0)) 47 | (check-equal? (f '(0 1 2 3)) '(0 3)) 48 | (check-equal? (f '(0 1 2 3 4)) '(0 3 4))) 49 | ``` 50 | 51 | Later, I saw that uselpa posted a generalized, elegant 52 | [answer](http://stackoverflow.com/a/24355921/343414) using `for/fold` 53 | and `in-cycle`. 54 | 55 | ```racket 56 | (define (pattern-filter pat lst) 57 | (reverse 58 | (for/fold ([res null]) 59 | ([p (in-cycle pat)] 60 | [e (in-list lst)]) 61 | (if p (cons e res) res)))) 62 | ``` 63 | 64 | At which point a few things happened. 65 | 66 | I was impressed by that answer. Wished I'd answered that way. Consoled 67 | myself, well, at least my bespoke pattern-specific version is probably 68 | somewhat _faster_. Right? 69 | 70 | After feeling smug for a moment, I had a nagging feeling. Well shit. I 71 | should measure. See how much faster. 72 | 73 | I measured. I was horrified how wrong I was. 74 | 75 | ```racket 76 | (define bench 77 | (let ([xs (build-list 10000 values)]) 78 | (λ (f) 79 | (for ([i 3]) (collect-garbage)) 80 | (printf "Timing ~a ... " (object-name f)) 81 | (time (void (f xs)))))) 82 | 83 | (bench slow) 84 | ;; Timing slow ... cpu time: 256 real time: 266 gc time: 46 85 | ``` 86 | 87 | Whereas uselpa's `partition-filter` version was: 88 | 89 | ```racket 90 | ;; Timing partition-filter ... cpu time: 3 real time: 4 gc time: 0 91 | ``` 92 | 93 | WAT. 94 | 95 | I rewrote it using `cond`, `car`, etc. -- i.e. what I imagined `match` 96 | would expand to. That _was_ as fast as I'd expected. 97 | 98 | I scratched my head for a bit. On a hunch I tried a slightly different 99 | `match` pattern, which we'll name `fast`: 100 | 101 | ```racket 102 | (define (fast xs) 103 | (match xs 104 | [(list* a _ _ d more) (list* a d (fast more))] 105 | [(cons a _) (list a)] 106 | [_ (list)])) 107 | 108 | (bench slow) 109 | ;; Timing slow ... cpu time: 256 real time: 266 gc time: 46 110 | (bench fast) 111 | ;; Timing fast ... cpu time: 1 real time: 0 gc time: 0 112 | ``` 113 | 114 | The difference?[^1] 115 | 116 | ```racket 117 | (list this more ...) ;; BAD 118 | (list* this more) ;; GOOD 119 | ``` 120 | 121 | All along I'd believed they're equivalent. They're not.[^quasi] 122 | 123 | In fact, if you _double_ the test list length (from 10,000 to 20,000) 124 | the time _quadruples_ (from 256 to about 1000). 125 | 126 | ## Why? 127 | 128 | As Jay McCarthy explained to me on IRC, `(list this more ...)` needs 129 | to test that the input is a `list?`, and `cons` up the elements to 130 | bind to `more`. Whereas `(list* this more)` doesn't care if the input 131 | is a list -- whatever it is just gets bound to `more`. 132 | 133 | The `list*` match pattern is like the `list*` function: 134 | 135 | ```racket 136 | (list* 1 2 (list 3 4 5)) ;; '(1 2 3 4 5) 137 | (list* 1 2 3) ;; '(1 2 . 3) 138 | (list* 1 2 "whatever") ;; '(1 2 . "whatever") 139 | ``` 140 | 141 | --- 142 | 143 | ## Final thoughts 144 | 145 | 1. I should write a blog post about this (done). 146 | 147 | 2. I should submit a PR with a "Tip!" for the `match` documentation, 148 | in case this is non-obvious to anyone else. 149 | 150 | 3. I wonder if `match` could optimize the special case of `(list this 151 | more ...)` to be `(list* this more)`. Maybe there are subtleties 152 | (or not-so-subtleties) I'm overlooking. Update 2014-06-25: [done]. 153 | 154 | 4. Although my _corrected_ version _was_ indeed faster than 155 | `pattern-filter`, it's almost immeasurably so on a list of 10,000 156 | items. At 100,000 it _starts_ to matter. In many applications, it 157 | would be N/A. In general, I think uselpa's answer is best. 158 | 159 | 160 | [^1]: Also I changed the second pattern from `(list a _ ...)` to 161 | `(cons a _)`, although that test is only hit at the end so it hardly 162 | effects the run time in this case. 163 | 164 | [^quasi]: By the way, same issue with quasiquote patterns, which for 165 | instance I frequently use to destruct x-expressions. Instead of `` 166 | `(,this ,that ,more ...) `` use `` `(,this ,that . ,more) ``. 167 | -------------------------------------------------------------------------------- /src/posts/2014/06/fallback-when-required-function-not-available.md: -------------------------------------------------------------------------------- 1 | Title: Fallback when required function not available 2 | Date: 2014-06-02T09:27:17 3 | Tags: Racket, racket-cookbook 4 | 5 | Let's say we want to use `find-collects-dir`, which was added 6 | in Racket 6.0. We get a bug report from someone using Racket 5.3.6. 7 | 8 | To fix this, we can `dynamic-require` the desired function; when it 9 | doesn't exist, we can use our own fallback implementation.[^1] 10 | 11 | 12 | 13 | The basic recipe: 14 | 15 | ```racket 16 | (define (our-fallback ....) 17 | ....) 18 | 19 | (define desired-function 20 | (dynamic-require 'collection/path 'desired-function 21 | (thunk our-fallback))) 22 | ``` 23 | 24 | Here `(thunk x)` is just shorthand for `(lambda () x)`. 25 | 26 | A simple but full example: 27 | 28 | ```racket 29 | #lang racket/base 30 | 31 | ;; find-collects-dir was added in Racket 6.0. 32 | (provide find-collects-dir) 33 | 34 | (require racket/list 35 | racket/function 36 | racket/path) 37 | 38 | (define (our-find-collects-dir) 39 | (apply build-path 40 | (reverse (cdr (reverse (explode-path (collection-path "racket"))))))) 41 | 42 | (define find-collects-dir 43 | (dynamic-require 'setup/dirs 'find-collects-dir 44 | (thunk our-find-collects-dir))) 45 | ``` 46 | 47 | Let's say this is in a file `find-collects-dir.rkt`. Then elsewhere in 48 | our program instead of `(require setup/dirs)` we do `(require 49 | "find-collects-dir.rkt")`. 50 | 51 | Now our program uses Racket's `find-collects-dir` when running on 52 | Racket 6.0 or newer, otherwise it falls back to using our own 53 | implementation. 54 | 55 | Season with parsley. Serves many. 56 | 57 | [^1]: Of course another approach is to _only_ use our own implementation, even in newer versions of Racket. Sometimes that would be simpler. However the "official" function might someday change in beneficial ways. Also, someday we might decide to stop supporting an older Racket version, in which case it's cleaner to discard this scaffolding and adjust the `require`s to use the "official" function directly. 58 | -------------------------------------------------------------------------------- /src/posts/2014/06/file-and-line-in-racket.md: -------------------------------------------------------------------------------- 1 | Title: `__FILE__` and `__LINE__` in Racket 2 | Date: 2014-06-03T00:00:00 3 | Tags: Racket, racket-cookbook 4 | 5 | Many languages have a variable (or preprocessor macro) called 6 | `__FILE__` or `__file__` whose value is the pathname of the current 7 | source file. Likewise `__LINE__` for the the source line number. 8 | 9 | You probably need this less in Racket than you imagine. For example: 10 | 11 | - We wouldn't test that `__FILE__` ends in `main.rkt`; instead we'd 12 | use a `main` submodule `(module+ main )`. 13 | 14 | - To get a data file `foo.dat` located in the same directory as a 15 | source file we'd use `(define-runtime-path foo.dat "foo.dat")`. If 16 | we're a package or executable this works regardless of where our 17 | files happen to get installed. 18 | 19 | But if you really did need a `__FILE__` in Racket, how would you do 20 | it? 21 | 22 | 23 | 24 | Simply: 25 | 26 | ```racket 27 | (syntax-source #'here) ;full path to the source file, i.e. __FILE__ 28 | (syntax-line #'here) ;line number, i.e. __LINE__ 29 | ``` 30 | 31 | You could use `#'0` or `#'42` instead. The only important thing about 32 | `#'here` is that it's a _syntax object_ representing syntax from the 33 | current source file. The `#'` prefix is what matters; it's reader 34 | shorthand for the `syntax` function. So `#'here` is read as `(syntax 35 | here)`, which you could also use if you don't mind typing a few more 36 | keys. 37 | 38 | ```racket 39 | ;; Same as above 40 | (syntax-source (syntax here)) ;full path to the source file, i.e. __FILE__ 41 | (syntax-line (syntax here)) ;line number, i.e. __LINE__ 42 | ``` 43 | 44 | In some Lisps, a macro works on a plain s-expression -- the `0` or 45 | `42` or `here` or `(+ 1 1)`. A _syntax object_ in Racket contains the 46 | s-expression, and also information such as source location (line, 47 | column, position, span) and lexical scope.[^1] 48 | 49 | So in Racket, you simply need to make _some_ syntax object in your 50 | source file, like `#'here`, and feed it to syntax object accessors 51 | like `syntax-source` and `syntax-line`.[^2] 52 | 53 | --- 54 | 55 | What if you want to write `__FILE__` and `__LINE__` like in other 56 | languages? You could define these as macros in a file, and `require` 57 | and use where desired: 58 | 59 | ```racket 60 | #lang racket/base 61 | 62 | (provide __FILE__ __LINE__) 63 | 64 | (require (for-syntax racket/base)) 65 | 66 | (define-syntax (__FILE__ stx) 67 | ;; `stx` comes from where the macro was invoked, so give _that_ to 68 | ;; `syntax-source`. 69 | (with-syntax ([file (syntax-source stx)]) 70 | (syntax-case stx () 71 | [_ #'file]))) 72 | 73 | (define-syntax (__LINE__ stx) 74 | ;; `stx` comes from where the macro was invoked, so give _that_ to 75 | ;; `syntax-line`. 76 | (with-syntax ([line (syntax-line stx)]) 77 | (syntax-case stx () 78 | [_ #'line]))) 79 | ``` 80 | 81 | In this case, we care about the source file and line from where the 82 | macro was invoked -- from where we typed `__FILE__` or `__LINE__`. So 83 | we're careful to give that input `stx` to `source-file` or 84 | `source-line`. (Whereas if we used some made-up syntax object literal 85 | like `#'here`, it would give us the source or line of `#'here` in the 86 | macro _definition_.) 87 | 88 | --- 89 | 90 | It turns out that learning how to do a `__FILE__` in Racket ends up 91 | being a gentle, practical introduction to syntax objects. 92 | 93 | [^1]: For more about syntax objects see my 94 | [Fear of Macros](http://www.greghendershott.com/fear-of-macros/). Or 95 | if you're coming to Racket from another Lisp, it might be quicker to 96 | read 97 | [Eli Barzilay's blog post](http://blog.racket-lang.org/2011/04/writing-syntax-case-macros.html). 98 | 99 | [^2]: You might be wondering, why isn't there an error that no 100 | identifier named `here` is defined? Great question. A syntax object 101 | such as `#'here` a.k.a. `(syntax here)` is basically quoted data -- 102 | like `'here` a.k.a. `(quote here)`. We're not `eval`uating the syntax. 103 | -------------------------------------------------------------------------------- /src/posts/2014/06/racket-cookbook.md: -------------------------------------------------------------------------------- 1 | Title: Racket cookbook 2 | Date: 2014-06-02T09:26:34 3 | Tags: Racket, racket-cookbook 4 | 5 | Two parallel thoughts: 6 | 7 | 1. I haven't blogged in awhile. I've been heads-down on a few projects. 8 | Plus I haven't had ideas I feel are "big" or unique enough to merit a 9 | post. 10 | 11 | 2. It's occurred to me that a "Racket Cookbook" might be a useful 12 | resource. Because examples. Because real-life, practical examples.[^1] 13 | 14 | Although I haven't created a cookbook, my working assumption is that 15 | it would be better to write one recipe at a time. As they arise. As I 16 | think, "Ah right, this wasn't obvious to me when I was learning 17 | Racket." 18 | 19 | So I plan to experiment with releasing the things one at a time, as 20 | short blog posts. Thereby terminating two avian organisms with one 21 | geologic projectile. 22 | 23 | Not sure if I'll keep it up. Definitely not sure if I'll ever collect 24 | and polish them into a "book" of some form. They might only ever live 25 | as a `racket-cookbook` tag on this blog. 26 | 27 | [^1]: Admittedly I rarely recall using any "cookbook" resource to find 28 | the answer to a problem I have. Taken literally, programming cookbooks 29 | are largely a failure, for me. On the other hand, flipping through one 30 | can give the "flavor" of a language. And 31 | [as I've written](/2013/07/skim-or-sink.html), one valid learning 32 | strategy is to absorb things shallowly -- you may not know the answer, 33 | but you know roughly where to _find_ the answer. It rings a bell. 34 | Probably cookbooks fit that model -- filling your head with, um, 35 | bells. 36 | 37 | -------------------------------------------------------------------------------- /src/posts/2014/10/hacker-school-day-15.md: -------------------------------------------------------------------------------- 1 | Title: Hacker School day 15 2 | Date: 2014-10-25T11:03:17 3 | Tags: Hacker School 4 | 5 | I spent most of late Thursday and Friday working on open source 6 | projects that pre-date Hacker School. 7 | 8 | 9 | 10 | - Racket wanted to accept a [pull request] from me to fix a bug with 11 | small bit-vectors. But because I had stumbled around before finding 12 | the correct solution, and discussion on the PR, I'd accumulated 13 | about 8 commits on the PR branch that needed to be squashed. Also, 14 | the PR had been open for about 8 weeks, so I needed to fetch Racket 15 | HEAD and build in order to test properly. Finally I needed to wait 16 | for Travis CI to finish (the full builds of Racket there take ~30 17 | minutes). In all, a fair amount of busywork, but that's an important 18 | part of the process. 19 | 20 | [pull request]: https://github.com/plt/racket/pull/756 21 | 22 | - I fixed a [bug][markdown-bug] in my markdown parser. After boiling 23 | it down to the minimal example, the problem was obvious; the parser 24 | was sometimes returning an x-expression at a stage where a raw 25 | `string?` was expected -- in the case of an `` 26 | inside a list item. The fix was simply deleting the check for the 27 | HTML comment. Which made me suspicious. Had there been some intent 28 | behind the check, even though it now seemed crazy? Or had the check 29 | just been a brain fart? I decided it was the latter, after thinking 30 | it through, and also seeing that all the unit tests (of which I have 31 | many) still passed. So I added the fix, plus a couple regression 32 | tests, [committed], pushed, and commented/closed the issue on GitHub. 33 | 34 | [markdown-bug]: https://github.com/greghendershott/frog/issues/106 35 | [committed]: https://github.com/greghendershott/markdown/commit/0c2e8f8a4b1aa8df2d3ed2955f16553628ac4e08 36 | 37 | - I fixed a [bug][racket-mode-bug] in my racket-mode for Emacs. Turns 38 | out it wasn't treating Unicode characters as symbol constituents, 39 | causing a problem with some paredit operations. Fortunately I had 40 | inherited the problem from scheme-mode, and knew that the problem 41 | did _not_ occur in lisp-mode. As a result, it was straightforward to 42 | track down. The fix was simply setting `multibyte-syntax-as-symbol` 43 | to `t`. 44 | 45 | [racket-mode-bug]: https://github.com/greghendershott/racket-mode/issues/54 46 | 47 | - I reviewed a [pull request for `#lang rackjure`][PR] regarding how a 48 | Racket `#lang` could install a readtable for the REPL, without 49 | doinking the readtable for other modules. 50 | 51 | [PR]: https://github.com/greghendershott/rackjure/pull/46 52 | 53 | Of the three bugs, two turned out to be one-line fixes -- even though 54 | they took some time to figure out and to do "paperwork" like bug 55 | trackers and regression tests. On the one hand, such bugs feel like a 56 | low signal:noise ratio. On the other hand, they're satisfying because 57 | they make you feel like the overall design/assumptions were good. 58 | 59 | Also I did some work implementing Racket variants of some Clojure 60 | map-related functions like `get`, `get-in`, `assoc-in`, `update-in`, 61 | `partition`, `take`,[^take] and `juxt`. More trivially, alias things 62 | like `every?`, `some`, `assoc`, and `dissoc` to their Racket 63 | equivalents. Although I haven't yet pushed these to `#lang rackjure` I 64 | may do so over the weekend. 65 | 66 | --- 67 | 68 | That was day 15 of about 60. On the one hand, it feels like a very 69 | long 3 weeks (in a good way). On the other hand, I can't believe I'm 70 | already 25% through. 71 | 72 | Time perceptions, how do they work?[^magnets] 73 | 74 | [^take]: Unlike Racket's, this doesn't raise an exception when there 75 | are fewer than N elements left, it just returns them. Which IMHO is 76 | more useful; in the past I'd written a `take<=`. 77 | 78 | [^magnets]: Also: Magnets. 79 | -------------------------------------------------------------------------------- /src/posts/2014/10/hands-on-with-clojure-day-3.md: -------------------------------------------------------------------------------- 1 | Title: Hands-on with Clojure day 3 2 | Date: 2014-10-10T11:13:34 3 | Tags: Clojure, Hacker School 4 | 5 | Please see the usual disclaimers from my [previous][] [posts][]. 6 | 7 | [previous]: http://www.greghendershott.com/2014/10/hands-on-with-clojure.html 8 | [posts]: http://www.greghendershott.com/2014/10/hands-on-with-clojure-day-2.html 9 | 10 | As I mentioned yesterday, my next toy project is to write wrappers 11 | libraries for the new [Hacker News API]. This seems like a good 12 | exercise because the REST API is very simple, and I have experience 13 | doing this sort of thing in Racket. In fact, I'll do the same thing in 14 | both Racket and Clojure. 15 | 16 | [Hacker News API]: https://github.com/HackerNews/API 17 | 18 | The result is [clacker-news] and [racker-news]. Trademark registration 19 | application is in-process.[^1] 20 | 21 | [clacker-news]: https://github.com/greghendershott/clacker-news 22 | [racker-news]: https://github.com/greghendershott/racker-news 23 | 24 | [^1]: Kidding. 25 | 26 | 27 | 28 | Although I knew this would be a simple project, it turned out to be 29 | simpler than I expected: 30 | 31 | - The Hacker News API is just a half-dozen items, so that helps. 32 | 33 | - Both Racket and Clojure have easy ways to parse JSON into the 34 | relevant idiomatic representations. (As you might imagine, Racket 35 | is more `list`y and Clojure is more `vector`y, but they are pretty 36 | similar. Because JSON.) 37 | 38 | - I didn't try to do anything fancy with error-handling. If there's an 39 | HTTP-related exception, it's going to bubble up to the user of my 40 | library. I think this is probably fine. 41 | 42 | - I didn't try to do anything fancy with retries. This is probably 43 | less-fine. Given response like say `429 Too Many Requests`, it would 44 | nice if my library would automatically retry some number of times 45 | (with an exponential delay back-off). But it doesn't. 46 | 47 | However, maybe it was unduly easy -- because I overlooked something 48 | important in Clojure (or about making REST API requests in general). 49 | If so, feel free to hit the comments. 50 | 51 | # Redefinitions and the top-level 52 | 53 | While doing some copy-pasta of the Racket source to Clojure, I 54 | accidentally ended up with two `defn`s of the same function, 55 | `get-user`. The thing is, Clojure did _not_ give me an error message. 56 | This really surprised me. In Racket, this would be a redefinition 57 | error. 58 | 59 | I guess this is related to my observation in yesterday's post about 60 | the need to use `declare` for forward references. If I understand 61 | correctly, this means that Clojure's evaluation model is closer to a 62 | simple `load`: It is essentially equivalent to typing stuff at a 63 | top-level REPL prompt. Things are evaluated one s-expression `read` at 64 | a time. 65 | 66 | So for example it's perfectly fine to redefine something at the 67 | top-level in the REPL. 68 | 69 | And, if you `read` things one s-expression at a time -- as opposed to 70 | an entire file and/or namespace -- you can't know about something that 71 | isn't defined yet. You need a hint like `declare`: "Chill, I'm going 72 | to supply it later." 73 | 74 | What I'm used to from Racket is an actual module system. As I 75 | understand it, the first unit of evaluation is a module. And the 76 | `#lang` business is a shorthand for `module` forms. In other words: 77 | 78 | ```racket 79 | #lang racket 80 | (define (id x) 81 | x) 82 | ``` 83 | 84 | is shorthand for: 85 | 86 | ```racket 87 | (module racket 88 | (define (id x) 89 | x)) 90 | ``` 91 | 92 | And in fact you will see older Racket files that use the `module` form 93 | explicitly like that (as well as newer `#lang` files that use `module` 94 | forms within, i.e. for nested modules). 95 | 96 | Maybe I'm misunderstanding, and the business about modules is 97 | orthogonal to the business about redefinitions and forward references. 98 | Maybe that's really about `read`-ing s-expressions one at a time like 99 | in a top-level REPL. In any case, I don't love this aspect of Clojure.[^2] 100 | 101 | [^2]: Among Racketeers, a famous quote is, ["the top-level is hopeless"](https://gist.github.com/samth/3083053). 102 | 103 | # Next steps 104 | 105 | I need an idea for a gradually more-complicated project to try, next. 106 | 107 | Ideally it would exercise something special/strong about Clojure, such 108 | as the persistent immutable data structures, concurrency primitives, 109 | or so on. 110 | 111 | (As a counter-example, I could explore macros in Clojure, but my 112 | expectation is that's an area that might be disappointing compared to 113 | Racket. Instead I'd like to find something where I'm more likely to 114 | say, "Dang, I wish Racket did this.") 115 | 116 | If you have suggestions, let me know in the comments, and thanks in 117 | advance. 118 | -------------------------------------------------------------------------------- /src/posts/2014/10/why-macros.md: -------------------------------------------------------------------------------- 1 | Title: Why macros? 2 | Date: 2014-10-21T13:56:54 3 | Tags: Racket, Clojure, Hacker School 4 | 5 | Yesterday a couple people asked me, "How and why do you use macros in 6 | a Lisp like Racket or Clojure?". 7 | 8 | I gave answers like: 9 | 10 | - The compiler can do a search-and-replace on your code. 11 | 12 | - You can make DSLs. 13 | 14 | - They're an "API for the compiler". 15 | 16 | Although all true, I wasn't sure I was getting the full idea across. 17 | 18 | 19 | 20 | Worse, one time Peter Seibel was within earshot. Although I don't know 21 | if he heard my explanation, I imagined him biting his tongue and 22 | politely remembering the "well, actually" rule. :) 23 | 24 | Later I remembered Matthias Felleisen boiling down macros into three 25 | main categories: 26 | 27 | 1. **Binding forms**. You can make your own syntax for binding values 28 | to identifiers, including function definition forms. You may hear people 29 | say, in a Lisp you don't have to wait for the language designers to 30 | add a feature (like `lambda` for Java?). Using macros you can add it 31 | yourself. Binding forms is one example. 32 | 33 | 2. **Changing order of evaluation**. Something like `or` or `if` can't 34 | really be a function, because you want it to "short-circuit" -- if the 35 | first test evaluates to true, don't evaluate the other test at all. 36 | 37 | 3. **Abstractions like domain specific langagues (DSLs)**. You want to 38 | provide a special language, which is simpler and/or more task-specific 39 | than the full/raw Lisp you're using. This DSL might be for users of 40 | your software, and/or it might be something that you use to help 41 | implement parts of your own program. 42 | 43 | Every macro is doing one of those three things. Only macros can really 44 | do the first two, at all[^order]. Macros let you do the last one more 45 | elegantly. 46 | 47 | I think the preceding is a better answer. However, maybe it's still 48 | not the best way to get people from zero to sixty on, "Why 49 | macros?".[^fear] 50 | 51 | Maybe the ideal is a "teachable moment" -- facing a problem that 52 | macrology would solve.[^learning] That's also good because you really 53 | really _really_ don't want to use a macro when a normal function would 54 | suffice. So the goal isn't to get people _so_ enthusiastic about 55 | macros that they go forth in search of nails to which to apply that 56 | new hammer. Macros often aren't the right approach. But once in a 57 | while, they are the bestest approach ever. 58 | 59 | [^order]: A language like Haskell can choose lazy evaluation, and 60 | implement `if` as a function. I'm saying that only a macro can futz 61 | with whatever the default evaluation order is, be it eager or lazy. 62 | 63 | [^learning]: Certainly that's my own optimal learning situation, as 64 | opposed to getting answers or solutions before I have the questions or 65 | problems. 66 | 67 | [^fear]: Although I wrote a guide called 68 | [Fear of Macros](http://www.greghendershott.com/fear-of-macros/), it's 69 | (a) specific to Racket macros and (b) much more about the "how" than 70 | the "why". 71 | 72 | -------------------------------------------------------------------------------- /src/posts/2014/11/hacker-school-week-6.md: -------------------------------------------------------------------------------- 1 | Title: Hacker school week 6 2 | Date: 2014-11-14T13:42:14 3 | Tags: Hacker School 4 | 5 | I'm at the end of week 6 at Hacker School, which marks the halfway 6 | point. There are overlapping 12-week batches. As a result, the 7 | previous batch never-graduated yesterday. 8 | 9 | My early weeks here included _some_ pairing, and it was fun, but I 10 | spent more time learning and coding solo. My previous week was nearly 11 | the opposite. I spent most of my time pairing with Sumana 12 | Harihareswara. 13 | 14 | 15 | 16 | Her project idea was a web service that would help people find 17 | vulnerabilities in C code, and return a report card itemizing the 18 | issues and giving a score[^1]. 19 | 20 | [^1]: Silicon Valley has its "Weisman Score". Well _this_ is a 21 | "Hendershott-Harihareswara Score". Really rolls off the tongue, 22 | doesn't it? 23 | 24 | Writing a static code analyzer from scratch seemed like it would be 25 | too ambitious for the time available. So for version 0.1 we decided to 26 | have our web service wrap existing tools like 27 | [Clang Static Analyzer](http://clang-analyzer.llvm.org/) and 28 | [Cppcheck](http://cppcheck.sourceforge.net/). Someone can use the 29 | analyzers without needing to install them. 30 | 31 | We installed and got familiar with `clang`'s `scan-build`. I wrote 32 | some C code with deliberate problems, and we saw what was reported. 33 | 34 | For our web server, we decided to make something using Python. We 35 | started to look at our choices for frameworks, like Django, Flask, and 36 | several dozen others. But then we realized, hey -- our web service is 37 | really simple. It has one resource. A request will be: 38 | 39 | ```http 40 | POST /api/analyze HTTP/1.1 41 | Content-Length: 42 42 | 43 | /* my awful C code */ 44 | main () { 45 | } 46 | ``` 47 | 48 | Our response body will be `scan-build` output parsed into some sort of 49 | JSON. 50 | 51 | Do we need any framework at all? 52 | 53 | We decided no. We derived from `BaseHTTPServer`, overriding `do_POST`. 54 | This became a great opportunity to look at how HTTP requests and 55 | responses work. 56 | 57 | Of course, if our web service were to grow, we would find ourselves 58 | copying and pasting a bunch of code. We would DRY it into some helper 59 | functions. One day we'd say, "Let's share this with the world!". And 60 | voila, the world would have another framework. 61 | 62 | Or better yet, we'd say, "OK, now let's use some existing framework. 63 | Now we know what problems it's solving for us. Now we have a 64 | reasonable mental model for how it probably works; it's not some 65 | magical black box." 66 | 67 | When it came time to parse the `scan-build` output into JSON, I found 68 | it quicker to write that in Racket than in Python. Which was fine, 69 | because that was something we needed done, as opposed to something we 70 | wanted to learn about. 71 | 72 | At this point we had a reasonably well-working application that ran... 73 | on our laptops. We wanted to deploy it on a Digital Ocean droplet. 74 | Putting one web service on one droplet isn't difficult. But we opted 75 | to pretend that our web service would become wildly popular and we'd 76 | need to scale it across multiple machines. A couple people had asked 77 | if we were going to try Docker. We did. 78 | 79 | It took us awhile to wrap our head around the concepts. Eventually we 80 | understood that an _image_ is a read-only thing created from a 81 | `Dockerfile`. It is built from a base image, e.g. Ubuntu, plus 82 | additional packages that you install, and other programs that you run 83 | and settings that you configure. Thereafter you launch _containers_ 84 | for an image. These have a read/write layer and are a kind of 85 | lightweight VM in which your application runs. You can `docker run -d 86 | ` to detach these and they run in the background; you `docker 87 | logs ` to see their output. You can `docker stop 88 | ` to stop one. After stopped it still exists. You can see 89 | all these with `docker ps -a`, or just the running ones with `docker 90 | ps`. You can delete a stopped container with `docker rm `. 91 | 92 | Therefore we wanted to create an image based on Ubuntu, install some 93 | packages like `git` and Racket 6.1, and finally do a `git clone` of 94 | our app code. 95 | 96 | A couple issues that slowed us down briefly: 97 | 98 | 1. `apt-get` wouldn't work until we changed `/etc/default/docker/` to 99 | un-comment the line saying to use Google's DNS servers. 100 | 101 | 2. How to set an environment variable in our `Dockerfile`, that would 102 | survive into the container's environment. Doing `RUN export 103 | PATH=$PATH:/new/path` did not work. Turned out we needed `ENV`, as 104 | in `ENV PATH $PATH:/new/path`. 105 | 106 | In the end we got things working. 107 | 108 | The final day we made a simple web _site_ to go along with our web 109 | _service_. So as a human you can visit a page, type C code in a form 110 | or do a file upload, and get the report card on a web page rather than 111 | as raw JSON. Although we didn't learn a whole lot by doing this part, 112 | it was a satisfying way to wrap up the project. 113 | 114 | I enjoyed the project because I got a chance to: 115 | 116 | - Try more Python coding in a week than I've done, ever. 117 | 118 | - Understand Docker hands-on. 119 | 120 | - Exercise developer teamwork skills. 121 | 122 | - Validate my understanding of HTTP mechanics by explaining them 123 | effectively. 124 | 125 | For my second half of Hacker School, I'm looking forward to doing more 126 | pairing. After all, I can study things solo anytime. Now is when I 127 | have so many great opporunities to pair. 128 | -------------------------------------------------------------------------------- /src/posts/2014/12/blogging-catch-up.md: -------------------------------------------------------------------------------- 1 | Title: Blogging catch-up 2 | Date: 2014-12-05T20:00:00 3 | Tags: Hacker School 4 | 5 | It's been a few weeks since I've blogged. Bad me. This is a catch-up 6 | post. 7 | 8 | Hacker School has a tool called [Blaggregator] created by Sasha Laundy. 9 | We can submit feeds for our blogs. Blaggregator provides an aggregate 10 | page, and puts new-post messages on Zulip, the chat tool. 11 | 12 | [Blaggregator]: https://github.com/sursh/blaggregator 13 | 14 | 15 | 16 | Awhile ago I noticed a bug with time zones in the RSS feed generated by 17 | [Frog]. I was concerned that fixing the feed would change date-times, 18 | and that _might_ cause Blaggregator to flood Zulip with "new post!" 19 | notifications. Which would be embarrassing. So I unsubscribed my feed, 20 | fixed the problem, and subscribed again. All went well, but Sasha 21 | pinged me on Zulip today to follow up and make sure. 22 | 23 | [Frog]: https://github.com/greghendershott/frog 24 | 25 | That prompted me to check out Sasha's profile page and list of 26 | projects. That led me to 27 | [On Rakefiles and Rabbit Holes](http://blog.sashalaundy.com/blog/2013/03/25/on-rakefiles-and-rabbit-holes/). 28 | This is such a classic Hacker School type of post -- where someone 29 | takes the time to follow the trail wherever it leads, and learn cool 30 | new things. 31 | 32 | Sasha's mention of `stty -echo` reminded me of a mild annoyance in 33 | Emacs' shell-mode. Sometimes -- but not always -- it starts echoing my 34 | input, as if I had typed `stty echo`. Sasha's post this reminded me I 35 | could type `stty -echo` to turn it off. But how and why does it turned 36 | on in the first place? I searched and found a Stack Overflow question, 37 | [Python in Emacs shell-mode turns on stty echo and breaks C-d](http://stackoverflow.com/questions/19246065/python-in-emacs-shell-mode-turns-on-stty-echo-and-breaks-c-d). 38 | 39 | Aha. As it says: 40 | 41 | > OS X ships with the BSD-licensed editline rather than the 42 | > GPL-licensed readline, so this would explain why the behaviour is 43 | > different on OS X from other Unixes. The same thing happens with 44 | > other interactive interpreters. I find that the Lua, Ruby and 45 | > Sqlite3 command-line interpreters also turn on terminal echo when 46 | > run inside Emacs. So it seems to be some kind of "feature" of the 47 | > editline library. 48 | 49 | Yes. A fresh M-x `shell` is fine, until I run (say) 50 | `python` or `racket`. 51 | 52 | Anyway, Sasha's Zulip message managed to nudge me into writing a blog 53 | post. 54 | 55 | ## So what _have_ I been up to? 56 | 57 | My first six weeks here, I mostly spent multiple weeks at a time on 58 | things like getting hands-on with Clojure and then Haskell, or pairing 59 | a full week on a web service. 60 | 61 | Whereas so far in the second half, I've been doing a variety of 62 | smaller projects -- which I haven't blogged about one by one. Here's a 63 | quick overview: 64 | 65 | - Help some people who were pairing to create a lean web framework in 66 | Python. We got to see first-hand how cookies are a way to provide 67 | state for a multi-page form application. 68 | 69 | - Work through the [Rust Guide] hands-on. 70 | 71 | - Pair with someone working on a Python web app that shows you if it's 72 | Shabbat in the location of someone you want to call. They wanted to 73 | refactor the code to be less repetitive. 74 | 75 | - Participate in Zach Allaun's hands-on workshop about configuring and 76 | developing using ClojureScript. 77 | 78 | - Fix a bug in [racket-mode] on Windows. 79 | 80 | - Pair with someone who is vastly more experienced in Haskell than me, 81 | to learn how to use Template Haskell for my Haskell port of wffi. 82 | 83 | - Fix some issues with a couple of my Racket packages, [aws] and 84 | [http], that are now being used by Racket's DrDr continuous 85 | integration tool. 86 | 87 | - Pair with someone who is getting started with Racket and wanted to 88 | use racket-mode on Emacs. 89 | 90 | - Start to learn about microKanren from looking at [sokuza-kanren.scm] 91 | and a couple papers including 92 | [μKanren: A Minimal Functional Core for Relational Programming]. 93 | 94 | - Start to work through the [Matasano crypto challenges].[^1] 95 | 96 | [Rust Guide]: http://doc.rust-lang.org/guide.html 97 | [aws]: https://github.com/greghendershott/aws 98 | [http]: https://github.com/greghendershott/http 99 | [racket-mode]: https://github.com/greghendershott/racket-mode 100 | [sokuza-kanren.scm]: http://pobox.com/~oleg/ftp/Scheme/sokuza-kanren.scm 101 | [μKanren: A Minimal Functional Core for Relational Programming]: http://webyrd.net/scheme-2013/papers/HemannMuKanren2013.pdf 102 | [Matasano crypto challenges]: http://cryptopals.com/ 103 | 104 | In most of these cases, I didn't feel like I had an entire interesting 105 | blog post to write. I did take notes and had some fantastic 106 | experiences both learning and teaching. 107 | 108 | [^1]: All the friends and family who think I'm learning cracking here 109 | at Hacker School? This is the closest I've come so far. 110 | -------------------------------------------------------------------------------- /src/posts/2017/03/please-scroll.md: -------------------------------------------------------------------------------- 1 | Title: Please scroll 2 | Date: 2017-03-08T17:00:00 3 | Tags: Racket, Emacs 4 | 5 | Recently I got more time to catch up on [racket-mode]. I improved two 6 | things that happen to fit one theme -- an extraordinarily advanced UX 7 | concept I call, "scrolling down to the point of interest." 8 | 9 | [racket-mode]: https://github.com/greghendershott/racket-mode 10 | 11 | 12 | 13 | ## Visit definition 14 | 15 | In racket-mode you can M-. (press Alt and 16 | period) to visit the definition of a Racket identifier. 17 | 18 | There is no Racket library function that supports this, exactly. The 19 | [`identifier-binding`] function gives you a filename, but not a 20 | position within. And actually, it gives you _two_ filenames (and 21 | identifier symbols), because the location and symbol of the 22 | _definition_ might differ from the location and symbol under which it 23 | is _provided_. 24 | 25 | [`identifier-binding`]: http://docs.racket-lang.org/reference/stxcmp.html#%28def._%28%28quote._~23~25kernel%29._identifier-binding%29%29 26 | 27 | I've also found it can be tricky when something is renamed more than 28 | once -- for example both renamed and wrapped in a contract. Of the 29 | three (or more) names involved, `identifier-binding` will return only 30 | two. For example in `(provide (contract-out [rename orig new 31 | contract]))` it reports (1) the contract wrapper's generated 32 | identifier and (2) `new` -- but _not_ (3) `orig`. Unfortunately the 33 | definition of `orig` is our desired destination. 34 | 35 | So, I need to treat `identifier-binding` as a valuable head start 36 | -- but maybe not the real answer. 37 | 38 | I need to check each file and try to find the identifier within. This 39 | isn't a job for regular expression; the name might not appear 40 | textually at the definition site (think of `define`-er macros). 41 | Instead I read the file as syntax and walk it. Sometimes it makes 42 | sense to search the syntax after it has been fully-expanded. Sometimes 43 | it helps to walk it unexpanded, looking for some special forms, for 44 | example the "rename" variant of `contract-out`. 45 | 46 | If after all that, we can't find the position, racket-mode plops you 47 | at the start of the file. 48 | 49 | The change I made was to reduce the chance of that happening. Details 50 | in the [commit] message and diff. 51 | 52 | [commit]: https://github.com/greghendershott/racket-mode/commit/c50cd48edc74348bd89b09661ea325dac12fcb48 53 | 54 | ## View documentation 55 | 56 | In racket-mode you can C-c C-d to view Racket's HTML 57 | documentation in your default web browser. It should (a) open the 58 | correct page and (b) scroll to the item on that page. Unfortunately 59 | (b) didn't always happen on macOS. Under certain conditions, macOS is 60 | reluctant to open `file:` URLs _and_ scroll down to the 61 | anchor/fragment (the bit after the `#` in the URL). 62 | 63 | ```sh 64 | 65 | # Will open the default browser to the top of the define.html page 66 | # but not scroll down to the define-values item: 67 | $ open 'file:///Applications/Racket_v6.7/doc/reference/define.html#%28form._%28%28quote._~23~25kernel%29._define-values%29%29' 68 | 69 | # Ditto 70 | $ osascript -e 'open location "file:///Applications/Racket_v6.7/doc/reference/define.html#%28form._%28%28quote._~23~25kernel%29._define-values%29%29"' 71 | 72 | # But this works! 73 | $ osascript -e 'tell application "chrome" to open location "file:///Applications/Racket_v6.7/doc/reference/define.html#%28form._%28%28quote._~23~25kernel%29._define-values%29%29"' 74 | 75 | # Well, you also want the "activate" at the end: 76 | $ osascript -e 'tell application "chrome" to open location "file:///Applications/Racket_v6.7/doc/reference/define.html#%28form._%28%28quote._~23~25kernel%29._define-values%29%29" activate' 77 | 78 | ``` 79 | 80 | Interestingly the generic `open` seems to work fine for `http:`. Also 81 | fine if a `file:` location is under your home directory instead of 82 | `/Applications`. Because security?[^1] Anyway, this is probably why 83 | Racket developers and power users haven't noticed, if they're building 84 | Racket (and its docs) from HEAD. 85 | 86 | [^1]: Security theater? I don't see why it's safe to load a page, but risky to scroll to an anchor in it? I also don't see why `/Applications` is more risky than your home dir -- much less some rando `http:` location? 87 | 88 | So I can use `osascript` if I know the default browser. How do I know 89 | that? Ugh. OK. This information seems to reside in 90 | `Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`. 91 | Read the JSON, find the correct entry, grab the `browser` part of 92 | `com.company.browser`, and hopefully we're good. 93 | 94 | ## Pontification 95 | 96 | Computer science mostly isn't science. 97 | 98 | Software engineering mostly isn't engineering. 99 | 100 | Sometimes success is simply scrolling. 101 | -------------------------------------------------------------------------------- /src/posts/2017/04/racket-makefiles.md: -------------------------------------------------------------------------------- 1 | Title: Racket Makefiles 2 | Date: 2017-04-18T12:00:00 3 | Tags: Racket 4 | 5 | A few years 6 | ago 7 | [I wrote about makefiles for Racket](/2014/06/does-your-racket-project-need-a-makefile.html). 8 | Some things have changed. 9 | 10 | 1. The old makefile built and pushed documentation to a GitHub Pages 11 | branch of the repo. That's no longer necessary: The Racket package 12 | catalog builds and hosts documentation. 13 | 14 | 2. The Racket package catalog puts a yellow badge of shame on packages 15 | with missing dependencies (`deps` and `build-deps` in the package's 16 | `info.rkt`). I want the makefile to check this. 17 | 18 | 3. In `.travis.yml` files for Travis CI, I think the `script` section 19 | ought to simply invoke targets in the makefile -- delegating 20 | details to the latter. 21 | 22 | 4. Likewise some details needn't even be in the makefile -- they can 23 | move to the collection's `info.rkt`. Example: The list of 24 | directories to `clean`. 25 | 26 | 5. The old makefile had separate `PACKAGENAME` and `COLLECTS` 27 | variables; for single-collection packages they were the same value. 28 | I wanted to simplify this to just the package name and use the 29 | appropriate package variants of `raco` commands. 30 | 31 | In that spirit, here's an updated Makefile, which I recently started 32 | using in the [rackjure], [markdown], and [frog] projects. 33 | 34 | [rackjure]: https://github.com/greghendershott/rackjure 35 | [markdown]: https://github.com/greghendershott/markdown 36 | [frog]: https://github.com/greghendershott/frog 37 | 38 | 39 | 40 | ```make 41 | PACKAGE-NAME=rackjure 42 | 43 | # Racket 6.1 adds pkg dep checking. 44 | ifeq ($(findstring "$(RACKET_VERSION)", "6.0", "6.0.1"),) 45 | DEPS-FLAGS=--check-pkg-deps --unused-pkg-deps 46 | else 47 | DEPS-FLAGS= 48 | endif 49 | 50 | all: setup 51 | 52 | # Primarily for use by CI. 53 | # Installs dependencies as well as linking this as a package. 54 | install: 55 | raco pkg install --deps search-auto 56 | 57 | remove: 58 | raco pkg remove $(PACKAGE-NAME) 59 | 60 | # Primarily for day-to-day dev. 61 | # Note: Also builds docs (if any) and checks deps. 62 | setup: 63 | raco setup --tidy --avoid-main $(DEPS-FLAGS) --pkgs $(PACKAGE-NAME) 64 | 65 | # Note: Each collection's info.rkt can say what to clean, for example 66 | # (define clean '("compiled" "doc" "doc/")) to clean 67 | # generated docs, too. 68 | clean: 69 | raco setup --fast-clean --pkgs $(PACKAGE-NAME) 70 | 71 | # Primarily for use by CI, after make install -- since that already 72 | # does the equivalent of make setup, this tries to do as little as 73 | # possible except checking deps. 74 | check-deps: 75 | raco setup --no-docs $(DEPS-FLAGS) $(PACKAGE-NAME) 76 | 77 | # Suitable for both day-to-day dev and CI 78 | test: 79 | raco test -x -p $(PACKAGE-NAME) 80 | ``` 81 | 82 | The two main scenarios here: 83 | 84 | - Day-to-day development: `make setup` and `make test`. 85 | 86 | - CI: `make install`, `make check-deps`, and `make test`. 87 | 88 | I think you could probably use this as a template for any 89 | single-collection package project. Just change `PACKAGE-NAME`. 90 | Possibly append a target or two for something unique to your project. 91 | 92 | --- 93 | 94 | This Makefile is designed to work with Racket 6.0 or newer -- because 95 | I have some existing packages that support Rackets that old. If you 96 | only care about Racket 6.1 or newer, then all this: 97 | 98 | ```make 99 | # Racket 6.1 adds pkg dep checking. 100 | ifeq ($(findstring "$(RACKET_VERSION)", "6.0", "6.0.1"),) 101 | DEPS-FLAGS=--check-pkg-deps --unused-pkg-deps 102 | else 103 | DEPS-FLAGS= 104 | endif 105 | ``` 106 | 107 | can become just this: 108 | 109 | ```make 110 | DEPS-FLAGS=--check-pkg-deps --unused-pkg-deps 111 | ``` 112 | 113 | --- 114 | 115 | By the way, I wouldn't call myself a very experienced user of `make`. 116 | 117 | For example the way I check for Racket < 6.1 seems smelly -- but it 118 | was the least-worst way I could figure out. 119 | 120 | So please feel free to share any suggestions or corrections in the 121 | comments. 122 | -------------------------------------------------------------------------------- /src/posts/2018/05/extramaze-llc-using-system-fonts-not-google-fonts.md: -------------------------------------------------------------------------------- 1 | Title: Extramaze LLC: Using system fonts (not Google fonts) 2 | Date: 2018-05-20T00:00:00 3 | Tags: Racket, Extramaze 4 | 5 | > Update: Due to lack of interest/use, in June 2021 this site was shut 6 | > down and user data (emails, names, search alerts) deleted from all 7 | > systems and backups. 8 | 9 | In my [previous post] I discussed what I'm doing with 10 | [deals.extramaze.com] -- and what I'm intentionally _not_ doing. Since 11 | then, I'm not-doing more. This improves performance and simplifies the 12 | content security policy. 13 | 14 | [previous post]: /2018/05/extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js.html 15 | [deals.extramaze.com]: https://deals.extramaze.com 16 | 17 | 18 | 19 | In the [privacy] section I described various third-party services the site is 20 | _not_ using. On [Recurse Center's] private chat ([Zulip]), [Josh Bronson] 21 | pointed out that using Google-hosted fonts might not be the best idea: 22 | 23 | [privacy]: /2018/05/extramaze-llc-using-racket-postgresql-aws-but-no-ads-or-js.html#trying-to-do-the-right-thing-privacy 24 | 25 | [Josh Bronson]: https://twitter.com/jab_______ 26 | 27 | [Recurse Center's]: https://www.recurse.com/ 28 | 29 | [Zulip]: https://zulipchat.com 30 | 31 | - Privacy: Using `fonts.googleapis.com` tells Google what sites the 32 | user is visiting. It doesn't make much sense to avoid Google 33 | Analytics but keep using Google Fonts. 34 | 35 | - Performance: Downloading custom fonts takes time. This can be 36 | especially annoying on slower mobile connections. 37 | 38 | Often these are two sides of the same coin: Leaking information to a 39 | third party usually consumes extra time and bandwidth to make extra 40 | connections and transfer data. 41 | 42 | Instead host the font files locally? That would address privacy. But 43 | it could make performance worse not better. Using fonts hosted by 44 | Google _does_ have the advantage that the user might already have 45 | downloaded them, as a result of some other web site using them. 46 | 47 | Josh asked if I'd considered using system fonts, and shared 48 | [two](https://furbo.org/2018/03/28/system-fonts-in-css/) 49 | [links](http://markdotto.com/2018/02/07/github-system-fonts/). 50 | Initially I was reluctant -- but but but... my carefully chosen fonts! 51 | And yes, it changed the look and feel of the site, somewhat. But after 52 | living with it for about half an hour, I thought it was just fine. 53 | 54 | # Accessibility 55 | 56 | At the same time I was looking at fonts, I'd been starting to review 57 | accessibility issues. Such as making sure that: 58 | 59 | - `img` elements have `alt` attributes 60 | - `svg` elements have child `title` elements 61 | - the `html` element has a `lang=en` attribute 62 | - all `id` attributes on a page are unique 63 | 64 | Another item on this list: Ensuring that font colors have sufficient 65 | contrast. So I did that while I had the hood open. 66 | 67 | (I have more work to do for accessibility: I've only just started to 68 | use the site with the macOS screen reader. Maybe this should be 69 | another blog post.) 70 | 71 | # Content Security Policy 72 | 73 | Dropping Google hosted fonts let me simplify the content security 74 | policy. Indeed it seems to have cut down on the number of violation 75 | reports. 76 | 77 | Since my last blog post, I had temporarily switched from 78 | `Content-Security-Policy` to `Content-Security-Policy-Report-Only`. I 79 | was nervous because I didn't understand all the violations. 80 | [report-uri.com] helps by filtering things like violations caused by 81 | browser extensions. But even after such filtering, I had violations 82 | that made no sense to me. 83 | 84 | [report-uri.com]: https://report-uri.com 85 | 86 | Using system fonts, that situation improved significantly. I feel good 87 | about cranking it back up again to be enforced instead of 88 | report-only.[^footnote-about-using-both] 89 | 90 | [^footnote-about-using-both]: Of course you can use both headers. `Content-Security-Policy` is what you're enforcing, and `Content-Security-Policy-Report-Only` can be used to dry-run changes. 91 | 92 | I suppose this illustrates a concern using any third-party service. 93 | Effectively it mutates your web site with various scripts or fonts or 94 | styles. What are all its mutations? You may think you know from 95 | observation: Oh it needs a script from a certain URI. But oops, in a 96 | certain scenario it turns out that it also needs a style from another 97 | URI. Surprise. And that's just today. What if the service changes in 98 | the future? 99 | 100 | Really, every service ought to state exactly what content security 101 | policy settings it needs to work. 102 | -------------------------------------------------------------------------------- /src/posts/2018/11/thread-names.md: -------------------------------------------------------------------------------- 1 | Title: Thread names 2 | Date: 2018-11-01T20:10:21 3 | Tags: Racket 4 | 5 | Sometimes people want Racket `thread`[racket]s to have useful names -- 6 | for example to show in logger output. Here is one way to do it. 7 | 8 | 9 | 10 | When you call `thread` it returns a _thread descriptor_. 11 | 12 | You may also get the thread descriptor of the current thread using 13 | `current-thread`[racket]. 14 | 15 | The `print`[racket]ed representation of a thread descriptor includes 16 | the `object-name`[racket] of its thunk procedure. 17 | 18 | Often the thunk is an anonymous function. In that case the function 19 | object-name is something like `/path/to/file.rkt:1:3` -- so the thread 20 | descriptor prints as `#`. 21 | 22 | ```racket 23 | (thread (λ () #f)) ;=> # 24 | ``` 25 | 26 | It is more interesting when you name the thunk: 27 | 28 | ```racket 29 | (define (foo) #f) 30 | (thread foo) ;=> # 31 | ``` 32 | 33 | You can also use `object-name` on the thread descriptor to extract the 34 | thunk's `object-name` as a symbol: 35 | 36 | ```racket 37 | (object-name (thread (λ () #f))) ;=> '/tmp/thread.rkt:18:21 38 | (object-name (thread foo)) ;=> 'foo 39 | ``` 40 | 41 | What if you start 10 threads that all use the same thunk procedure? 42 | You can use `procedure-rename`[racket] to rename the thunk each time: 43 | 44 | ```racket 45 | (thread (procedure-rename foo 'bar)) ;=> # 46 | (thread (procedure-rename foo 'baz)) ;=> # 47 | ``` 48 | 49 | The new name can be any symbol -- including one generated at runtime: 50 | 51 | ```racket 52 | (define (now-sym) 53 | (string->symbol (~a (current-inexact-milliseconds)))) 54 | (thread (procedure-rename foo (now-sym))) ;=> ex: # 55 | (thread (procedure-rename foo (now-sym))) ;=> ex: # 56 | ``` 57 | 58 | Of course, you can include in the generated name any interesting 59 | identifying information from your problem domain -- such as an "ID" or 60 | other details about a "job", "request", or "message". 61 | 62 | So something like this: 63 | 64 | ```racket 65 | #lang at-exp racket/base 66 | 67 | (require racket/format) 68 | 69 | (define-logger thor) 70 | 71 | (define (thor) 72 | (log-thor-debug @~a{hammering job @(object-name (current-thread))})) 73 | 74 | (define (job-id) 75 | (string->symbol (~a (current-inexact-milliseconds)))) 76 | 77 | (thread (procedure-rename thor (job-id))) 78 | (thread (procedure-rename thor (job-id))) 79 | ``` 80 | 81 | Can produce logger output like this: 82 | 83 | ```sh 84 | [ debug] thor: hammering job 1541119025888.965 85 | [ debug] thor: hammering job 1541119025889.021 86 | ``` 87 | 88 | In conclusion, it is possible for threads to have unique names in 89 | Racket -- provided you're OK giving unique names to the thunks run by 90 | the theads. 91 | 92 | -------------------------------------------------------------------------------- /src/posts/2019/04/exploding-frog.md: -------------------------------------------------------------------------------- 1 | Title: Exploding Frog 2 | Date: 2019-04-03T00:00:00Z 3 | Tags: Racket, Frog 4 | 5 | I'm writing and publishing this post using something other than [Frog]. 6 | 7 | Having said that, I'm not planning to abandon maintaining Frog. 8 | 9 | [Frog]: https://github.com/greghendershott/frog 10 | 11 | 12 | 13 | Over the past week I explored doing something I've wanted to try for 14 | many years: "Exploding" Frog from an app that you run, into [a set of 15 | little commands that you call from a 16 | Makefile](https://github.com/greghendershott/blog). 17 | 18 | I believe there's a variation of [Greenspun's Tenth Rule]: 19 | 20 | > Any sufficiently complicated static blog generator contains an 21 | > ad-hoc, informally-specified, bug-ridden, slow implementation of 22 | > half of `make`. 23 | > 24 | >
-- Someone who has made a static blog generator
25 | 26 | [Greenspun's Tenth Rule]: https://en.wikipedia.org/wiki/Greenspun's_tenth_rule 27 | 28 | If you look at Frog's code, by volume, an awful lot of it consists of: 29 | 30 | 1. Figuring out what needs to be (re)built. 31 | 2. "Path math". 32 | 3. Configuration, customization, templates. 33 | 4. Support for Bootstrap or various surveillance capitalism gadgets. 34 | 35 | On the one hand, it is certainly very "Rackety" to write this in 36 | Racket. On the other hand, Racket advocates language-oriented 37 | programming. Make is a language for expressing the first two things. 38 | 39 | As for the third item: Originally, I thought it would be cool to write 40 | Frog as an app, thinking people might use it even if they weren't 41 | particularly into Racket programming. In reality, of course 42 | approximately 103.8% of Frog users like to program in Racket. So 43 | maybe, call this crazy, programmers who want a blog generating program 44 | to work a little differently could, I don't know, change the program a 45 | little. Maybe there need not be layers of configuration and 46 | customization. 47 | 48 | --- 49 | 50 | And yet, I've never really known that much about GNU Make. Just enough 51 | to cargo cult. And the GNU Make documentation, although thorough, 52 | never really "clicked" for me. This time, though, I stuck with it. It 53 | helped to have a non-trivial project in mind. 54 | 55 | The most confusing thing for me at first, was, Make seems oriented 56 | around defining targets and "pulling" those from sources. "This 57 | executable depends on linking these object files. These object files 58 | depend on these .c and .h files." I didn't really grok how to make it 59 | be "push"-driven: We have a bunch of (say) `.md` post sources. Yes 60 | those get built into `.html` files, but we also have tags, and feeds, 61 | and...? 62 | 63 | In the end I made use of some variables to define the sources, 64 | intermediate build files, and final outputs: 65 | 66 | ```make 67 | # Configure where are sources, build cache, and root of the web site 68 | # output. 69 | src := src 70 | cache := .cache 71 | www := www 72 | 73 | # Make normally "pulls" targets from sources, but we want to "push" 74 | # sources to targets. As a result, we need to build lists of sources 75 | # and from those build lists of targets. 76 | 77 | post-sources := $(shell find $(src)/posts -type f) 78 | post-caches := $(patsubst $(src)/posts/%.md,$(cache)/%.rktd,$(post-sources)) 79 | post-htmls := $(patsubst $(cache)/%.rktd,$(www)/%.html,$(post-caches)) 80 | 81 | tag-caches := $(wildcard $(cache)/tags/*) 82 | tag-htmls := $(patsubst %,$(www)/tags/%.html,$(notdir $(tag-caches))) 83 | tag-atom-feeds := $(patsubst %,$(www)/feeds/%.atom.xml,$(notdir $(tag-caches))) 84 | tag-rss-feeds := $(patsubst %,$(www)/feeds/%.rss.xml,$(notdir $(tag-caches))) 85 | 86 | non-post-sources := $(wildcard $(src)/non-posts/*.md) 87 | non-post-htmls := $(patsubst %.md,$(www)/%.html,$(notdir $(non-post-sources))) 88 | ``` 89 | 90 | Then, for example, a rule like this is how we turn a `.md` source into 91 | an intermediate `.rktd` cached file: 92 | 93 | 94 | ```make 95 | $(cache)/%.rktd: $(src)/posts/%.md 96 | $(make-post-cache) $< $(abspath $(cache)/tags) $@ 97 | ``` 98 | 99 | and that into an `.html` file: 100 | 101 | ```make 102 | $(www)/%.html: $(cache)/%.rktd rkt/page-xexpr.rkt 103 | $(make-post-html) $< $(www) $@ 104 | ``` 105 | 106 | What is this business about an "intermediate post cached file"? Well, 107 | the overall model is a two-stage "compile and link" sort of metaphor, 108 | similar to Frog. Post `.md` sources only define the "body" of a post 109 | page, e.g. the `
` element that will go somewhere in a page. 110 | Building that can be semi-expensive when you do enhancements like 111 | syntax-highlighting and automatic links to Racket documentation. It's 112 | nice not to need to redo that, simply because some other element on 113 | the containing page changes. Also, each post has one or more tags, so 114 | the first pass is a chance to build an index of tags to posts, which 115 | the second pass can use to make feed files and an index page for each 116 | tag. 117 | 118 | The little commands that the `Makefile` calls are defined in in a 119 | `rkt/` subdirectory. Some of them `require` modules from Frog. Others 120 | are copypasta with some adaptation. Any of these files that could be 121 | truly generic -- i.e. won't elicit a pull-request someday to get 122 | different blog behavior -- I'd like to move eventually into a 123 | "tadpole" package. The remaining code should go in some repo -- _not_ 124 | a package -- as example code. That is, you could copy it -- probably 125 | not even fork it -- and just hack away. It is your blog, so it is your 126 | program. 127 | 128 | So far I'm enjoying this new approach. 129 | 130 | Again, I still plan to maintain Frog, in terms of fixing bugs and 131 | keeping it working on newer versions of Racket. Already in the past 132 | couple years I've been super reluctant to add new features, and 133 | haven't. All I'm saying here is that I plan to continue that 134 | not-doing. 135 | -------------------------------------------------------------------------------- /src/posts/2019/04/supporting-multi-in.md: -------------------------------------------------------------------------------- 1 | Title: Supporting multi-in 2 | Date: 2019-04-25T00:00:00Z 3 | Tags: Racket, Emacs 4 | 5 | In [racket-mode] I improved support for the `multi-in`[racket] form 6 | provided by `racket/require`[racket]. 7 | 8 | [racket-mode]: https://github.com/greghendershott/racket-mode/ 9 | 10 | 11 | 12 | # What is `multi-in`[racket]? 13 | 14 | Instead of: 15 | 16 | ```racket 17 | (require net/uri-codec 18 | net/url 19 | racket/contract 20 | racket/format 21 | racket/string) 22 | ``` 23 | 24 | You can say: 25 | 26 | ```racket 27 | (require racket/require 28 | (multi-in net (uri-codec url)) 29 | (multi-in racket (contract format string))) 30 | ``` 31 | 32 | One detail: The `racket/require` must appear before any `multi-in` 33 | forms. Any sorting must make an exception for this. 34 | 35 | # What are the `racket-{tidy trim base}-requires` commands? 36 | 37 | - `racket-tidy-requires` gathers multiple `require` forms into one. 38 | Within that, it groups phase level sub-forms such as `for-syntax`. 39 | Finally it sorts absolute module paths like `foo` before relative 40 | paths like `"foo.rkt"`, and sorts each of those alphabetically. 41 | 42 | - `racket-trim-requires` uses the `show-requires`[racket] function 43 | from `macro-debugger/analysis/check-requires`[racket] to analyze 44 | requires and delete any unused. Also it tidies. 45 | 46 | - `racket-base-requires` uses the analysis to change a `#lang racket` 47 | file to `#lang racket/base`, adding requires as necessary. Also it 48 | trims and tidies. 49 | 50 | # What are the changes? 51 | 52 | 1. Sorting makes sure to keep `racket/require` first. 53 | 54 | 2. If `racket/require` is present in the original `require` form(s), 55 | then `multi-in` is used when tidying. 56 | 57 | 3. Any analysis that says requires should be removed, should handle 58 | `multi-in` forms. 59 | 60 | Essentially, the commands first "expand" or "explode" `multi-in` forms 61 | to individual requires, do any analysis and modifications, then try to 62 | "unexpand" or "implode" them back again.[^1] 63 | 64 | [^1]: Not everything survives a round-trip, exactly. A Cartesian product like `(multi-in (a b) (c d))` will end up as a `(multi-in a (c d))` and a `(multi-in b (c d))` -- equivalent but not as concise. 65 | 66 | All together, these changes close issues [355], [356], and [369]. 67 | 68 | [355]: https://github.com/greghendershott/racket-mode/issues/355 69 | [356]: https://github.com/greghendershott/racket-mode/issues/356 70 | [369]: https://github.com/greghendershott/racket-mode/issues/369 71 | 72 | 73 | ## Example without `racket/require` 74 | 75 | Here's an example where `racket/require` is _not_ in the original 76 | `require` forms: 77 | 78 | ```racket 79 | #lang racket 80 | 81 | (require net/url) 82 | (require net/uri-codec) 83 | 84 | ;; Just some expressions using imported definitions 85 | string-join ~a get-pure-port uri-decode (match-define (list n) (list 1)) 86 | ``` 87 | 88 | After M-x `racket-base-requires`: 89 | 90 | ```racket 91 | #lang racket/base 92 | 93 | (require net/uri-codec 94 | net/url 95 | racket/format 96 | racket/match 97 | racket/string) 98 | 99 | ;; Just some expressions using imported definitions 100 | string-join ~a get-pure-port uri-decode (match-define (list n) (list 1)) 101 | ``` 102 | 103 | ## Example with `racket/require` 104 | 105 | Here's an example where `racket/require` _is_ in the original 106 | `require` forms: 107 | 108 | ```racket 109 | #lang racket 110 | 111 | (require racket/require) ;new 112 | (require net/url) 113 | (require net/uri-codec) 114 | 115 | ;; Just some expressions using imported definitions 116 | string-join ~a get-pure-port uri-decode (match-define (list n) (list 1)) 117 | ``` 118 | 119 | After M-x `racket-base-requires`: 120 | 121 | ```racket 122 | #lang racket/base 123 | 124 | (require racket/require 125 | (multi-in net (uri-codec url)) 126 | (multi-in racket (format match string))) 127 | 128 | ;; Just some expressions using imported definitions 129 | string-join ~a get-pure-port uri-decode (match-define (list n) (list 1)) 130 | ``` 131 | -------------------------------------------------------------------------------- /src/posts/2019/06/linux-laptop.md: -------------------------------------------------------------------------------- 1 | Title: Linux Laptop 2 | Date: 2019-06-03T00:00:00Z 3 | Tags: Linux 4 | 5 | For the first time, ever, I've been using Linux on raw hardware for a 6 | couple months. The experience has been utterly boring and wonderful. 7 | 8 | 9 | 10 | Sometime in the 1990s, I upgraded a desktop computer from something 11 | like Windows 3.1 to Windows 95. The result was... not good. Some 12 | hardware wasn't working well. I remember cursing Plug and Play. I 13 | figured out some things, but that computer never was fully right. 14 | Eventually I replaced it. 15 | 16 | That experience left me feeling, I'd rather do any major operating 17 | system upgrade as a result of buying a new computer. 18 | 19 | There was a time when many Windows laptops didn't "just work" when it 20 | came to things like power management or wireless networking. 21 | Eventually that improved. But then Linux had a similar reputation for 22 | many years. And I really don't want to spend time futzing with basics. 23 | 24 | My previous dalliances with Linux include: 25 | 26 | - Using Linux in a Virtual Box VM. Generally works, maybe slowly. 27 | 28 | - Using [crouton] on a Chomebook Pixel. For awhile, this seemed like 29 | the best of both worlds, to me. Rely on Chrome OS to have decent 30 | drivers for power, networking, trackpad. 31 | 32 | - Using Windows Subsystem for Linux. This is actually what led me to 33 | go all-in. Around New Year's, I got a Windows laptop with the idea 34 | that I'd test Racket Mode on Windows, that way. I got side-tracked 35 | by playing with WSL. Then I got frustrated by WSL support for Emacs 36 | getting worse not better over time. 37 | 38 | About three months ago, I followed this [guide]: "Installing Ubuntu 39 | 18.04 LTS on a Lenovo ThinkPad X1 Carbon Gen 6". 40 | 41 | [crouton]: https://github.com/dnschneid/crouton 42 | [guide]: https://thornelabs.blog/posts/installing-ubuntu-1804-lts-on-a-lenovo-thinkpad-x1-carbon-gen-6.html 43 | 44 | Since then, I've hit a few "milestones" where I expected I might have my first bad experience: 45 | 46 | - Suspend/resume? It worked. 47 | 48 | - Connecting an external 4K monitor via a USB-C cable? Both displays 49 | working fine. Laptop getting charged. 50 | 51 | - Printing? It found the driver for the printer and printed. Yawn. 52 | 53 | Maybe it's beginner's luck. Maybe I'm in the honeymoon phase. I keep 54 | waiting for the other shoe to drop. 55 | 56 | -------------------------------------------------------------------------------- /src/posts/2019/07/future-of-racket.md: -------------------------------------------------------------------------------- 1 | Title: Future of Racket 2 | Date: 2019-07-28T00:00:00Z 3 | Tags: Racket 4 | 5 | For most of the last decade I've [made things in Racket] -- including 6 | making [tools] and [tutorials] to support other people making things 7 | in Racket. 8 | 9 | [made things in Racket]: https://www.greghendershott.com/2014/09/written-in-racket.html 10 | [tools]: https://www.racket-mode.com/ 11 | [tutorials]: https://www.greghendershott.com/fear-of-macros/ 12 | 13 | At RacketCon 2019, Aaron Turon gave the keynote about the Rust 14 | community. 15 | 16 | That afternoon, I had a talk about Racket Mode for Emacs. 17 | 18 | The next morning? 19 | 20 | 21 | 22 | Matthew Flatt gave his State of Racket talk. He said the community is 23 | growing well. Next, he proposed, let's change the surface syntax away 24 | from lispy s-expressions, to something that will be a lower barrier of 25 | entry to new users. 26 | 27 | He said it's just a proposal. It would probably take a couple years. 28 | `#lang racket` programs would continue to work meanwhile and after the 29 | change. But eventually, in his proposal, the Racket culture should use 30 | this new syntax. ("Culture" meaning documentation, discussions, books, 31 | tutorials, tools.) 32 | 33 | To be fair, he gave people a lot of time to share their initial 34 | reactions and answer their questions. 35 | 36 | Also to be fair, if you have some mileage on your odometer, your 37 | pattern-recognition machinery will quickly assign high probabilities 38 | to various outcomes. 39 | 40 | I'm concerned the change won't help grow the community; instead hurt 41 | it. I've explained why in a few racket-users posts.[^posts] I won't 42 | here. I don't think it matters. All you can say is, "Hey, that stove 43 | is hot, you might not want to touch it." 44 | 45 | I don't regret contributing things and trying to help the community 46 | grow. I learned a lot! I had fun! On the other hand, if I'll no longer 47 | learn or have fun? I'm not getting any younger. There are other things 48 | to do. 49 | 50 | In the near future, I'm spending more time with other programming 51 | languages (currently Rust and Haskell). Possibly less time 52 | programming, at all. We'll see. 53 | 54 | Who cares? Why write this post? 55 | 56 | - To have a background explanation I can simply link to from things 57 | like README files, as needed from time to time. 58 | 59 | - Emotionally, I can't quite bring myself to say, "So Long and Thanks 60 | for All the Standard-Fish!" That feels too abrupt. But it's probably 61 | realistic about my current level of mental separation. So if there 62 | are a few people making plans, in part based on my own plans, I want 63 | to be up-front. 64 | 65 | Finally, I sincerely wish all the best for the Racket core team and 66 | the community, which in my experience consists of amazing people with 67 | good intentions, open hearts, and sharp brains. 68 | 69 | [^posts]: Update 2019-08-10: Now that some people are linking to this blog post? Here are links to my racket-users posts: [blah](https://groups.google.com/d/msg/racket-users/ewWuCvbe93k/s_P94KBmCAAJ), [blah](https://groups.google.com/d/msg/racket-users/ewWuCvbe93k/dEHdpFZnCAAJ), [blah](https://groups.google.com/d/msg/racket-users/ewWuCvbe93k/aiskVQd1CAAJ), [blah](https://groups.google.com/d/msg/racket-users/ewWuCvbe93k/WpIl8bsWCgAJ), and [blah](https://groups.google.com/d/msg/racket-users/vN_1uUJZnXo/5bXiMEBvCgAJ). Also I updated this to "linkify" some other things and add Haskell to the list of languages I'm mostly focusing on now. 70 | -------------------------------------------------------------------------------- /src/static/.well-known/keybase.txt: -------------------------------------------------------------------------------- 1 | ================================================================== 2 | https://keybase.io/greghendershott 3 | -------------------------------------------------------------------- 4 | 5 | I hereby claim: 6 | 7 | * I am an admin of http://www.greghendershott.com 8 | * I am greghendershott (https://keybase.io/greghendershott) on keybase. 9 | * I have a public key with fingerprint 2E96 43B5 C285 5815 6140 8A12 81A9 7656 9121 EC46 10 | 11 | To do so, I am signing this object: 12 | 13 | { 14 | "body": { 15 | "key": { 16 | "eldest_kid": "010117c0b4d055dfbadc72b6c93fd9fb807fca09fbfb1202481f587521198a1358c20a", 17 | "fingerprint": "2e9643b5c285581561408a1281a976569121ec46", 18 | "host": "keybase.io", 19 | "key_id": "81a976569121ec46", 20 | "kid": "010117c0b4d055dfbadc72b6c93fd9fb807fca09fbfb1202481f587521198a1358c20a", 21 | "uid": "2b21f453c2aecd3a45e65b9563f3c500", 22 | "username": "greghendershott" 23 | }, 24 | "merkle_root": { 25 | "ctime": 1507508299, 26 | "hash_meta": "cd7c7c560e0099f0ffc10d12d289e236e4657cfddcb382d0de16b1c57ed83724", 27 | "seqno": 1542411 28 | }, 29 | "revoke": { 30 | "sig_ids": [ 31 | "8db48c55fc0235251721230bf4d8f0f6fd5de131ff0001960c0a5db3f2d2c7000f" 32 | ] 33 | }, 34 | "service": { 35 | "hostname": "www.greghendershott.com", 36 | "protocol": "http:" 37 | }, 38 | "type": "web_service_binding", 39 | "version": 1 40 | }, 41 | "ctime": 1507508320, 42 | "expire_in": 157680000, 43 | "prev": "c74e0fe771ccab43a43d706814d6281423fbfaf1e11ee65c5a99867485e8c7e6", 44 | "seqno": 22, 45 | "tag": "signature" 46 | } 47 | 48 | which yields the signature: 49 | 50 | -----BEGIN PGP MESSAGE----- 51 | Version: Keybase OpenPGP v2.0.74 52 | Comment: https://keybase.io/crypto 53 | 54 | yMLAAnicrVNdaB1FGL1pTKqRiJVKDVSFbaTYxDAzO397A6a0oiANFaRaTe11d+ab 55 | e7c39+7N7iZpKaloowVj8SHWlAgBiWBpaUERbGtpS2hE8D4IfQj1hyBFJUULjb6I 56 | Njp7qYjio0/zw/nOOXO+by61N+famk6dmN7Wz1a3N30+d2g499yXF1/c7wSR3ufk 57 | 9ztlaCwwqCFJC+VQO3kHYYSxUCigGjGmTeBrJUjAleca7ZlAImGUj+zOBJggQiU2 58 | TApGMPakj10mFUG+0+2YsFqEuBaH1dTSEvA4dQOmiGRMYsYxRRZOJPY9wRn3MMGg 59 | KLeFpSjJKqy5wE+gJ4zsnT0UGvb+A/8/+x5u0JGAYEOZq4gPSrs+ZcBZ4DHuGlcx 60 | hDJgAnHVr4BFF2MolqCqIU5KUZo6Y91OBeLyIBTiKEqzjFUaZkjMkGBIEs+z7/ST 61 | UqECqW8JlBZKKMYRIOR5BhmjMNKYaCI9IC4HyplQRmsVuJJopAHzACsmQEtXEGrt 62 | JDBUjTIFSijG1kEMI1EZMvEkLNr0Eic/4EgdUKkYMwoRlxGGBcHERYGhWlpZbjSz 63 | 3C42BiGEPY4U8pkOXGOtKGHvjPPCWCYWj4SqQZ6161YMo6OjPf+KokdFFWuuFkdp 64 | pKJBCyqlaS2fJZTuqzWKICjcoisEYVXbsbEFI7Y+jKr2PRb5z/Bcgrod2FsLYyiE 65 | GYIJLq0zlMnASJamoIAMCIGV8gNqu+dqgbjEVHM7cZS4dgp8gwFjsG1VzPc8yQWV 66 | DKQSwP9OkxDr0y9aTpth1U+HY3DG5i7uui3X1JZrbVmV/adc2x13//XLnp6/PTfb 67 | AYtPHPh95tontYmxhw7ueKe5aaD11GB9cnLo+77e8UcWjvS//DZ6fabzyqZDe5LL 68 | K+M3eo/f/+0vrcs/vL9Mrq+6lv/0qwdWL3V0vLHr8KWpe986vPanO3ux9+C2LXh9 69 | x/TakesTLTu3L2946vTijaNdJ9e3/FavvPbslY27v5juWggPks3JUt/Gy6Nbz96T 70 | 0oUj81vvq9WnBvp7Xl1T3FM4mRwd/3H3hy1nD0yp+vwf51b6Zt+b/Ob5lWPnT/98 71 | /OvyhiX/2KOLv5bGX3n4zGMX1nXN7Xy8uW1Cf7Cl/ebsRzuuxlPy3Gd3FWbq5dnk 72 | yQtD6zrffOmZ2t6+m9FcZ/3Ewub5j6+e4e9u/+78n64bhgc= 73 | =hTNe 74 | -----END PGP MESSAGE----- 75 | 76 | And finally, I am proving ownership of this host by posting or 77 | appending to this document. 78 | 79 | View my publicly-auditable identity here: https://keybase.io/greghendershott 80 | 81 | ================================================================== 82 | -------------------------------------------------------------------------------- /src/static/CNAME: -------------------------------------------------------------------------------- 1 | www.greghendershott.com 2 | -------------------------------------------------------------------------------- /src/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greghendershott/blog/d60793a25498792294e4f0c7dc8a251b8b94f621/src/static/favicon.ico -------------------------------------------------------------------------------- /src/static/img/1x1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greghendershott/blog/d60793a25498792294e4f0c7dc8a251b8b94f621/src/static/img/1x1.gif -------------------------------------------------------------------------------- /src/static/img/check-syntax.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greghendershott/blog/d60793a25498792294e4f0c7dc8a251b8b94f621/src/static/img/check-syntax.gif -------------------------------------------------------------------------------- /src/static/img/chrome-extensions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greghendershott/blog/d60793a25498792294e4f0c7dc8a251b8b94f621/src/static/img/chrome-extensions.png -------------------------------------------------------------------------------- /src/static/img/feed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | image/svg+xml 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/static/img/google-nexus-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greghendershott/blog/d60793a25498792294e4f0c7dc8a251b8b94f621/src/static/img/google-nexus-4.png -------------------------------------------------------------------------------- /src/static/img/grumpy-regexp-parser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greghendershott/blog/d60793a25498792294e4f0c7dc8a251b8b94f621/src/static/img/grumpy-regexp-parser.png -------------------------------------------------------------------------------- /src/static/img/navbar-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greghendershott/blog/d60793a25498792294e4f0c7dc8a251b8b94f621/src/static/img/navbar-logo.jpg -------------------------------------------------------------------------------- /src/static/img/racket-mode-step-debugger.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greghendershott/blog/d60793a25498792294e4f0c7dc8a251b8b94f621/src/static/img/racket-mode-step-debugger.gif -------------------------------------------------------------------------------- /src/static/img/suggested-post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greghendershott/blog/d60793a25498792294e4f0c7dc8a251b8b94f621/src/static/img/suggested-post.png -------------------------------------------------------------------------------- /src/static/img/the-fuck-was-that-is-this.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greghendershott/blog/d60793a25498792294e4f0c7dc8a251b8b94f621/src/static/img/the-fuck-was-that-is-this.gif --------------------------------------------------------------------------------