Simon Marlow wrote: > On 04/07/2012 16:33, Roman Leshchinskiy wrote: >> Simon Marlow wrote: >>> We should be moving towards safe APIs by default, and separating out >>> unsafe APIs into separate modules. >> >> I completely agree with separating out unsafe APIs but I don't >> understand >> why modules are the right granularity for this, especially given >> Haskell's >> rather rudimentary module system. As I said, the module-based approach >> results in a significant maintainance burden for vector. > > The choice to use the module boundary was made for pragmatic reasons - > it reduces complexity in the implementation, but also it makes things > much simpler from the programmer's point of view. The programmer has a > clear idea where the boundary lies: in a Safe module, they can only > import other Safe/Trustworthy modules. The Safe subset is a collection > of modules, not some slice of the contents of all modules. The Haddock > docs for a module only have to say in one place whether the module is > considered safe or not. > > This is certainly a debatable part of the design, and we went back and > forth on it once or twice already. Conceivably it could change in the > future. But I don't think this is the right place to discuss the design > of SafeHaskell, and at least in our experience the current design seems > to work quite well.
I think we're misunderstanding each other slightly here. You seem to be using "separating out unsafe APIs" and SafeHaskell as synonyms whereas I'm only talking about how to do the former in the vector package, not necessarily using SafeHaskell. So to clarify my position: I'm all for distinguishing between safe and unsafe APIs and vector already mostly does that. But I don't want to support SafeHaskell in vector because SafeHaskell's notion of safety doesn't coincide with the one prevalent in vector and because SafeHaskell imposes requirements on the module structure which I consider too heavy-weight for what would only be an additional and, in this particular library, less useful guarantee. > Could you say something more about the maintenance burden? I imagined > that you would just separate the unsafe (in the SafeHaskell sense) > operations into separate modules. At the moment vector has *.Safe modules which reexport the SafeHaskell-safe functions from other modules. This means that whenever I add new functions, I have to remember to reexport them from the *.Safe modules. Adding a new operation to vector already requires touching 4 modules; having to update the *.Safe modules as well is impractical. Which is why I'd like to drop them. >From the maintainance point of view, this would become easier if I had *.Unsafe modules rather than the *.Safe ones. But this is a signficant restructuring and the only reason to do it would be to support SafeHaskell. Moreover, I believe (though I haven't checked) that there are calls from safe to unsafe functions and vice versa. So now I would have to have a common base module with both safe and unsafe functions and reexport those from the right top-level module. No, this just isn't feasible. >> At the risk of being blunt, I do find SafeHaskell's notion of safety >> somewhat obscure. In vector, all unsafe functions have the string >> "unsafe" >> in their name. Here are two examples of functions that don't do bounds >> checking: >> >> unsafeIndex :: Vector a -> Int -> a >> unsafeRead :: IOVector a -> Int -> IO a >> >> Unless I'm mistaken, SafeHaskell considers the first one unsafe and the >> second one safe. Personally, I find vector's current notion of safety >> much >> more useful and wouldn't want to weaken it. > > SafeHaskell's notion of safety is very clear: it is essentially just > type safety and referential transparency. It would be impossible to > have a clear notion of safety that considers some IO operations unsafe > and others safe: e.g. do you consider reading a file to be unsafe? Some > applications would, and others wouldn't. IO is certainly problematic. However, it is quite possible to have a clear notion (or, rather, notions) of safety for IO in a particular problem domain, such as arrays. For vector, "natural" safety includes bounds checking. > Sticking strictly to > clearly-defined properties like type safety (and a couple of other > things, including module abstraction) as the definition of safety is the > only sensible thing you can do. As I said, I would like to be able to have multiple notions of safety. What SafeHaskell provides is essentially the lowest common denominator. I agree that it is useful to have but only in addition to other, tigher and perhaps more domain-specific concepts which I consider more useful. But the module-based approach requires me to structure the library around SafeHaskell, essentially making it the "main" concept of safety in vector and that's not a design I would be comfortable with. > But this is beside the point. Since unsafeRead is considered safe by > SafeHaskell, you have the option of either putting it in the safe API or > the unsafe API; it's up to you. But wouldn't putting it in the unsafe API essentially be "abusing" SafeHaskell to express a notion of safety different from type safety + referential transparency? For me, the only sensible structure would be putting unsafeIndex in the *.Unsafe module and unsafeRead in the safe one. I strongly dislike this. >> Additionally, >> Data.Vector.Storable is entirely unsafe even in the SafeHaskell sense >> (as >> in, it unsafePerformIOs essentially arbitrary code) due to the design of >> the Storable class - there are no safe bits there at all. It still uses >> "unsafe" to distinguish between functions that do bounds checking and >> those that don't. What would be the benefit of moving functions like >> unsafeIndex into a separate module (and would it be called >> Unsafe.unsafeIndex then? or would it be Unsafe.index?)? Would you >> advocate >> renaming Data.Vector.Storable to Data.Vector.Storable.Unsafe? > > Since it's the entire module in this case, I think it would be fine to > just remark in the documentation for the module that the API is unsafe, > and briefly explain why. Well, it's not that simple. Data.Vector.Storable exports exactly the same interface as Data.Vector.Unboxed and Data.Vector (modulo operations it doesn't support). Now, if I move some functions from Data.Vector.Unboxed to Data.Vector.Unboxed.Unsafe, I would also have to move the corresponding functions from Data.Vector.Storable to Data.Vector.Storable.Unsafe even though neither of these last two modules would be safe. This just seems wrong to me. Roman _______________________________________________ Haskell-platform mailing list Haskell-platform@projects.haskell.org http://projects.haskell.org/cgi-bin/mailman/listinfo/haskell-platform