Roman Cheplyaka

Find out the type of an expression/function with typed holes

Published on March 13, 2014; tags: Haskell

An often asked Haskell question is how to find out a type of a locally defined function or expression.

The classic solutions are to specify the wrong type and read the error message, or get help from a tool like hdevtools.

Here’s a new one: use the typed holes feature of GHC 7.8+.

On the surface, typed holes solve a somewhat different problem: find out the desired type of the yet unwritten code, while we want to find out the actual type of the written code.

But typed holes are very easy to adapt for our needs. The asTypeOf :: a -> a -> a function forces the types of its two arguments to unify. So we can force the type of a hole to be the same as the type of an existing expression — and voilà!

(Note that asTypeOf is exported from Prelude, so typically you don’t have to import anything to bring it in scope. Nor do you have to enable any extensions for typed holes to work.)

In my case I had code like this

  let leave = branch testName False

and I wanted to see what the type of leave is. So I appended `asTypeOf` _:

  let leave = branch testName False `asTypeOf` _

and ghci told me:

*Test.Tasty.Runners.Html> :r
[2 of 2] Compiling Test.Tasty.Runners.Html ( Test/Tasty/Runners/Html.hs, interpreted )

    Found hole ‘_’
      with type: Maybe (String, H.AttributeValue)
                 -> H.AttributeValue
                 -> H.AttributeValue
                 -> H.AttributeValue
                 -> H.Markup
    Relevant bindings include
      leave :: Maybe (String, H.AttributeValue)
               -> H.AttributeValue
               -> H.AttributeValue
               -> H.AttributeValue
               -> H.Markup
        (bound at Test/Tasty/Runners/Html.hs:86:17)
      mkSummary :: H.Html -> Summary
        (bound at Test/Tasty/Runners/Html.hs:88:17)
      status :: Tasty.Status (bound at Test/Tasty/Runners/Html.hs:82:13)
      i :: IntMap.Key (bound at Test/Tasty/Runners/Html.hs:79:11)
      testName :: String (bound at Test/Tasty/Runners/Html.hs:78:19)
      runTest :: t
                 -> String
                 -> t1
                 -> Traversal (Functor.Compose (t2 IO) (Const Summary))
        (bound at Test/Tasty/Runners/Html.hs:78:9)
      runner :: Tasty.OptionSet
                -> Tasty.TestTree
                -> m (IntMap.IntMap (STM.TVar Tasty.Status) -> IO Bool)
        (bound at Test/Tasty/Runners/Html.hs:72:3)
      (Some bindings suppressed; use -fmax-relevant-binds=N or -fno-max-relevant-binds)
    In the second argument of ‘asTypeOf’, namely ‘_’
    In the expression: branch testName False `asTypeOf` _
    In an equation for ‘leave’:
        leave = branch testName False `asTypeOf` _
Failed, modules loaded: Paths_tasty_html.

As you see, I got not just the type of the hole itself, but also the types of some other relevant definitions — very handy!

Another thing you can do is put _ = _ inside a let or where bindings group and play with -fmax-relevant-binds=N and -fno-max-relevant-binds.