Where did the structure go?

g :: Applicative f => (t -> a) -> t -> f a
g f x = pure (f x)
ghci> g (+1) 1
2

The result should be `f a` but the structure is not visible in GHCi. Wondering if it is still there?

I’m guessing that f defaulting to IO in ghci.

3 Likes
> :t 2
2 :: Num p => p

Is 2 an Applicative because it is Num?

No, 2 is not an Applicative, but IO is, as @gilmi is hinting to.

In ghci, everything is considered in the IO monad (roughly speaking).

For example

ghci> pure 1 :: IO Int
1

and

ghci> getLine :: IO String
test <-- Input
"test" <-- Output

In your case it’s simply getting cast to IO, here illustrated using replacement:

ghci> g (+1) 1 :: IO Int
2
ghci> pure ((+1) 1) :: IO Int
2

My question really came from expecting to see some indication of structure as this does

ghci> (+) <$> Just 1 <*> Just 1
Just 2

Perhaps that is nonsensical since Maybe is a type and Applicative is a class. I have the sense that I’m missing something obvious. Maybe it is that a class is a context and a type is a context but I need to think through the differences.

So in ghci (but not if you write out the program in a text file and compile it), things of type IO a tend to get handled specially. For example, if I have a :: IO Int and I enter a in ghci, it will print out an Int.

Try doing :t g (+1) 1 in order to see the type of the expression. That should help work out what’s going on.

Having seen some other examples of confusion caused by this “default IO context” in GHCi, maybe there’s a need for an "educational/introductory" mode or option, which means e.g. GHCi uses an empty context like GHC does for compiled modules (cf. the principle of least surprise astonishment), until new Haskellers are sufficiently annoyed by having to type in something like:

ghci> :#getLine
test
"test"
ghci>

…just to run an IO action in GHCi.

It’s worth noting that if you pick a different Applicative besides the default IO, you’ll get results that you expect:

g :: Applicative f => (t -> a) -> t -> f a
g f x = pure (f x)
Prelude> g (+1) 1 :: Maybe Int
Just 2

I noticed that. So is that saying that if the context is the default GHCi will not show it but otherwise will?

Yes, or more specifically if the context is IO (which is the default in GHCi), then GHCi will not show it. And that’s because GHCi has custom behavior for handling of IO. Outside of GHCi, there won’t be any weirdness, and inside GHCi the weirdness only happens if it’s specifically IO.

4 Likes