Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Composing a Storable by its parts

Tags:

haskell

I want to compose a Foreign.Storable into two parts....

import Foreign

data FullData type1 type2 = FullData {first::type1, second::type2}

instance (Storable type1, Storable type2)=>Storable (FullData type1 type2) where
    sizeOf _ = sizeOf (undefined::type1) + sizeOf (undefined::type2)
    alignment _ = 0 --I am just setting this to zero for testing....

main = putStrLn $ show $ sizeOf (undefined::FullData Int Char)

Yet this fails with-

storableTest.hs:13:44:
Could not deduce (Storable a1) arising from a use of `sizeOf'
from the context (Storable type1, Storable type2)
  bound by the instance declaration at storableTest.hs:12:10-74
The type variable `a1' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there are several potential instances:
  instance (Storable type1, Storable type2) =>
           Storable (FullData type1 type2)
    -- Defined at storableTest.hs:12:10
  instance Storable Bool -- Defined in `Foreign.Storable'
  instance Storable Char -- Defined in `Foreign.Storable'
  ...plus 16 others
In the second argument of `(+)', namely
  `sizeOf (undefined :: type2)'
In the expression:
  sizeOf (undefined :: type1) + sizeOf (undefined :: type2)
In an equation for `sizeOf':
    sizeOf _
      = sizeOf (undefined :: type1) + sizeOf (undefined :: type2)

Here are some more important facts-

  1. The same error appears even if I use ExistentialQuantification or RankNTypes and declare (Storable type1, Storable type2) directly in the data definition (outside the constructor or in the fields themselves).

  2. I can get this to work if I change the sizeOf definition to

    sizeOf (FullData x y) = sizeOf x + sizeOf y

but this only works if I have a concrete instance of FullData, and some of my programs need to know the size of the data before an instance is created (I know, I could just have a dummy instance, but that seems kinda ugly).

like image 704
jamshidh Avatar asked Dec 03 '13 06:12

jamshidh


1 Answers

This error is happening because the :: typeN annotations in the definition of sizeOf don't refer to the types of the same name in the instance declaration. This is just how Haskell works - type variables are only scoped over the type signature in which they appear.

An extension called ScopedTypeVariables will change that. Among other things, it makes type variables used in an instance declaration scope over all the definitions in the declaration.

Just insert the following line at the start of the file:

{-# LANGUAGE ScopedTypeVariables #-}

Alternatively, you could actually do something lazier than the pattern match. Since FullData is a record, the accessor functions restrict type appropriately.

sizeOf fd = sizeOf (first fd) + sizeOf (second fd)

As long as sizeOf is implemented correctly for the nested types, that is appropriately non-strict. Since the nested calls to sizeOf don't evaluate their arguments, the calls to first and second won't actually be evaluated - but their type will be calculated, and force things to work properly.

You could even use an irrefutable pattern match to do the same thing without using the field accessors:

sizeOf ~(FullData x y) = sizeOf x + sizeOf y

The ~ asserts to the compiler that the pattern will always match, and it can put off actually testing it until the values of x or y are needed. If you lied to the compiler about that pattern always matching, it will generate a runtime error when x or y is used, as it attempts to look them up but finds that the pattern wasn't actually matched properly. This works in a similar way to the previous case - if x and y aren't used, everything is good.

like image 89
Carl Avatar answered Oct 02 '22 13:10

Carl