ff107268c4e3

Links, etc
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Wed, 21 Aug 2024 09:10:34 -0400
parents 3bd8a34568c3
children 578872d23f06
branches/tags (none)
files content/blog/2008/04/shooting-girl-jam.markdown content/blog/2008/08/beauty-in-computer-science-recursion.markdown content/blog/2008/08/negative-space-dancing.markdown content/blog/2009/02/how-i-shoot-dances.markdown content/blog/2010/09/coming-home-to-vim.markdown content/blog/2012/10/why-i-two-space.markdown content/blog/2022/04/fun-with-macros-do-file.markdown content/links.markdown content/resume.markdown

Changes

--- a/content/blog/2008/04/shooting-girl-jam.markdown	Fri Jun 07 09:49:58 2024 -0400
+++ b/content/blog/2008/04/shooting-girl-jam.markdown	Wed Aug 21 09:10:34 2024 -0400
@@ -3,6 +3,7 @@
 :snip "I'm finally getting the kind of dancing photos I want."
 :date "2008-04-29T18:31:16Z"
 :draft nil
+:hidden t
 
 )
 
--- a/content/blog/2008/08/beauty-in-computer-science-recursion.markdown	Fri Jun 07 09:49:58 2024 -0400
+++ b/content/blog/2008/08/beauty-in-computer-science-recursion.markdown	Wed Aug 21 09:10:34 2024 -0400
@@ -3,6 +3,7 @@
 :snip "Why I love what I do."
 :date "2008-08-29T15:30:38Z"
 :draft nil
+:hidden t
 
 )
 
--- a/content/blog/2008/08/negative-space-dancing.markdown	Fri Jun 07 09:49:58 2024 -0400
+++ b/content/blog/2008/08/negative-space-dancing.markdown	Wed Aug 21 09:10:34 2024 -0400
@@ -3,6 +3,7 @@
 :snip "It’s not just for artsy kids."
 :date "2008-08-31T15:33:57Z"
 :draft nil
+:hidden t
 
 )
 
--- a/content/blog/2009/02/how-i-shoot-dances.markdown	Fri Jun 07 09:49:58 2024 -0400
+++ b/content/blog/2009/02/how-i-shoot-dances.markdown	Wed Aug 21 09:10:34 2024 -0400
@@ -3,6 +3,7 @@
 :snip "Slow shutter and flash."
 :date "2009-02-09T18:04:36Z"
 :draft nil
+:hidden t
 
 )
 
--- a/content/blog/2010/09/coming-home-to-vim.markdown	Fri Jun 07 09:49:58 2024 -0400
+++ b/content/blog/2010/09/coming-home-to-vim.markdown	Wed Aug 21 09:10:34 2024 -0400
@@ -1,6 +1,6 @@
 (
 :title "Coming Home to Vim"
-:snip "I'm sorry I ever left, baby."
+:snip "I'm sorry I ever left."
 :date "2010-09-20T18:15:00Z"
 :draft nil
 
--- a/content/blog/2012/10/why-i-two-space.markdown	Fri Jun 07 09:49:58 2024 -0400
+++ b/content/blog/2012/10/why-i-two-space.markdown	Wed Aug 21 09:10:34 2024 -0400
@@ -3,6 +3,7 @@
 :snip "You can pry my extra spaces from my cold, dead hands."
 :date "2012-10-12T10:10:00Z"
 :draft nil
+:hidden t
 
 )
 
--- a/content/blog/2022/04/fun-with-macros-do-file.markdown	Fri Jun 07 09:49:58 2024 -0400
+++ b/content/blog/2022/04/fun-with-macros-do-file.markdown	Wed Aug 21 09:10:34 2024 -0400
@@ -1,256 +1,31 @@
-(:title "Fun with Macros: Do-File"
- :snip "Part 3 in a series of short posts about fun Common Lisp Macros."
- :date "2022-04-19T13:45:00Z"
- :draft nil)
+(:title "Fun with Macros: With-EOF-Handled"
+ :snip "Part 4 in a series of short posts about fun Common Lisp Macros."
+ :date "2024-06-23T13:45:00Z"
+ :draft t)
 
