Polymorphic Types in Records

I am trying to write a function that reads raw bytes from a file, “discards” it to a “simple” type, and then sorts it.

To do this, I need to tell how it should interpret binary data - that is, the data type of binary data.

To be “binary” data, in the sense of “I can treat this data as raw bits when I read and write it from disk,” the data type must be binary and bits. And to sort it, he must be a member of the Horde.

Any type limited to these methods must be sortable.

As a small hack, to pass the type of the sorting function, instead, I move on to a resident of this type. (If there is a way to convey the type itself and achieve results, I would love to know.)

{-# LANGUAGE RankNTypes #-} import Data.Binary.Get import Data.Binary.Put type Sortable = forall a. (Bits a, Binary a, Ord a) => a data SortOpts = SortOpts { maxFiles :: Int , maxMemory :: Integer , maxThreads :: Int , binType :: Sortable } defaultOpts = SortOpts { maxFiles = 128 , maxMemory = 1000 * 1000 * 1000 * 1000 , maxThreads = 4 , binType = 0 :: Word32 }; putBinaryValues :: Binary a => Handle -> [a] -> IO () putBinaryValues out vals = do let bytes = runPut . mapM_ put $ vals BL.hPut out bytes binaryValues :: (Binary a, Bits a) => a -> Handle -> IO [a] binaryValues t inf = do size <- hFileSize inf let cast = runGet (genericReplicateM (size `div` byteWidth) get) cast . BL.fromChunks . (:[]) <$> BS.hGetContents inf where genericReplicateM n = sequence . (DL.genericReplicate n) byteWidth = fromIntegral $ (bitSize t) `div` 8 

But this does not compile. Haskell seems to be insisting that all record values ​​are concrete types. At least this is what I am collecting from the error message:

 Could not deduce (a ~ Word32) from the context (Bits a, Ord a, Binary a) bound by a type expected by the context: (Bits a, Ord a, Binary a) => a at ... `a' is a rigid type variable bound by a type expected by the context: (Bits a, Ord a, Binary a) => a 

So how could I achieve this generalization?

EDIT:

I wanted to use record update syntax to “tweak” sorting. For instance:.

 configure = defaultOpts -- and exporting that 

and later

 let myOpts = configure{ binType = 42 :: Word16 } 

But this will not work, and I cannot understand why, unless it is NYI.

 Record update for insufficiently polymorphic field: binType :: a In the expression: configure {binType = words !! 0} In an equation for `o': o = configure {binType = words !! 0} In the expression: do { inTestHandle <- inTest; words <- testRandomWords; putBinaryValues inTestHandle $ take 100 words; seekBeg inTestHandle; .... } 

So, should my client code just copy the values ​​from defaultOpts in parts and create a new record, each of which wants to reconfigure the sort?

+6
source share
2 answers

You can use ExistentialQuantification in your SortOpts type. The following compilations:

 {-# LANGUAGE ExistentialQuantification #-} import Data.Bits import Data.Word import Data.Binary import Data.Binary.Get import Data.Binary.Put data SortOpts = forall a. (Bits a, Binary a, Ord a) => SortOpts { maxFiles :: Int , maxMemory :: Integer , maxThreads :: Int , binType :: a } defaultOpts = SortOpts { maxFiles = 128 , maxMemory = 1000 * 1000 * 1000 * 1000 , maxThreads = 4 , binType = 0 :: Word32 } 

However, note that you cannot use binType as a function because it will have a type of type exists a. SortOpts -> a exists a. SortOpts -> a , and you cannot use existential types as return values. However, you can get the field value from the pattern, for example

 test :: SortOpts -> ByteString -> ByteString -> Ordering test (SortOpts{binType=binType}) bsa bsb = compare ab where a = runGet get bsa `asTypeOf` binType b = runGet get bsb `asTypeOf` binType 

This deserializes and compares two bytes using the existential binType in the given SortOpts .

As you noticed, Haskell's syntax does not support existential fields, so you need to do something like this to update binType :

 defaultOpts = SortOpts { maxFiles = 128 , maxMemory = 1000 * 1000 * 1000 * 1000 , maxThreads = 4 , binType = 0 :: Word32 } alternativeOpts = withBinType (0 :: Word16) $ defaultOpts { maxFiles = 256 } withBinType :: (Bits a, Binary a, Ord a) => a -> SortOpts -> SortOpts withBinType bt (SortOpts{..}) = SortOpts maxFiles maxMemory maxThreads bt 

The above example uses RecordWildCards to make copying a record easier. It is also a convenient extension when using parameter writing later.

Alternatively, as jozefg suggested, you can use the shell type for binType . You would use it as follows:

 {-# LANGUAGE ExistentialQuantification #-} data BinType = forall a. (Bits a, Binary a, Ord a) => BinType a data SortOpts = SortOpts { maxFiles :: Int , maxMemory :: Integer , maxThreads :: Int , binType :: BinType } defaultOpts = SortOpts { maxFiles = 128 , maxMemory = 1000 * 1000 * 1000 * 1000 , maxThreads = 4 , binType = BinType (0 :: Word32) } alternativeOpts = defaultOpts { binType = BinType (0 :: Word16) } 

Since SortOpts now only a regular recording type, you can use all write operations normally. To reference the deployed binType , you need to match pattern matching with the wrapper, so the test example from earlier would be (using RecordWildCards )

 test :: SortOpts -> ByteString -> ByteString -> Ordering test (SortOpts{..}) bsa bsb = case binType of BinType bt -> compare ab where a = runGet get bsa `asTypeOf` bt b = runGet get bsb `asTypeOf` bt 

Note that all of the above assumes that you have a specific use case in which for some reason you need to hide the exact type parameter behind the existential one. Usually you just leave the type parameter in SortOpts and restrict it to functions that use SortOpts . I.e.

 data SortOpts a = SortOpts { maxFiles :: Int , maxMemory :: Integer , maxThreads :: Int , binType :: a } test :: (Bits a, Binary a, Ord a) => SortOpts a -> ByteString -> ByteString -> Ordering test (SortOpts{..}) bsa bsb = compare ab where a = runGet get bsa `asTypeOf` binType b = runGet get bsb `asTypeOf` binType 

You can use the ConstraintKinds extension to create a shorter alias if necessary, as in

 {-# LANGUAGE ConstraintKinds #-} type BinType a = (Bits a, Binary a, Ord a) test :: BinType a => SortOpts a -> ByteString -> ByteString -> Ordering 
+1
source

Problem

The problem is RankNTypes . Look at Sortable , this is a function that will return an arbitrary a , where a is an instance of Ord , Bits and Bytes . In other words, you do not have only one of the three classes, you have all the instances.

Word32 obviously cannot do this, so trying to say it is a mistake.

Think about it, for example undefined , undefined not "some type compatible with a ", it can be all types. That would be equivalent to saying

 foo :: a foo = 1 

If you want some vocab: a be universally defined, so the caller chooses an implementation. What you want is an existential quantification where the caller selects a particular type.

Possible fixes

So the easiest way is

 data SortOpts a = SortOpts { maxFiles :: Int , maxMemory :: Integer , maxThreads :: Int , binType :: a } 

and limit a for each function

  someFun :: (Bits a, Bytes a, Ord a) => SortOpts a -> whatever 

To make typing easier,

  class (Ord a, Bytes a, Bits a) => Sortable a where instance (Ord a, Bytes a, Bits a) => Sortable a where 

Otherwise, you need to create an existential box type. Here I use GADT for this.

  {-# LANGUAGE GADTs #-} data SortBox where Sort :: (Bits a, Bytes a, Ord a) => a -> SortBox 

and then instantiate Bits , Bytes and Ord , just unzip hidden a and act on it. This will allow you to set any type using Sort , and then use it in general terms like Bits , Bytes or Ord . It is transparent at the level level, but at the value level you must put content that is odd.

 data SortOpts a = SortOpts { maxFiles :: Int , maxMemory :: Integer , maxThreads :: Int , binType :: SortBox } 
+8
source

Source: https://habr.com/ru/post/953076/


All Articles