I'm looking over the Cloud Haskell package's Encoding.hs, and encountered some strange code that I was hoping someone could help me better understand. Included is the necessary code:
class (Binary a,Typeable a) => Serializable a
instance (Binary a,Typeable a) => Serializable a
data Payload = Payload
{
payloadType :: !ByteString,
payloadContent :: !ByteString
} deriving (Typeable)
serialDecodePure :: (Serializable a) => Payload -> Maybe a
serialDecodePure a = (\id ->
let pc = payloadContent a
in pc `seq`
if (decode $! payloadType a) == show (typeOf $ id undefined)
then Just (id $! decode pc)
else Nothing ) id
I'm just curious about what the $! does (I'm guessing just strictly evaluates), and also why we need the id trick (something with lazy evaluation?). Also I am specifically having problems with this line:
if (decode $! payloadType a) == show (typeOf $ id undefined)
I'm guessing this is seeing if the payloadType is invalid for whatever reason, but if that is the case shouldn't the then and else clauses be switched, ie change:
if (decode $! payloadType a) == show (typeOf $ id undefined)
then Just (id $! decode pc)
else Nothing
to
if (decode $! payloadType a) == show (typeOf $ id undefined)
then Nothing
else Just (id $! decode pc)
Thanks for any help you can provide.
You are correct that $!
is a strict evaluator. It's type is identical to $
, and the only semantic difference is that the second argument is seq
'd before being passed to the function.
I think the id
is actually there to help type inference. Within the function block (\id -> ...)
, the function id
is forced to have type a -> a
, where a
is not just any type variable, but the same a
as in
serialDecodePure :: (Serializable a) => Payload -> Maybe a
This is due to this line:
Just (id $! decode pc)
since this has type Maybe a
, id
has the inferred type a -> a
. As a consequence, on the line you're looking at,
if (decode $! payloadType a) == show (typeOf $ id undefined)
id undefined :: a
, where a
is again the same as the output.
Now we can get to the type checking. Since this function is polymorphic and will decode to any type, it needs to check that the encoded data is compatible with the type it's decoding to. What if you encoded a String
and are trying to decode to an Int
? The LHS would decode to "[Char]", which is the TypeRep representation of a String. The RHS would instead be "Int", the type it's attempting to decode to. Since they aren't equal, the "else" path is the one that returns None
.
Rather than this id function type restriction, you could accomplish the same thing with the ScopedTypeVariables
extension.
Wow, that's some weird-looking code! As you guessed, ($!)
is about strictness:
f $! x = x `seq` f x
The id
trick is sneakier, and is all about type restriction. You'll notice that id
is used twice in the function body. The second time it's used as id $! decode pc
; this fixes the type of id
to operate on whatever kind of thing decode
outputs. The first use is as typeOf $! id undefined
; since the type of id
has been fixed already, this fixes the type of undefined
, so that typeOf
is applied to a monomorphic argument (and you don't get "ambiguous type" errors). This kind of thing is often done with the ScopedTypeVariables
extension instead of this trickery, but perhaps they wanted to avoid extensions wherever possible. As for the meaning of this:
(decode $! payloadType a) == show (typeOf $ id undefined)
...it looks to me like that is checking that the payload matches the type of the thing returned by the call to decode
in the then
branch. It seems to make sense to have a Just
value (i.e. success) when they match, and a Nothing
value (i.e. failure) when they don't.
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