0

When I was reading the documentations of SDL in haskell, I found that some functions inevitably modifies its input. For example, blitSurface has destination surface as input, but it is updated within the function. Now, generalizing the problem, if I have a function f :: a -> IO a, does it break composition if I modify a inside the function? What about f :: IO a -> IO a? What about a -> IO ()? And what about IO a -> IO ()?

Considering the case that blitSurface is actually a foreign function, and making a new surface every frame doesn't sound very efficient, these functions are hard to avoid. Will such functions cause problems in a larger scale? For example, using fModifySurface :: Surface -> IO () which does destructive update as an example:

main = do
    w <- ... -- The window surface
    -- Do something here
    s <- someFuncGetSurface -- We get a surface here
    fModifySurface s -- Destructively update s
    blitSurface ...... -- Ignore the actual API, but destructively updates w

Are there any unexpected semantics in the code above? If so, what is the best way to make use of foreign functions that changes the input?

Carl Dong
  • 1,299
  • 15
  • 26
  • 1
    Strictly speaking, those are not 'functions'. – Thomas M. DuBuisson Oct 05 '15 at 22:40
  • OK, 'functions' in imperative sense. – Carl Dong Oct 05 '15 at 22:45
  • 1
    As long as all foreign functions return `IO SomeType` no fundamental property should be broken. Is your case much different than calling e.g. `writeIORef` to update a mutable variable? The latter is "safe", even if it can be abused to write unidiomatic code. – chi Oct 05 '15 at 22:45
  • The thing is, the foreign function is like `f (type* src, type* dest)`, and content of `dest` is obviously changed. So if I call `f a`, it returns nothing but `a` is modified – Carl Dong Oct 05 '15 at 22:47
  • 3
    All values in Haskell are immutable, so no function modifies its input. However some values are (or contain) references to cells whose contents may be changed. But changing the contents of those cells doesn't modify the reference any more than changing the contents of a file modifies its filename. So there is no problem from a language point of view. Of course it can sometimes be more difficult to reason about a system that involves mutable state. – Reid Barton Oct 05 '15 at 23:44
  • You ask, "Does it break composition?" and "Are there any unexpected semantics?". I ask, "What do you mean by composition?" and "What are the expected semantics?". – Daniel Wagner Oct 06 '15 at 00:20
  • Like will it make code too "impure" for reasoning – Carl Dong Oct 06 '15 at 00:25
  • @ReidBarton I know all values in _Haskell_ are immutable, but `blitSurface` is in C, a foreign function. So after `blitSurface`, the `w` is no longer the same `w` as before. `IORef` doesn't have this problem – Carl Dong Oct 06 '15 at 00:27
  • No, exactly the same is true here. `w` is a value in a Haskell program, so it is a Haskell value. Concretely `w` stores some kind of pointer. The value of the pointer never changes; what changes is the data pointed to by the pointer. Same as `IORef`. – Reid Barton Oct 06 '15 at 01:08
  • @ReidBarton I see what you mean. I've never used IORef before so I don't know that it is expected – Carl Dong Oct 06 '15 at 01:33

1 Answers1

5

I observe that f a b and flip f b a are beta-equivalent terms. On the other hand, the straightforward IO version of these, namely, f <$> a <*> b and flip f <$> b <*> a, are certainly not beta-equivalent; and even using the equivalence from "Tackling the Awkward Squad", which makes many more IO actions equivalent, these two terms are not equivalent.

At a high level, what this means is that if you prove something about the behavior of pure terms, then you can re-use that proof even when the pure computation is used as part of a larger program. On the other hand, there is not a corresponding way to uniformly lift a local proof about an IO term into a proof about a larger IO-based program; if you wish to do so, you must invoke some global properties about the particular IO actions you plan to use together.

This is the impetus behind the common advice to lift as much of your computation as possible out of IO and into the pure world -- indeed, it is one of the primary motivations for doing pure functional programming in the first place, namely, that imperative programs do not compose well.

However, none of this discussion is specific to the FFI or to functions whose IO actions update the values referenced by one of their inputs. blitSurface is no worse or better in this regard than basically any of the "sin bin" we toss into IO. Imperative programs simply aren't compositional in the same sense that pure ones are.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380