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?
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.
> :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
.
I just tried it and used :t to see if it’s IO, turns out it’s just an unspecified Applicative
λ :{
Prelude| g :: Applicative f => (t -> a) -> t -> f a
Prelude| g f x = pure (f x)
Prelude| :}
Prelude
λ :t g (+1) 1
g (+1) 1 :: (Applicative f, Num a) => f a
I know that doesn’t answer the question why, but at least we know it didn’t pick IO as the type.
Welcome francisco!
While the type of g (+1) 1
might be (Applicative f, Num a) => f a
, that type isn’t ready to be executed yet. f
and a
have to be resolved to concrete types in order to execute it. You can observed this by seeing what the return type is after you evaluate that statement:
Prelude> g (+1) 1
2
Prelude> :t it
it :: Integer
(note that it
refers to the result of the previous statement.) So you might expect it :: Num a => a
, but that’s not a valid runtime type. It has to choose some concrete type for a
, and it chooses Integer
by default. Think of Applicative
and Num
similar to interfaces in an OO language. You can work with just an interface at compile time, but at runtime, it has to be some concrete type like a class that implements the interface.
I can’t find any good way to prove that it chooses IO
as the default Applicative
in GHCi, but if you read this, it makes sense since IO
is the only Applicative
said to be given the sort of special treatment that could explain this behavior. To cherry pick some statements:
If you type something of type
IO a
for somea
, then GHCi executes it as an IO-computation.
However, there’s no monad overloading here: statements typed at the prompt must be in the
IO
monad.