Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it necessary to use IO when importing a foreign function that allocates?

In Haskell, when using the FFI to bind to a function that allocates, is it appropriate to avoid using IO when the foreign function allocates for and constructs some value, and that value only depends on the function arguments?

Consider the following function:

/**
 * The foo_create contract: if allocation succeeds, the
 * return value points to a value that depends only on 'x'
 * and 'name', otherwise a null pointer is returned.
 */
foo_t *foo_create(int x, const char *name);

Would it be appropriate to import this function in the following way?

newtype Foo = Foo (Ptr Foo)

foreign import unsafe "foo.h foo_create"
foo_create :: CInt -> CString -> Ptr Foo

This low-level binding function can then be wrapped to provide a nicer API:

makeFoo :: CInt -> CString -> Maybe Foo
makeFoo x s =
  let
    ptr = foo_create x s
  in
    if ptr == nullPtr
      then Nothing
      else Just (Foo ptr)

Although the allocation affects the real world, and whether or not it succeeds is also dependent on the real world, the type does model the possible outcomes. Furthermore, even pure functions and data can cause the Haskell runtime to allocate. So, is it reasonable to avoid the IO monad in situations such as these?

like image 269
frasertweedale Avatar asked Mar 18 '15 08:03

frasertweedale


1 Answers

If foo_create returns a value that only depends on the values of x and name, then yes it's fine return value outside IO. As you say, creating new values inside Haskell causes allocation, and they don't need to be in IO because it's impossible to observe the particular memory addresses that get allocated outside of IO, and also impossible to observe whether or not an allocation succeeds (i.e. whether the program is running out of memory) outside of IO.

However, you say "whether or not it succeeds is also dependent on the real world". In that case, no, it is an effectful operation which should have a return type in IO. A Haskell function with a type makeFoo :: CInt -> CString -> Maybe Foo says that whether the Maybe Foo is Nothing or Just _ must depend only on the values of the CInt and the CString. This is every bit as important as that the value inside the Just only depends on the arguments.

If it's possible to call a function with the same arguments and get different results depending on the state of the real world, then it isn't a function at all, and should be an IO action.

like image 128
Ben Avatar answered Nov 15 '22 08:11

Ben