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.