:sprint for polymorphic values?
Asked Answered
H

1

14

I am wondering why :sprint reports xs = _ in this case:

Prelude> xs = map (+1) [1..10]
Prelude> length xs
10
Prelude> :sprint xs
xs = _

but not in this case:

Prelude> xs = map (+1) [1..10] :: [Int]
Prelude> length xs
10
Prelude> :sprint xs
xs = [_,_,_,_,_,_,_,_,_,_]

Note: I am running ghci with -XNoMonomorphismRestriction. Does it have to do with the fact that the type of xs is polymorphic in the first case but not in the second? I'd like to know what's going on internally.

Heal answered 3/2, 2014 at 1:41 Comment(1)
This is exactly the kind of confusion the monomorphism restriction is designed to prevent, so you should definitely read the wiki page on it.Fable
T
10

The gist is that the with the polymorphic xs it has a type of the form

xs :: Num a => [a]

typeclasses under the hood are really just functions, they take an extra argument that GHC automatically fills that contains a record of the typeclasses functions. So you can think of xs having the type

xs :: NumDict a -> [a]

So when you run

Prelude> length xs

It has to choose some value for a, and find the corresponding NumDict value. IIRC it'll fill it with Integer, so you're actually calling a function with and checking the length of the resulting list.

When you then :sprint xs, you once again fill in that argument, this time with a fresh type variable. But the point is that you're getting an entirely different list, you gave it a different NumDict so it's not forced in any way when you called length before.

This is very different then with the explicitly monomorphic list since there really is only one list there, there's only one value to force so when you call length, it forces it for all future uses of xs.

To make this a bit clearer, consider the code

data Smash a = Smash { smash :: a -> a -> a }
-- ^ Think of Monoids

intSmash :: Smash Int
intSmash = Smash (+)

listSmash :: Smash [a]
listPlus = Smash (++)

join :: Smash a -> [a] -> a
join (Smash s) xs = foldl1' s xs

This is really what type classes are like under the hood, GHC would automatically fill in that first Smash a argument for us. Now your first example is like join, we can't make any assumptions about what the output will be as we apply it to different types, but your second example is more like

join' :: [Int] -> Int
join' = join intSmash
Troostite answered 3/2, 2014 at 5:52 Comment(3)
This explanation is difficult to follow.Emplane
@Emplane Any particular part?Troostite
Since the internal workings of typeclasses and "NumDict" stuff are not detailed, and exemplified, it is hard to follow your answer. Can you give a link where that stuff is well explained? Anyway it seems that :sprint is suffering from some implementation side effects, which is ironic for Haskell ... Note that, if length xs has to navigate through the entire list, then :sprint should always print [_,_, ...] irrespective of the type of the elements in the list. There is some bug around ...Emplane

© 2022 - 2024 — McMap. All rights reserved.