I think @HTNW's answer probably covers it, but for completeness, here's how the inContext
solution works in detail.
The type signature of the function:
inContext :: a -> (a -> b) -> a
means that, if you have a thing you want to type, and a "context" in which it's used (expressible as a lambda that takes it as an argument), say with types:
thing :: a1
context :: a2 -> b
You can force unification of a1
(the general type of thing
) with a2
(the constraints of the context) simply by constructing the expression:
thing `inContext` context
Normally, the unified type thing :: a
would be lost, but the type signature of inContext
implies that the type of this whole resulting expression will also be unified with the desired type a
, and GHCi will happily tell you the type of that expression.
So the expression:
(.) `inContext` \hole -> hole digitToInt
ends up getting assigned the type that (.)
would have within the specified context. You can write this, somewhat misleadingly, as:
(.) `inContext` \(.) -> (.) digitToInt
since (.)
is as good an argument name for an anonymous lambda as hole
is. This is potentially confusing, since we're creating a local binding that shadows the top-level definition of (.)
, but it's still naming the same thing (with a refined type), and this abuse of lambdas allowed us to write the original expression (.) digitToInt
verbatim, with the appropriate boilerplate.
It's actually irrelevant how inContext
is defined, if you're just asking GHCi for its type, so inContext = undefined
would have worked. But, just looking at the type signature, it's easy enough to give inContext
a working definition:
inContext :: a -> (a -> b) -> a
inContext a _ = a
It turns out that this is just the definition of const
, so inContext = const
works, too.
You can use inContext
to type multiple things at once, and they can be expressions instead of names. To accommodate the former, you can use tuples; for the latter to work, you have use more sensible argument names in your lambas.
So, for example:
λ> :t (fromJust, fmap length) `inContext` \(a,b) -> a . b
(fromJust, fmap length) `inContext` \(a,b) -> a . b
:: Foldable t => (Maybe Int -> Int, Maybe (t a) -> Maybe Int)
tells you that in the expression fromJust . fmap length
, the types have been specialized to:
fromJust :: Maybe Int -> Int
fmap length :: Foldable t => Maybe (t a) -> Maybe Int
ghci
. – Wundt> :t (.) Data.Char.digitToInt
? – Torsi(.)
is. Yes, I know, it's the type of the argument I'm giving to it, but that's the point of my question. – Wundt:t undefined :: ((b -> c) -> (a -> b) -> a -> c) ~ ((Char -> Int) -> d) => ((b -> c) -> (a -> b) -> a -> c)
, which replies... :: (Char -> Int) -> (a -> Char) -> a -> Int
. – Jayjaycee:t undefined :: (type of function) ~ (type of argument -> fresh variable) => (comma separated unknowns)
(where the unknown could be, say, another copy of the function's type signature). If both the function and the argument are polymorphic, you should make sure to rename the variables in one or the other so there are no accidental equalities. – JayjayceeinContext :: a -> (a -> b) -> a
defined byinContext = const
. Then, at the GHCi prompt, the expressioninContext (.) $ \(.) -> (.) digitToInt
gives the inferred type directly, so you don't have to copy type signatures and make up fresh variables. Note thatinContext
can be used infix, but I always mess up writing code with backticks in comments. – Ocasio> :t (.) `inContext` \(.) -> (.) Data.Char.digitToInt
. – Torsi:t let x :: forall t. _; x = ((.) :: t) digitToInt in x
says it can’t matcht
with(Char -> Int) -> (a -> Char) -> a -> Int
. You can save this in your.ghci
file as a custom command like:def itype \ expr -> …
, but I think it’d be better to make a feature request. – Shaquana:set
some extension for that, right? Would you explain that in an answer? I guess it could be useful for other readers. – Wundt