[Haskell-cafe] I fell in love with System.Console.Haskeline.getExternalPrint

Mario Lang mlang at delysid.org
Sun Mar 31 22:50:49 UTC 2019


Since I discovered getExternalPrint, I found a ton of use cases for it.
In particular, it makes it possible to write background threads that
report stuff to the console without disturbing the prompt.

I ended up writing a sort of uGHCi with the help of hint and the
unreleased master branch of haskeline[1] to make externalPrint available
from within the interpreter.  Combined with Shh, this opens the door for
a lot of useful functionality.  Here is a simplified example based
on shell programming with the help of Shh:

  % let watch r p = forkIO . forever $ printProc p >> readIORef r >>= OS.sleep
  % delay <- newIORef 10
  % clock <- watch delay OS.date
  Sat Mar  2 21:32:28 CET 2019
  Sat Mar  2 21:32:38 CET 2019
  % writeIORef delay 5
  Sat Mar  2 21:32:48 CET 2019
  Sat Mar  2 21:32:53 CET 2019
  Sat Mar  2 21:32:58 CET 2019
  Sat Mar  2 21:33:03 CET 2019
  % killThread clock
printProc uses externalPrint from haskeline to print the output of a
shell command to the console without disturbing the prompt.
The OS module in this example simply exports all executables as haskell
functions, thanks to the TH magic from Shh.

I am relying on a pretty crude hack to make this work:

  (rFd, wFd) <- liftIO createPipe
  eprint <- getExternalPrint  -- from haskeline
  liftIO . forkIO . forever $ do
    (s, bc) <- fdRead rFd 1024
    eprint s
  -- ...
  -- define a function in the interpreter using hint
  runStmt $ "let externalPrint s = fdWrite (read " <> show (show wFd) <> ") s >> pure ()"

This hack is basically the whole magic of my own hand-rolled uGHCi.

I'd love to not reinvent the wheel there, and just be able to use
standard GHCi to make use of externalPrint.

Question is, would a similar thing be possible to implement in GHCi
directly, and if so, what would be required to make this work?
I am likely far too much a rooky to get this working on my own, so I am
asking for help.  What steps should I follow to eventually achieve my
goal?  I guess submitting a feature request would be a start.
However, I want progress, so I am wondering:

* The pipe trick is likely too hacky for GHCi.  Are there any other
  portable alternatives for getting data from within the interpreter to
  the haskell process running it?
* Or is there a way to serialize an IO action into the interpreter that I've missed?

The problem here is the boundary between the process that runs
the interpreter, and the interpreter itself.  I am a bit whacky on
terminology here, but as I see it, getExternalPrint returns a function that
has internal state.  So it isn't really possible to make such a function
available from within the interpreter.  Hence, the pipe hack above,
which just sends the *argument* to externalPrint from the interpreter to
the process running it.

Any insights that might help me make that available in standard
GHCi?  I really think this is a pretty unique feature that would enable
all sorts of interesting interactive code.

[1] Haskeline < 0.8.0 doesn't allow to combine IntterpreterT from hint
with InputT because of the way exceptions are done.  The master branch
of haskeline fixes that, so finally you can have a transformer stack
that combines both, allowing for pretty simple interactive haskell
interpreters with readline functionality.  Thanks for that!


More information about the Haskell-Cafe mailing list