Question

I've been trying to do a little bit of abstraction over data types, and I've encountered a situation with GHC's generics that seems a little odd. Here is my basic set of declarations:

class GFields f where
    gfields :: f a -> [String]

instance (GFields c) => GFields (D1 i c) where
    gfields = gfields . unM1

instance (GFields fs) => GFields (C1 i fs) where
    gfields = gfields . unM1

instance (GFields f, GFields fs) => GFields (f :*: fs) where
    gfields (f :*: fs) = gfields f ++ gfields fs

instance (Selector s) => GFields (S1 s r) where
    gfields = (:[]) . selName

data Thing = Thing { foo :: String, bar :: Int }
    deriving (Generic)

Trying to use this in GHCi gives me Prelude.undefined if I give it an undefined value:

> gfields $ from (undefined :: Thing)
*** Exception: Prelude.undefined

However, if I try running some of the expected instance by hand (just grabbing a single field), I get what I would expect:

> selName $ (\(l :*: r) -> l) $ unM1 $ unM1 $ from (undefined :: Thing)
"foo"

Why do I get Prelude.undefined in one, but not the other?

Was it helpful?

Solution

So this was interesting, what you have there isn't actually quite what's done, the actual code after the inlining is

main = print
    . (\(l :*: r) -> selName l ++ selName r)
    . unM1
    . unM1
    . from
    $ (undefined :: Thing)

However, changing \(l :*: r) -> selName l ++ selName r to what you had doesn't crash. So the difference is clearly in this line. The obvious thought, that there's something bad about the right field is quickly disproved since \(l :*: r) -> r still runs.

We can see that the only nonbottom results are of the form (\l :*: r -> ???) where ??? is either l or r. Nothing else.

So let's take a look at the derived instance with -ddump-deriv.

from (Thing g1_atM g2_atN) = M1 (M1 (M1 (K1 g1_atM) :*: M1 (K1 g2_atN)))

Notice that this is strict in the constructor. So we must not be strict in the result of from undefined because the code will crash. So now we're kinda walking on a house of cards here, since forcing any part of this will crash our program. The interesting bit is that

-- The derived selectors
instance Selector S1_0_0Thing where
  selName _ = "foo"

instance Selector S1_0_1Thing where
  selName _ = "bar"

isn't strict in it's argument. So here's the catch, your code all compiles down to the constant "foo" because selName is constant, we don't use any of the previous computations; it's a compile time computation. However, if we do any sort of computation with l and r in that lambda, than when we use selName or do anything to see the result, we force the lambda to run, but since l :*: r is really bottom, we crash.

As a quick demonstration, this will crash

main = (`seq` putStrLn "Crashes")
       . (\(l :*: r) -> ())
       . unM1
       . unM1
       . from
       $ (undefined :: Thing)

But this will not

main = (const $ putStrLn "Crashes")
       . (\(l :*: r) -> ())
       . unM1
       . unM1
       . from
       $ (undefined :: Thing)

TLDR, just make each of the fields undefined, but the toplevel constructor shouldn't be bottom.

OTHER TIPS

The problem is that none of your instances force the argument in any way, except this one:

instance (GFields f, GFields fs) => GFields (f :*: fs) where
  gfields (f :*: fs) = gfields f ++ gfields fs

You're aiming to pass undefined to your function, so you have to be very careful in forcing the arguments. The argument is only there to guide the type-checker, it can't be looked at.

The fix is easy. Make the pattern lazy (or irrefutable, as the Haskell Report calls it):

instance (GFields f, GFields fs) => GFields (f :*: fs) where
  gfields ~(f :*: fs) = gfields f ++ gfields fs

This way, the match doesn't actually force the value. It will instead always succeed, and the use of f and fs translates into applications of selector functions.

With this change, your program works:

ghci> gfields $ from (undefined :: Thing)
["foo","bar"]

Your other program works because you call selName on the outside:

ghci> selName $ (\(l :*: r) -> l) $ unM1 $ unM1 $ from (undefined :: Thing)
"foo"

Now even though you have the pattern match on the pair in the expression, only the type of the argument of selName is relevant for the result. But this expression isn't exactly the same as your first test program, as the different results demonstrate and as jozefg further explains in his answer.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top