haskell magical code, what's going on here
Asked Answered
W

2

11

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.

Wineglass answered 13/9, 2011 at 21:41 Comment(2)
Yes, f $! x is strict application. You may find this article enlightening about how adding strictness works.Wondawonder
See also https://mcmap.net/q/449126/-what-is-the-difference-between-dollar-and-dollar-exclamation-point/246886Disputatious
I
12

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.

Interlock answered 13/9, 2011 at 22:3 Comment(0)
B
4

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.

Ballista answered 13/9, 2011 at 22:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.