Hacker News new | ask | show | jobs
by mananaysiempre 1517 days ago
> I don't really know what "conditions+restarts" is but a few articles landed me into LISP which I find totally unreadable. So, can you point me to some "conditions+restarts" code that I can understand/appreciate easily?

You’ll have to read Lisp, I’m afraid; the best description I know is in the book Practical Common Lisp[1].

(Come on, Lisp syntax is quirky, but it’s not unreadable, and unlike APL or Forth or even Haskell it doesn’t require you to memorize a bunch of semi-meaningless punctuation before you can understand what is going on—it’s pretty wordy usually. I’m not saying you must bring yourself to love writing (f x y) instead of f(x, y), only that adjusting from one to the other should not be particularly hard.)

I mean, I have done a toy Forth implementation, but that is hardly more readable with no experience with the language.

One system that is almost conditions and restarts is 32-bit(!) Win32 SEH, but it is not particularly well-documented and the language bindings usually try rather hard to hide that (though, if you think about it, On Error Resume Next from classic VB is unimplementable on top of bare try/catch).

...

OK, you nerd-sniped me :) Here’s a toy (no subtyping! no introspection! no condition firewall[2]! no tracebacks! no support for native errors! etc.) condition system in Lua (sorry, nested functions in Python are painful):

  -- save as cond.lua
  
  local M = {}
  
  local error, unpack = error, unpack or table.unpack
  local running = coroutine.running
  local stderr = io.stderr
  local exit = os.exit
  local insert, remove = table.insert, table.remove
  
  -- conditions
  
  local handlers = setmetatable({}, {
      __mode = 'k', -- do not retain dead coroutines
      __index = function (self, key) -- no handlers by default
          self[key] = {}; return self[key]
      end,
  })
  
  local function removing(xs, x, ok, ...)
      assert(remove(xs) == x)
      if ok then return ... else error(...) end
  end
  
  -- establish a handler during call
  function M.hcall(h, f, ...)
      local hs = handlers[running()]
      insert(hs, h)
      return removing(hs, h, pcall(f, ...))
  end
  
  -- signal the given condition to currently active handlers
  function M.signal(...)
      local hs = handlers[running()]
      for i = #hs, 1, -1 do hs[i](...) end
  end
  local signal = M.signal
  
  function M.error(...)
      signal(...)
      stderr:write("error: " .. tostring(...) .. "\n")
      exit(1)
  end
  
  function M.warn(...)
      signal(...)
      stderr:write("warning: " .. tostring(...) .. "\n")
  end
  
  -- restarts
  
  -- invoke the given restart
  function M.restart(r, ...)
      local n = select('#', ...); r.n = n
      for i = 1, n do r[i] = select(i, ...) end
      error(r)
  end
  
  local function continue(r, ok, ...)
      if ok then return ok, ... end
      if ... == r then return false, unpack(r, 1, r.n) end
      error(...)
  end
  
  -- establish a restart during call
  function M.rcall(f, ...)
      local r = {}
      return continue(r, pcall(f, r, ...))
  end
  
  return M
Example: DOS-style abort-retry-ignore prompt implemented in the shell with some support in the (mock) I/O system and no support in the application:

  local cond = require 'cond'
  
  -- common condition types (XXX should use proper dynamic variables instead)
  
  local retry, use = nil, nil
  
  -- I/O library
  
  local function _gets()
      if math.random() < 0.5 then cond.error 'lossage' end
      return 'user input'
  end
  
  local function gets()
      local ok, value = cond.rcall(function (_use)
          use = _use
          local ok, value
          repeat ok, value = cond.rcall(function (_retry)
              retry = _retry
              return _gets()
          end) until ok
          return value
      end)
      -- ok or not, we got a value either way
      return value
  end
  
  -- application (knows nothing about errors)
  
  local function app()
      for i = 1, 5 do print(string.format("got: %q", gets())) end
      return "success"
  end
  
  -- shell
  
  local ok, value = cond.rcall(function (abort)
      return cond.hcall(function (err)
          io.stderr:write("I/O error: " .. err .. "\n")
          while true do
              io.stderr:write("[a]bort, [r]etry, [u]se value? ")
              local answer = io.read('*l')
              if answer == 'a' then cond.restart(abort, "aborted") end
              if answer == 'r' then cond.restart(retry) end
              if answer == 'u' then
                  io.stderr:write("value? ")
                  cond.restart(use, io.read('*l'))
              end
          end
      end, app)
  end)
  print(ok, value)
This is not a perfectly accurate semantic model for real condition system, but it should be enough to give a general idea of how these things work and what the advantage over bare unwinding mechanisms like try / throw or Lua’s pcall / error is.

[1] https://gigamonkeys.com/book/beyond-exception-handling-condi...

[2] https://www.nhplace.com/kent/Papers/Condition-Handling-2001....