-It's been a while, but it's time to take a look at another fun little Common
-Lisp macro with some interesting things inside it: `do-file`.
+It's been a while (again), but it's time to take a look at another little Common
+Lisp macro that I find myself using fairly often: `with-eof-handled`.
 
 <div id="toc"></div>
 
 ## Usage
 
-The macro we'll be taking a look at today is called `do-file`.  It's used to
-open a file and iterate over the contents using a reader function, saving you
-some tedious boilerplate.
-
-First let's look at some examples of how you could use it.  Processing each
-line of a file is the default:
-
-```lisp
-(do-file (line "foo.txt")
-  (unless (string= "" line)
-    (write-line (string-upcase line))))
-```
-
-Using a different reader function and [another
-macro](/blog/2018/05/fun-with-macros-gathering/) to gather data from inside the
-iteration:
-
-```lisp
-(gathering
-  (do-file (n :reader #'read-integer)
-    (when (primep n)
-      (gather n))))
-```
-
-Passing along options to the underlying `open`, and returning early:
-
-```lisp
-(do-file (form "foo.lisp" :reader #'read :external-format :EBCDIC-US)
-  (when (eq form :stop)
-    (return :stopped-early))
-  (print form))
-```
-
-All of these could of course be done in other ways.  You could have a separate
-function that reads the file into a sequence and then pass that to `mapcar` or
-something else, but it can be wasteful to cons up the entire list if you're only
-going to process items and don't need to retain then (or if you're going to stop
-early).
-
-You could also write a `mapc-file` that takes a function instead of making this
-a macro, but sometimes it's nice to not have to wrap things in a thunk.  It's
-probably worth having that function as an additional tool in the toolbox though!
-
-## Implementation
-
-Here's the full implementation of the macro:
-
-```lisp
-(let ((eof (gensym "EOF")))
-  (defmacro do-file ((symbol path
-                      &rest open-options
-                      &key (reader '#'read-line) &allow-other-keys)
-                     &body body)
-    "Iterate over the contents of `file` using `reader`.
-
-    During iteration, `symbol` will be set to successive values read from the
-    file by `reader`.
-
-    `reader` can be any function that conforms to the usual reading interface,
-    i.e. anything that can handle `(read-foo stream eof-error-p eof-value)`.
-
-    Any keyword arguments other than `:reader` will be passed along to `open`.
-
-    If `nil` is used for one of the `:if-…` options to `open` and this results
-    in `open` returning `nil`, no iteration will take place.
-
-    An implicit block named `nil` surrounds the iteration, so `return` can be
-    used to terminate early.
-
-    Returns `nil`.
-
-    Examples:
-
-      (do-file (line \"foo.txt\")
-        (print line))
-
-      (do-file (form \"foo.lisp\" :reader #'read :external-format :EBCDIC-US)
-        (when (eq form :stop)
-          (return :stopped-early))
-        (print form))
-
-      (do-file (line \"does-not-exist.txt\" :if-does-not-exist nil)
-        (this-will-not-be-executed))
-
-    "
-    (let ((open-options (alexandria:remove-from-plist open-options :reader)))
-      (alexandria:with-gensyms (stream)
-        (alexandria:once-only (path reader)
-          `(when-let ((,stream (open ,path :direction :input ,@open-options)))
-             (unwind-protect
-                 (do ((,symbol
-                       (funcall ,reader ,stream nil ',eof)
-                       (funcall ,reader ,stream nil ',eof)))
-                     ((eq ,symbol ',eof))
-                   ,@body)
-               (close ,stream))))))))
-```
-
-There are a few interesting things to talk about here.
-
-### Let Over Defmacro
-
-The very first line is unusual: instead of the `defmacro` being the top level
-form, we wrap it in a `let` to generate one single unique EOF sentinel object:
-
-```lisp
-(let ((eof (gensym "EOF")))
-  (defmacro do-file (…)
-    …))
-```
-
-We could put the `let` inside the macro, but then we'd be generating a separate
-EOF object for every use of the macro, which is wasteful.
-
-### &rest and &key
-
-Note how the argument list of the macro takes both `&rest` and `&key` arguments, and uses
-`&allow-other-keys` to let the macro take arbitrary keyword arguments
+Common Lisp has several functions for reading from character streams, e.g.
+`read-char` and `read-line`.  The arguments for these functions follow a common
+pattern where they take `(&optional input-stream eof-error-p eof-value)` (and
+one more argument that's not relevant here). By default, calling one of these
+functions will signal an error if an end of file occurs.  You can change this by
+passing true as the `eof-error-p` argument (which, despite its `-p` suffix
+should be a (generalized) boolean, *not* a predicate), in which case the
+`eof-value` will be returned instead of signaling an error.
 
 
 ```lisp
-(defmacro do-file ((symbol path
-                    &rest open-options
-                    &key (reader '#'read-line) &allow-other-keys)
-                   &body body)
-  (let ((open-options (alexandria:remove-from-plist open-options :reader)))
-    …
-    (when-let ((,stream (open ,path :direction :input ,@open-options)))
-      …)))
-```
 
-We pass along any keyword arguments we get (aside from the special `:reader`
-argument for this macro) to `open`.  Using `&allow-other-keys` means we don't
-need to hardcode all the possible options to `open`, and also allows for
-additional implementation-specific options to be passed to `open` if the user
-wants.
-
-We could have omitted the keyword arguments entirely, taken the arguments as
-a raw `&rest`, and pulled out `:reader` ourselves with `getf`.  But doing it
-this way means we don't have to fiddle around doing that, and also can also
-provide slightly nicer documentation in an editor when it shows the macro's
-argument list in the status bar.  We'll also get a nicer error if we
-accidentally pass an odd number of keyword arguments.
-
-One more thing before we move on: note the extra level of quoting for the
-`(reader '#'read-line)` default value.  It's important to remember that this is
-a *macro*, and so when someone writes `(do-file (… :reader #'foo) …)` the macro
-isn't getting the *function* `foo` because it's not evaluated yet, it's getting
-the *list* `(function foo)`.  But the default value is *evaluated* when the
-argument is missing, so we need the extra layer of quoting to make sure the
-result makes sense and matches what we'd be getting normally.
-
-### Macros Using Macros
-
-We use `with-gensyms` and `once-only` from Alexandria to maintain good hygiene
-in the macro.  We also use [`when-let`](/blog/2018/07/fun-with-macros-if-let/)
-to avoid some more boilerplate:
-
-```lisp
-(defmacro do-file (…)
-  (alexandria:with-gensyms (stream)
-    (alexandria:once-only (path reader)
-      `(when-let ((,stream (open ,path :direction :input ,@open-options)))
-         (unwind-protect
-             (do …)
-           (close ,stream))))))
 ```
 
-### Don't Loop
 
-Finally we get to the meat of the macro:
-
-```lisp
-(do ((,symbol
-     (funcall ,reader ,stream nil ',eof)
-     (funcall ,reader ,stream nil ',eof)))
-    ((eq ,symbol ',eof))
-  ,@body)
-```
-
-Unfortunately we need to use the tedious `do` instead of `loop` here to avoid an
-annoying bug: if we expanded into a `loop` call, and the user is calling this
-from their *own* loop, and they use `(loop-finish)` in the body code, then it
-would finish *our* loop instead of *their* loop, which would very confusing.
-
-Imagine the user wrote this very contrived example:
-
-```lisp
-(defun find-the-cat (&rest paths)
-  (loop
-    :with result = nil
-    :for (path . remaining) :on paths
-    :for i :from 1
-    :do (do-file (line path)
-          (when (string= line "meow")
-            (setf result path)
-            (loop-finish))) ;; This should obviously go to the finally below.
-    :finally
-    (when result
-      (format t "Found cat after searching ~D files (did not search ~D other~:P)."
-              i (length remaining))
-      (return result))))
-```
-
-If `do-file` expanded into a `loop` form, then the `(loop-finish)` would only
-terminate *that* loop.
-
-The same issue kind of applies with the implicit block named `nil` around `do`.
-But this is much less surprising for a macro named `do-…`, and we've documented
-it in the docstring, so that's probably okay.
-
-### Repetition Allergies
-
-Using `do` here is a little annoying because the init form and the step form are
-exactly the same.  If you're allergic to repeating yourself you could use `#n=`
-and `#n#` reader macros to get around it:
-
-```lisp
-(do ((,symbol #1=(funcall ,reader ,stream nil ',eof) #1#))
-    ((eq ,symbol ',eof))
-  ,@body)
-```
-
-I find this more confusing than helpful, but to each their own.
+## Implementation
 
 ## Result
 
-We've got a nice little macro for easily iterating over files piece by piece.
-It can take any reader function that conforms to the usual `(read-foo stream
-eof-error-p eof-value)` interface, which means we can write our own reader
-functions that will compose nicely with the macro.
-
-We'll end with an exercise for the reader: figure out how to support
-declarations correctly.  For example:
-
-```lisp
-(do-file (n "numbers.txt" :reader #'read-fixnum)
-  (declare (type fixnum n))
-  (when (primep n)
-    (collect (* n n))))
-```
-
-Hint: you'll need to deal with the sentinel value a bit differently so it
-doesn't contaminate the type of the bound variable.
--- a/content/links.markdown	Fri Jun 07 09:49:58 2024 -0400
+++ b/content/links.markdown	Wed Aug 21 09:10:34 2024 -0400
@@ -21,6 +21,48 @@
 * [Red Blob Games](http://www.redblobgames.com/): Wonderful blog with *really* good posts about lots of different video game-related topics.
 * [The Digital Antiquarian](http://www.filfre.net/): Immense amount of information on the history of "computer entertainment".
 
+Articles
+--------
+
+These are some articles that have left a lasting impression on me (this doesn't
+mean I endorse everything in them).
+
+* [Black Triangles](https://rampantgames.com/blog/?p=7745)
+* [Execution in the Kingdom of Nouns](https://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html)
+* [Programming's Dirtiest Little Secret](https://steve-yegge.blogspot.com/2008/09/programmings-dirtiest-little-secret.html)
+* [The Cardinal Rule of Functions](https://courses.cs.northwestern.edu/325/readings/cardinal-rule.html)
+* [Thinking about Thinking](https://www.datapacrat.com/Opinion/Reciprocality/r0/Day1.html)
+* [How to Report Bugs Effectively](https://www.chiark.greenend.org.uk/~sgtatham/bugs.html)
+* [The Best of Intentions](https://www.nhplace.com/kent/PS/EQUAL.html)
+* [The Rise of Worse is Better](https://www.dreamsongs.com/RiseOfWorseIsBetter.html)
+* [Handwriting Repair](https://sites.google.com/view/briem/free-books/handwriting-repair)
+* [Programming Sucks](https://www.stilldrinking.org/programming-sucks)
+* [The Long, Painful History of Time](https://naggum.no/lugm-time.html)
+* [The Elves Leave Middle Earth — Sodas Are No Longer Free](https://steveblank.com/2009/12/21/the-elves-leave-middle-earth-%E2%80%93-soda%E2%80%99s-are-no-longer-free/)
+* [A Mathematician's Lament](https://worrydream.com/refs/Lockhart_2002_-_A_Mathematician's_Lament.pdf)
+* [A Taxonomy of Tech Debt](https://technology.riotgames.com/news/taxonomy-tech-debt)
+* [Improper Nouns](https://siderea.dreamwidth.org/1773806.html)
+* [Why numbering should start at zero](https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/EWD831.html)
+* [Unix Recovery Legend](https://www.ecb.torontomu.ca/~elf/hack/recovery.html)
+* [Fixing Unix/Linux/POSIX Filenames](https://dwheeler.com/essays/fixing-unix-linux-filenames.html)
+* [How Complex Systems Fail](https://how.complexsystems.fail/) and [The Hallmarks of Cancer](https://www.sciencedirect.com/science/article/pii/S0092867400816839)
+* [Software Disenchantment](https://tonsky.me/blog/disenchantment/)
+* [The short, tormented life of computer genius Phil Katz](http://www.bbsdocumentary.com/library/CONTROVERSY/LAWSUITS/SEA/katzbio.txt)
+* [The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets](https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/)
+* [The case of the 500-mile email](https://www.ibiblio.org/harris/500milemail.html)
+* [Project managers, ducks, and dogs marking territory](https://rachelbythebay.com/w/2013/06/05/duck/)
+* [Lisping at JPL](https://flownet.com/gat/jpl-lisp.html)
+* [Unix as IDE](https://blog.sanctum.geek.nz/series/unix-as-ide/)
+* [The illustrated guide to a Ph.D.](https://matt.might.net/articles/phd-school-in-pictures/)
+* [I want off Mr. Golang's Wild Ride](https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-ride)
+* [PHP: a fractal of bad design](https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/)
+* [I should have loved biology](https://jsomers.net/i-should-have-loved-biology/)
+* [Reverse Engineering the source code of the BioNTech/Pfizer SARS-CoV-2 Vaccine](https://berthub.eu/articles/posts/reverse-engineering-source-code-of-the-biontech-pfizer-vaccine/)
+* [Computers can be understood](https://blog.nelhage.com/post/computers-can-be-understood/)
+* [What if I were 1% charged?](https://gravityandlevity.wordpress.com/2013/05/22/what-if-i-were-1-charged/)
+* [Please, enough with the dead butterflies!](https://www.emilydamstra.com/please-enough-dead-butterflies/)
+
+
 YouTube Channels
 ----------------
 
@@ -29,21 +71,6 @@
 * [Game Maker's Toolkit](https://www.youtube.com/user/McBacon1337): Videos about game design.
 * [Makin' Stuff Look Good](https://www.youtube.com/channel/UCEklP9iLcpExB8vp_fWQseg): Videos about how to make things look good in Unity.  The best part of the channel is the "shader case studies" where he looks at visual effects in various games and shows how to recreate them with a shader.
 
-Subreddits
-----------
-
-* [/r/common\_lisp](http://reddit.com/r/common_lisp/)
-* [/r/emudev](http://reddit.com/r/emudev/)
-* [/r/lisp](http://reddit.com/r/lisp/)
-* [/r/proceduralgeneration](http://reddit.com/r/proceduralgeneration/)
-* [/r/roguelikedev](http://reddit.com/r/roguelikedev/)
-* [/r/worldbuilding](http://reddit.com/r/worldbuilding/)
-
-Tools
------
-
-* [Cheap Bots Done Quick](http://cheapbotsdonequick.com/): An online tool for making Twitter bots with [Tracery](http://tracery.io/).
-
 Game Development Beginner Resources
 -----------------------------------
 
--- a/content/resume.markdown	Fri Jun 07 09:49:58 2024 -0400
+++ b/content/resume.markdown	Wed Aug 21 09:10:34 2024 -0400
@@ -2,13 +2,17 @@
  :date "2022-08-27T17:45:00Z"
  :draft nil)
 
-I'm Steve. I'm a currently living in Ann Arbor, MI and pursuing a PhD in
-Bioinformatics at the University of Michigan.
+I'm Steve.  I'm a currently a PhD student at the [University of Michigan][UM]
+living in Ann Arbor, MI.
 
 I graduated from [Rochester Institute of Technology][rit] in 2008 with
-a Bachelor's degree in Computer Science, worked full-time until 2015, then went
-to graduate school.  I graduated from [Reykjavík University][RU] in 2017 with
-a Master's degree in Computer Science.  Since then I've returned to working full-time.
+a Bachelor's degree in Computer Science, then worked full-time until 2015.
+
+I graduated from [Reykjavík University][RU] in 2017 with a Master's degree in
+Computer Science, then returned to working full-time until 2023.
+
+I'm currently pursuing a PhD in Computational Medicine and Bioinformatics at
+the [University of Michigan][UM].
 
 If you'd *really* like to get to know me you should look at the [projects][] and
 [blog posts][] I've written, or just read my code on [GitHub][].
@@ -18,6 +22,7 @@
 [blog posts]: /blog/
 [github]: https://github.com/sjl/
 [RU]: https://www.ru.is/
+[UM]: https://umich.edu/
 
 <div id="toc"/>
 
@@ -40,9 +45,9 @@
 I also gave [a talk](https://youtu.be/UfZvP0v3NbM) at the 2017 European Lisp
 Symposium in Brussels about General Game Playing with Common Lisp.
 
-I've also had significant professional experience programming with [Go][],
+I've had significant professional experience programming with [Go][],
 [Python][], [Django][], [Scala][], [Clojure][], [Java][], [PostgreSQL][], and
-lots of various glue languages like [bash][].  I've used [AWS][] (services like
+lots of various glue languages like [bash][]. I've used [AWS][] (services like
 EC2, S3, RDS, KMS, etc) at several jobs, and managed VPSes myself at others.
 
 I've had some professional experience with frontend languages like