Skip to content
January 24, 2011 / cdsmith

My Dream GHCi Session, Take 2

About three or four years ago, I write a blog entry describing my dream GHCi session; basically a wish list of things I wish GHCi did for me.  Well, since today is official program announcement day for Google Summer of Code 2011, I’m revisiting this subject in hopes of roping some hapless college students into doing my bidding inspiring others.

So here, revised and updated, is what I’d love to see next time I start GHCi.

$ ghci MyFile.hs
GHCi, version 9.9.02013010: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
[1 of 1] Compiling MyFile              ( MyFile.hs, interpreted )

MyFile.hs:13:5: Not in scope: `callDatabase'

MyFile.hs:112:16:
    No instance for (Fractional Integer)

Partially failed, modules loaded: MyFile
*MyFile> :t foo
Warning: foo depends on callDatabase, which was not found.
Warning: Inferred type may be too general.

foo :: forall a b. (SQL a, SQLData b) => a -> t -> STM [b]

*MyFile> foo (Just 7) (DB $ connectPostgreSQL "dbname=fluffybunnies")

*** Exception: callDatabase: unresolved compile error

*MyFile> foo Nothing NoDB  -- doesn't force value for callDatabase

Just 42

*MyFile> :list 110-114

110:       return [a]
111:
112: bar x = (baz x) / 5
113:
114: superCoolFunction = unsafePerformIO $ do

*MyFile> :t baz
baz :: forall a. a -> Integer
*MyFile> let bar = fromIntegral (baz x) / 5
*MyFile> let callDatabase = undefined :: (SQL a, DBDriver b) => b -> a -> STM TVar
*MyFile> :recomp MyFile
[1 of 1] Compiling MyFile              ( MyFile.hs, interpreted )
Ok, modules loaded: MyFile

*MyFile> :t foo

foo :: forall a b. (SQL a, DBDriver t, SQLData b) => a -> t -> STM [b]

*MyFile> let data MyTree a = Leaf a | Branch (MyTree a) (MyTree a)
*MyFile> instance Monoid (MyTree a) where mappend = Branch
*MyFile> Leaf 6 `mappend` Leaf 7
Branch (Leaf 6) (Leaf 7)

*MyFile> :t 6              -- check type by itself
6 :: Num a => a

*MyFile> :t {{ 6 }} / 5  -- check type in context
6 :: Fractional a => a

MyFile> superCoolFunction
... BOOM!

Notes:

  1. It’s nice that I had to remove some parts of my old list because they’ve since been implemented!  In particular, the :list command was added with the GHCi debugger; GHCi now does multiline blocks with :{ and :}, and one of my requests for a better error message seems to no longer be an issue.  This is nice to see!
  2. The first request is for GHCi to continue doing its best to load a module when something goes wrong.  Instead of punting entirely, it could still put those bits in scope that work correctly, and just report the errors on the bits that don’t work.  This should probably only work for the open module (the one with an asterisk by it in the prompt.)
  3. Going further in this direction, the behavior of the first ‘:t foo’ suggests that GHCi may be able to report guess (possibly incorrect) at piecing together the rest of the file in the event of errors.  Here, callDatabase is not in scope, so type checking and inference can’t possible proceed correctly.  However, type checking and inference can still proceed speculatively, by assuming callDatabase :: forall a. a!  A warning is needed, of course, since the given type will likely be insufficiently specific once callDatabase is brought into scope.
  4. The next two lines record what happens to callDatabase (you know, that symbol that wasn’t in scope).  Basically, it’s treated as a bottom (i.e., undefined) value, so if it’s forced, then you get an exception.  If it’s not forced, you can still use foo.
  5. The :list command now exists for symbols or single lines.  I still think a line range is a reasonable idea.
  6. The next few lines show me interactively fixing the errors in MyFile.hs and using a made-up command “:recompile” to try to rebuild the currently loaded source file, except with  currently defined symbols from the command line added to the top-level scope to replace those from the file where relevant.  With that done, I can now ask for the type of foo, and get the true type, which is less general than the guess from earlier.
  7. Next, I define a data type and an instance on the command line.  I’d like to be able to define classes as well.  These may not even need to be GHCi-specific enhancements to Haskell.  Local instance declarations, for example, are already suggested (with restrictions would be needed to preserve principal types and coherence) by Oleg in the now-famous functional pearl on implicit configurations.  Their use within GHCi is another potential motivating example.
  8. The last bit I threw in is something I’ve found myself wanting far more often than I’d expect: a way to ask GHCi for the type of an expression in context.  So while 6 has type Num a => a as expected, if taken in the given context, it turns out to have the more restricted type Fractional a => a, because of the upcoming division.  I chose {{ and }} as markers in the unverified belief that at least “{{” can never occur in legal Haskell code, so they clearly form a subexpression marker instead of actual code.

Most of these are bigger projects, perhaps, than GSoC gives time for… or at a minimum, would need an applicant already familiar with GHC.  But they sure would be convenient.

4 Comments

Leave a Comment
  1. Anonymous / Jan 24 2011 10:22 pm

    GHC HEAD (7.1) now also supports multiline commands with :set +m.

    There will also be a :script [n] filename commands that runs a file as a sequence of actions at the ghci> command line.

  2. MightyByte / Jan 25 2011 8:04 am

    One thing I’ve wanted numerous times is something like “:w” that will save all (or appropriate) of the ghci session to a file for replay, editing, etc.

  3. augustss / Jan 25 2011 8:15 am

    I might implement some of those. Not in ghci, I’m afraid.

  4. beroal / Jan 25 2011 9:07 am

    Hey, you did not say what “BOOM” is supposed to mean!

Leave a reply to Anonymous Cancel reply