I must first mention that you have a problem in the design of your typeclass. The signature of your des function says that for each input ByteString there exists a valid output a. This is likely not true.
For example, imagine having an instance of SerDes for Int. By your definition, you'll have a valid Int representation even for 6GB ByteString of random data. Sounds wrong.
For this reason, you need to specify the possibility of deserialization failures in the signature of des. A typical approach would be to wrap a in Maybe or Either YourDetailedRepresentationOfFailure. E.g.,
class SerDes a where
ser :: a -> ByteString
des :: ByteString -> Either Text a
In fact this is the approach essentially taken by all Haskell serialization and parsing libraries. They may introduce some abstractions there, but in essence they all find ways to represent deserialization failures.
Now to your actual question. Typeclass instances are identified by the type they are for, so you must provide the specific type for a somehow. Proxy is one option for that. Another one is to directly refer to that type without using it in your function and passing in the undefined value for it. Third (and cleanest one, IMO) is to wrap the result in Tagged.
The undefined option
{-# LANGUAGE ScopedTypeVariables, TypeApplications #-}
check :: forall a. SerDes a => a -> ByteString -> Bool
check _ bytes =
isRight (des @a bytes)
Please notice that forall and ScopedTypeVariables extension are required to be able to refer to the type parameter in the function def.
You'll then call this function like this:
check (undefined :: a) bytes
or this:
check @a undefined bytes
The Tagged option
check :: SerDes a => ByteString -> Tagged a Bool
check bytes =
fmap isRight (Tagged (des bytes))
You'll then call this function like this:
unTagged (check @a bytes)
Final note
In reality you'll likely never need the check function, since des already carries all the information needed and more. It's easier and more understandable to just have isRight (des @a bytes) in place where you need check. The fact that you have to go thru complications for defining check is actually a signal of a design mistake. In practical Haskell code you'll rarely meet such complications.