3

I want to generate stateful functions (C signature T f()) with QuickCheck as arguments for foreign functions. Preferably I also want to make them (or their inner s->(T,s)) showable.

I know that for stateless functions I can write something like

type Compare = CInt -> CInt -> CBool
foreign import ccall "wrapper"
  mkCompare :: Compare -> IO (FunPtr Compare)

but I got stuck trying this approach for the stateful functions, as I don't see how I can translate the monad hiding the state in Haskell to the function hiding the state in C.

Example of a stateful function f

static int i = 0;

int f() {
    return i++;
}

In Haskell I would represent this function as State (\s -> (s,s+1)).

What does QuickCheck have to do with it?

If I have a C function that takes a stateful function as argument, e.g.

int twice(int (*f)()) {
    f();
    return f();
}

then I can test the function with QuickCheck, where QuickCheck can generate different implementations for f, which would usually look similar to

prop_total (Fun f) xs = total $ g f xs

but these generated functions are stateless, not stateful like the example C function above.

5
  • 2
    Perhaps somebody else can help you, but I'm not understanding what you are asking. First of all are you talking about C or C++? What is a "stateful function object" (is it a function or a C++ object)? What does QuickCheck have to do with it? Can you show a usage example in C code (not C++)? Commented Nov 1, 2022 at 10:32
  • @Noughtmare Thanks for telling me where my question was unclear. I've edited it to eliminate mentions of C++, which is irrelevant here, and answered your other questions. If there's anything else unclear, please let me know! Commented Nov 1, 2022 at 14:01
  • 2
    Not an FFI expert... does type Compare = CInt -> CInt -> IO CBool get you where you need to go? Then you can write whatever :: IORef Whatever -> Compare to get a stateful Compare, and QuickCheck can generate a Whatever -> CInt -> CInt -> (Whatever, CBool) that you can wrap into a IORef Whatever -> Compare. Commented Nov 1, 2022 at 14:22
  • @DanielWagner Hmm, maybe. I'll have to experiment with this a bit, as I face some difficulties with the IO type not being accepted as parameter type in the foreign declaration, and with IORef not having a QuickCheck instance. I'll let you know if I make any progress. If you could write up an answer with executable code, that would also be great. Commented Nov 1, 2022 at 19:39
  • 1
    @JohannesRiecken About the FFI part I'm not super sure. But on the QuickCheck bit: my comment proposes that you have QuickCheck generate a Whatever -> CInt -> CInt -> (Whatever, CBool), which does not mention IORef; then, after it's generated, you can wrap it up to do something to an IORef instead of having explicit state. Commented Nov 1, 2022 at 23:47

1 Answer 1

1

Thanks to Daniel Wagner's suggestion in the comments, I could figure it out. The Show instance comes for free. Here's a minimal example that I ran with

gcc -fPIC -shared -o libstateful.dylib stateful.c && ghc -L. Stateful.hs -lstateful && ./Stateful

As expected it will output a distribution of about 50% of 1 (True) and 50% of 0 (False).

Stateful.hs

import Data.IORef
import Foreign.C
import Foreign.Ptr
import System.IO.Unsafe
import Test.QuickCheck

instance CoArbitrary CInt where
  coarbitrary = coarbitraryIntegral

instance Arbitrary CBool where
  arbitrary = chooseEnum (0,1)
  shrink 1 = [0]
  shrink 0 = []

instance Function CInt where
  function = functionIntegral

type Generator = IO CBool

foreign import ccall "wrapper" mkGenerator :: Generator -> IO (FunPtr Generator)

foreign import ccall "changes" changes :: FunPtr Generator -> IO CBool

type StateFn = CInt -> (CBool,CInt)

stateFnToIORef :: StateFn -> IORef CInt -> IO CBool
stateFnToIORef f s_ref = do
    s <- readIORef s_ref
    let (a,s') = f s
    writeIORef s_ref s'
    pure a

prop_changes :: Fun CInt (CBool,CInt) -> Property
prop_changes (Fn f) = unsafePerformIO (do
    x_ref <- newIORef 0
    f' <- mkGenerator $ stateFnToIORef f x_ref
    res <- changes f'
    pure $ collect res (total res))

main :: IO ()
main = quickCheck prop_changes

stateful.c

_Bool changes(_Bool (*f)()) {
    _Bool x = f();
    _Bool y = f();
    return x != y;
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.