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

Reply via email to