Search code examples
haskellhaskell-ffi

How to Call C++ Setters & Getters from Haskell


I know how to call pure C++ functions from Haskell, but wondering how to get GHC to accept functions that have side effects.

I want Haskell to have read-only access to a C++ linked list, and exclusive write access to a C++ variable. For example:

class node {
    // Some other class makes a linked list out of these
    int x;
    node* previous;
    node* next;
}

class myHaskellInterface {
    node* presentLocation; // C++ decides what is regarded as current location

    int getVal() {
        // Haskell calls this to get information from C++
        return presentLocation->x;
    }

    int haskellResults; // Store Haskell output here

    void setVal(int x) {
        // Haskell calls this to pass information back
        haskellResults = x;
    }

};

Even though getVal() doesn't have side effects, it is part of a class that obviously has side effects, so it's not clear to me whether sneaky tricks are required to get GHC to accept it.

setVal(int) clearly has side effects, so how do you make GHC not care?


Solution

  • Note that the production of side effects isn't the issue. It's "impure" versus "pure" functions that are important. While getVal doesn't cause side effects, it relies on side effects to produce a value because it consults presentLocation. In other words, it's an impure function.

    Haskell can call foreign functions whether they are pure or impure, you just need to give them appropriate signatures. An impure function must be given an IO a return type. A pure function can be given a non-IO return type. (Of course, you could give a pure function an IO return type, too, but you don't have to and so usually wouldn't.)

    For example, suppose we have the simple C++ "interface":

    int value = 0;   // Haskell code sets value directly
    extern "C" int getValue() { return value; }  // and gets it with this
    

    If we incorrectly try to import getValue as a pure function:

    foreign import ccall "interface.cc &value" valuePtr :: Ptr CInt
    foreign import ccall "interface.cc getValue" getValue :: CInt  -- **NO NO NO!!!**
    

    and test it like so:

    main :: IO ()
    main = do
      print getValue
      poke valuePtr 5
      print getValue
    

    we get incorrect output:

    0
    0
    

    Instead, we need to give getValue a type IO CInt:

    foreign import ccall "interface.cc getValue" getValue :: IO CInt
    

    With appropriate modifications to the rest of the program:

    import Foreign
    import Foreign.C
    
    foreign import ccall "interface.cc &value" valuePtr :: Ptr CInt
    foreign import ccall "interface.cc getValue" getValue :: IO CInt
    
    main :: IO ()
    main = do
      print =<< getValue
      poke valuePtr 5
      print =<< getValue
    

    the output is as expected:

    0
    5
    

    Note that it's only the return value that should be given an IO type. If we add an impure function that takes arguments, like:

    extern "C" int getMultValue(int scale) { return value*scale; }
    

    then you'd use:

    foreign import ccall "interface.cc getMultValue" getMultValue :: CInt -> IO CInt
    

    The full programs:

    // interface.cc
    int value = 0;
    extern "C" int getValue() { return value; }
    extern "C" int getMultValue(int scale) { return value*scale; }
    
    -- Main.hs
    import Foreign
    import Foreign.C
    
    foreign import ccall "interface.cc &value" valuePtr :: Ptr CInt
    foreign import ccall "interface.cc getValue" getValue :: IO CInt
    foreign import ccall "interface.cc getMultValue" getMultValue :: CInt -> IO CInt
    
    main :: IO ()
    main = do
      print =<< getValue
      poke valuePtr 5
      print =<< getValue
      print =<< getMultValue 5
    

    Note that things will get a little more complicated when the functions or variables in question are actually methods / instance variables. Haskell doesn't directly support working with C++ objects, so you need to build some kind of extern "C" interface and pass object pointers as explicit arguments. If you run into trouble a little further along in your design, maybe post additional questions and we'll try to help.