Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GHC Generics behavior seems to differ in GHCi

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?

like image 837
iand675 Avatar asked Nov 11 '13 22:11

iand675


People also ask

What is the difference between GHC and ghci?

Introduction. GHCi is GHC's interactive environment, in which Haskell expressions can be interactively evaluated and programs can be interpreted.

Is ghci an interpreter?

This library offers interfaces which mediate interactions between the ghci interactive shell and iserv , GHC's out-of-process interpreter backend.

How do I run Haskell with ghci?

If you have installed the Haskell Platform, open a terminal and type ghci (the name of the executable of the GHC interpreter) at the command prompt. Alternatively, if you are on Windows, you may choose WinGHCi in the Start menu. And you are presented with a prompt. The Haskell system now attentively awaits your input.


1 Answers

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.

like image 158
Daniel Gratzer Avatar answered Nov 13 '22 06:11

Daniel Gratzer