["Simon Marlow" <simonmar@microsoft.com>] RE: cvs commit: fptools/libraries/base/Foreign ForeignPtr.hs
[Thread moved from the library cvs mailing list] Simon Marlow:
- Fix the Eq instance for ForeignPtr to match the behaviour specified by the spec. Two ForeignPtrs are equal iff their underlying Ptrs are equal (previously they were equal iff they were the same ForeignPtr).
Alastair Reid:
Hmmm, the Hugs implementation goes for 'same foreignptr' and, to me, that seems to make more sense.
I wonder why the spec changed the semantics from that in the Hugs-GHC spec?
Simon Marlow:
I don't have any strong opinions on the matter, I just changed it to match the spec because someone complained.
Do you have an example where the previous behaviour is more useful? Perhaps it should be brought up on the FFI list.
I don't have a really compelling example just a nagging feeling and worry that a design is being changed without (as far as I can recall) any discussion of the matter. Here's some questions that might help understand the difference: 1) Is there any practical difference between the old semantics and the new? There used to be a big difference because ForeignObjects supported an operation to replace the underlying Ptr. Equality on mutable objects is clearly different from equality on the contents of the mutable objects. But, ForeignPtrs with the same Ptr but different finalizers are different and the finalizer list _is_ mutable so there is a useful difference. The difference between two ForeignPtrs which contain the same Ptr is especially clear if we think about what happens if the first one dies before the second or the second is the first to die. 2) Can either semantics be implemented in terms of something we already have? We can implement the new semantics using castForeignPtrToPtr. There's no way to implement the old semantics using other existing operations. Even if there was a way to extract the list of finalizers (and assuming we're happy using pointer equality on the finalizer functions), we still couldn't implement it because ForeignPtrs are, effectively, mutable objects. In short, I can see that switching to the new semantics (ForeignPtr equality == equality of contents) removes something we had before and doesn't add anything new but I don't have any examples of where it is actually useful. (I also have no examples of where the new semantics is useful.) I should also say that I have no strong feelings on the matter - I'd just like to hear someone argue that the change is an improvement or be pointed at an old discussion on the subject. -- Alastair
Alastair Reid <alastair@reid-consulting-uk.ltd.uk> wrote,
[Thread moved from the library cvs mailing list]
Simon Marlow:
- Fix the Eq instance for ForeignPtr to match the behaviour specified by the spec. Two ForeignPtrs are equal iff their underlying Ptrs are equal (previously they were equal iff they were the same ForeignPtr).
Alastair Reid:
Hmmm, the Hugs implementation goes for 'same foreignptr' and, to me, that seems to make more sense.
I wonder why the spec changed the semantics from that in the Hugs-GHC spec?
Simon Marlow:
I don't have any strong opinions on the matter, I just changed it to match the spec because someone complained.
Do you have an example where the previous behaviour is more useful? Perhaps it should be brought up on the FFI list.
I don't have a really compelling example just a nagging feeling and worry that a design is being changed without (as far as I can recall) any discussion of the matter.
Here's some questions that might help understand the difference:
1) Is there any practical difference between the old semantics and the new?
There used to be a big difference because ForeignObjects supported an operation to replace the underlying Ptr. Equality on mutable objects is clearly different from equality on the contents of the mutable objects.
But, ForeignPtrs with the same Ptr but different finalizers are different and the finalizer list _is_ mutable so there is a useful difference.
The difference between two ForeignPtrs which contain the same Ptr is especially clear if we think about what happens if the first one dies before the second or the second is the first to die.
2) Can either semantics be implemented in terms of something we already have?
We can implement the new semantics using castForeignPtrToPtr.
There's no way to implement the old semantics using other existing operations. Even if there was a way to extract the list of finalizers (and assuming we're happy using pointer equality on the finalizer functions), we still couldn't implement it because ForeignPtrs are, effectively, mutable objects.
In short, I can see that switching to the new semantics (ForeignPtr equality == equality of contents) removes something we had before and doesn't add anything new but I don't have any examples of where it is actually useful. (I also have no examples of where the new semantics is useful.)
The issue came up in a binding to a BDD library currently being implemented by Peter Gammie <peteg@cse.unsw.edu.au>. With BDDs, it is important that when you construct a BDD for the same expression two times that you get the same BDD (ie, the resulting BDD values should be (==)). The C API of the BDD library represents BDD values as pointers that in Haskell land become ForeignPtrs as they need finalisers. This application obviously requires equality on ForeignPtrs to be the same as the equality of the underlying C pointers. On the other hand, I don't know any application where the semantics currently implemented by GHC-Hugs (ie, to ForeignPtrs are equal only when they were generated by the same newForeignPtr) would be essential. As for the reasons why the FFI spec adopted the "new" semantics: * The documentation of the existing libraries didn't say anything about the implemented semantics. * So, I choose the one which I regarded to be the "natural" one when trying to fill in the gaps in the existing documentation. I still think that the "new" semantics documented in the FFI spec is what a user would intuitively expect. When you compare two foreign pointers, you want to know something about the equality of the things being referenced. You are not interested whether the two objects share the same finalizer. In other words, I think the old GHC-Hugs semantics is misleading. Cheers, Manuel
Manuel M T Chakravarty <chak@cse.unsw.edu.au> writes:
The issue came up in a binding to a BDD library currently being implemented by Peter Gammie <peteg@cse.unsw.edu.au>. With BDDs, it is important that when you construct a BDD for the same expression two times that you get the same BDD (ie, the resulting BDD values should be (==)). The C API of the BDD library represents BDD values as pointers that in Haskell land become ForeignPtrs as they need finalisers. This application obviously requires equality on ForeignPtrs to be the same as the equality of the underlying C pointers.
Nice example. Though, as I said, this equality test can be implemented using existing operations.
I still think that the "new" semantics documented in the FFI spec is what a user would intuitively expect. When you compare two foreign pointers, you want to know something about the equality of the things being referenced. You are not interested whether the two objects share the same finalizer. In other words, I think the old GHC-Hugs semantics is misleading.
I think it depends a bit on how you think about the objects being compared. Equality on IORefs and mutable arrays doesn't compare the contents; equality on pairs, Maybes and arrays does compare the contents. One difference is that the former are created in the IO world and it is important to reason about sharing. The other difference is that the former can be mutated so comparing the contents isn't very useful. Which factor dominates? On the one hand, sharing of ForeignPtrs matters but, on the other, the only mutation is of the finalizer list (addFPFinalizer). -- Alastair
foreign dependencies can mutate too. they are attached to the ForeignPtr, not the underlying C Ptr. perhaps ForeignPtr should not be an instance of Eq so people can provide their own? although, I supose that is what newtypes are for. is testing if they are 'the same' foreign pointer efficiently possible with all plausable implementations of foreign pointers? John On Thu, Nov 07, 2002 at 03:43:07PM +0000, Alastair Reid wrote:
Manuel M T Chakravarty <chak@cse.unsw.edu.au> writes:
The issue came up in a binding to a BDD library currently being implemented by Peter Gammie <peteg@cse.unsw.edu.au>. With BDDs, it is important that when you construct a BDD for the same expression two times that you get the same BDD (ie, the resulting BDD values should be (==)). The C API of the BDD library represents BDD values as pointers that in Haskell land become ForeignPtrs as they need finalisers. This application obviously requires equality on ForeignPtrs to be the same as the equality of the underlying C pointers.
Nice example. Though, as I said, this equality test can be implemented using existing operations.
I still think that the "new" semantics documented in the FFI spec is what a user would intuitively expect. When you compare two foreign pointers, you want to know something about the equality of the things being referenced. You are not interested whether the two objects share the same finalizer. In other words, I think the old GHC-Hugs semantics is misleading.
I think it depends a bit on how you think about the objects being compared. Equality on IORefs and mutable arrays doesn't compare the contents; equality on pairs, Maybes and arrays does compare the contents.
One difference is that the former are created in the IO world and it is important to reason about sharing.
The other difference is that the former can be mutated so comparing the contents isn't very useful.
Which factor dominates? On the one hand, sharing of ForeignPtrs matters but, on the other, the only mutation is of the finalizer list (addFPFinalizer).
-- --------------------------------------------------------------------------- John Meacham - California Institute of Technology, Alum. - john@foo.net ---------------------------------------------------------------------------
perhaps ForeignPtr should not be an instance of Eq so people can provide their own?
Note that if we did this, we'd want to consider adding an operation eqForeignPtr :: FP a -> FP a -> Bool FP b -- possible variant but not very useful which lets people test equality of the container and not the contents. In fact the more I think on it, the more convinced I am that the Eq instance should compare contaner equality and not contents equality. The reason is that I believe Eq instances should follow the following design rule: Eq instances should compute observational equivalence I believe this is satisfied by all Haskell98 types, all the usual extensions (IORefs and friends) and by derived instances of datatypes. Just to be clear what I mean by observational equivalence, consider comparing two IORefs x and y using this code: eq x y = do a <- readIORef x writeIORef (a+1) b <- readIORef x return (x==y) Obviously, this code is a bad way to test if two IORefs are the same IORef but the point is that we can observe the difference between them. Similarily, with ForeignPtrs, adding a finalizer to one and not the other and then watching for when the finalizer runs is a way that we might observe differences between two FPs. What I don't mean by observational (in)equivalence is this: One might be able to distinguish two data structures of type [Int] (say), by observing how much memory they consume. This is perfectly true but Haskell semantics doesn't let you observe this so we'll rule any such 'observations' as irrelevant or invalid. -- Alastair ps Careful reading will reveal that I still haven't proposed a concrete use for eqForeignPtr. So far, I'm relying on a combination of gut instinct and the fact that we (i.e., me plus one or more of my coauthors at the time (Simon PJ, John Peterson, and Simon Marlow)) did consciously decide to use one semantics instead of the other when we designed MallocPtrs/ForeignObjs/ForeignPtrs originally. I thought about it a good deal harder when I added them than I claim to have thought about them in the last couple of days.
Alastair Reid <alastair@reid-consulting-uk.ltd.uk> wrote,
perhaps ForeignPtr should not be an instance of Eq so people can provide their own?
Note that if we did this, we'd want to consider adding an operation
eqForeignPtr :: FP a -> FP a -> Bool
FP b -- possible variant but not very useful
which lets people test equality of the container and not the contents.
In fact the more I think on it, the more convinced I am that the Eq instance should compare contaner equality and not contents equality. The reason is that I believe Eq instances should follow the following design rule:
Eq instances should compute observational equivalence
I believe this is satisfied by all Haskell98 types, all the usual extensions (IORefs and friends) and by derived instances of datatypes.
So far, I agree.
Just to be clear what I mean by observational equivalence, consider comparing two IORefs x and y using this code:
eq x y = do a <- readIORef x writeIORef (a+1) b <- readIORef x return (x==y)
Obviously, this code is a bad way to test if two IORefs are the same IORef but the point is that we can observe the difference between them. Similarily, with ForeignPtrs, adding a finalizer to one and not the other and then watching for when the finalizer runs is a way that we might observe differences between two FPs.
There is no way in H98 + FFI to observe that a finalizer has run. In fact, in the finalizer discussion your point was that any means to observe this would lead to races.
What I don't mean by observational (in)equivalence is this:
One might be able to distinguish two data structures of type [Int] (say), by observing how much memory they consume.
This is perfectly true but Haskell semantics doesn't let you observe this so we'll rule any such 'observations' as irrelevant or invalid.
In C, we can observe it, and only in C can we observe whether a finalizer was executed. Nevertheless, your point regarding observations is an important one. We must not be able to observe a change in the equality of two values. This is why we cannot define equality of IORef's as the contents of the IORef's. However, the "contents" (ie, vanilla pointer on which it is based) of a ForeignPtr can never change. Hence, defining the equality of ForeignPtrs via their contents is perfectly valid. Hence, I propose to leave the definition in the spec as it was; ie, the equality of ForeignPtrs is defined via the vanilla pointer that they encapsulate. Cheers, Manuel
On Mon, Jan 20, 2003 at 11:03:36PM +1100, Manuel M T Chakravarty wrote:
Hence, I propose to leave the definition in the spec as it was; ie, the equality of ForeignPtrs is defined via the vanilla pointer that they encapsulate.
However, if you generalize ForeignPtrs (which I hope you will) this would require Eq on the underlying type. I guess this is no great hardship for the types people want to use it with.
Ross Paterson <ross@soi.city.ac.uk> wrote,
On Mon, Jan 20, 2003 at 11:03:36PM +1100, Manuel M T Chakravarty wrote:
Hence, I propose to leave the definition in the spec as it was; ie, the equality of ForeignPtrs is defined via the vanilla pointer that they encapsulate.
However, if you generalize ForeignPtrs (which I hope you will) this would require Eq on the underlying type. I guess this is no great hardship for the types people want to use it with.
We actually would have two alternatives: (1) We could define instance Eq a => Eq (ForeignObj a) where x == y = foreignObjToObj x == foreignObjToObj y and hence always reduce equality of ForeignObjs to the equality of the base type. (Where this equality doesn't exist, we just don't have an equality on the corresponding ForeignObjs, but they are otherwise still usable.) (2) We could have a builtin instance Eq (ForeignObj a) that implements equality by object identity and define ForeignPtr as follows: newtype ForeignPtr a = ForeignPtr (ForeignObj (Ptr a)) instance Eq (ForeignPtr a) where (ForeignPtr x) == (ForeignPtr y) = foreignObjToObj x == foreignObjToObj y Alastair could, then, still get at his ForeignPtr-equality-as-identity definition whenever he wants. I lean towards (2). In fact, it is one more argument for this generalisation of ForeignPtrs. Hence, I propose that we incorporate this generalisation unless there are any serious objections. Cheers, Manuel
participants (4)
-
Alastair Reid -
John Meacham -
Manuel M T Chakravarty -
Ross Paterson