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?
Introduction. GHCi is GHC's interactive environment, in which Haskell expressions can be interactively evaluated and programs can be interpreted.
This library offers interfaces which mediate interactions between the ghci interactive shell and iserv , GHC's out-of-process interpreter backend.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With