Repository navigation
A "safe" subsection of the library #143
Description
Activity
I have come across something fairly nasty:
Agda.Builtin.Intdepends uponAgda.Builtin.Stringwhich uses apostulateto declare theStringtype. As a consequence any module importingAgda.Builtin.Intcannot be accepted by the current--safeoption Agda has.In other words either all
Data.Integer.*and any other module depending upon them have to be in the unsafe part of the library or Agda needs to provide e.g.Safe.Agda.Builtin.IntwithoutprimShowInteger(and therefore without any dependence onAgda.Builtin.String).- added a commit that references this issue
on Aug 1, 2017 It sounds like maybe we should relax the requirements of
--safeto accept postulates of typeSet.I don't understand why Agda.Primitive doesn't trigger an error given that it does use
postulateall over the place.All the tests are green. I think the branch is more or less ready to be merged (and I'd like to avoid it diverging too much from
masterbefore merge because that's a lot of changes to maintain!). When / If Agda gets more liberal in terms of what it accepts as safe, we can move more things intoSafe/. It should all be transparent from the users' point of view anyway.The main issues I see are:
Wider Use
Do we want to ask people on the agda ML to try to check their developments with the current
safebranch to make sure it doesn't break anything (e.g. we haven't forgotten to re-export any names anywhere)?Or would it be enough to run Agda's stdlib-based test suite on the modified one? In which case I guess it's simply a matter of creating a dummy branch on agda/agda where we update the submodule to point to the safe branch.
Stylistic Issue
Do we want
{-# OPTIONS --safe #-}as a header in all of theSafe/modules or are we just happy with having travis checkSafe/index.agdawith the appropriate command line options? At the moment we have both the header and the travis separate check (the latter is necessary even though we have the headers because of agda/agda#2487).Just had a look at the branch. What's the plan for maintaining this? Is every safe change to the non-safe part going to have to be copied over to the safe part manually?
Or is there some way of regenerating the safe part automatically?
The idea is to replace
masterwithsafe. Given that each unsafe module re-exports the content of its safe counterpart, it should be completely transparent for end users.New definitions can be added either to the safe module if they are innocuous, or to the unsafe one if they're not.
Data.Nat.Baseis a pretty good example: pretty much all of its former content is now inSafe.Data.Nat.Baseexcept forerasewhich is inData.Nat.Base.Oh yes. Apologies, hadn't noticed that all the non-safe versions contents had been moved.
Given that each unsafe module re-exports the content of its safe counterpart, it should be completely transparent for end users.
Until they go browsing through it!
I'm a little worried about what this is going to do to library usability. For instance looking for the decidability of the divides relation
_∣_, would currently only involve looking throughData.Nat.Divisibility. However it is now going to require browsing bothSafe.Data.Nat.DivisibilityandData.Nat.Divisibilitywhich are deep in different parts of the heirarchy. What's more you can't off-hand predict which one it's going to belong to.This seems like a big impact on the average user who's not that concerned about rigorous safety. Can users who are concerned about safety not just type-check it on the command line using the
--safeoption? Won't that automatically highlight the unsafe terms used? Or does it over-estimate, and mark otherwise safe terms unsafe if they share a file with unsafe terms?However it is now going to require browsing both Safe.Data.Nat.Divisibility and Data.Nat.Divisibility which are deep in different parts of the heirarchy. What's more you can't off-hand predict which one it's going to belong to.
The doc for the unsafe module systematically links to the safe one because it imports it.
Can users who are concerned about safety not just type-check it on the command line using the --safe option?
--safeonly succeeds if all of the definitions in a module (and all the module it depends upon) are safe. Hence the need for a split: it is currently near impossible to typecheck a development relying on the standard library with the--safeoption. I'm actually surprised that after 100+ articles using Agda published, no reviewer seems to have complained about this.The doc for the unsafe module systematically links to the safe one because it imports it.
Hmm... I guess so.
--safe only succeeds if all of the definitions in a module (and all the module it depends upon) are safe.
Whereas what it should ideally do is only check the definitions in other modules that are actually used. From my position of complete ignorance, this seems like it should be doable using some sort of flow analysis. I'm not sure if it is or not! If it is possible, wouldn't this be a far better solution than partitioning the standard library in two?
I guess its kind of the dual to agda/agda#2487. File options should be more strict with respect to imported definitions, and command line options need to be less strict....
Whereas what it should ideally do is only check the definitions in other modules that are actually used.
But then you'd have to call
agda --safeon every single module attached to a paper to make sure the whole development is not using any dirty tricks. Right now you can simply generate anEverything.agdaand typecheck that.Edit: I should perhaps mention the existence of
C-c C-zto search through definitions in scope. Which ought to be more efficient than navigating files when looking for a specific result.But then you'd have to call agda --safe on every single module attached to a paper to make sure the whole development is not using any dirty tricks
Aren't you going to have to do that anyway? Even if you've only used modules from the
Safehierarchy in the standard library, you still need to check that your own modules don't use any dirty tricks.You can call
agda --safeon a moduleEverything.agdaimporting all the modules in your development and it'll make sure everything is a-ok. Just like the travis test at the moment checks the whole ofSafe/simply by checkingSafe/index.agda.13 remaining items
Why doesn't the command:
agda --safe src/Relation/Binary/HeterogeneousEquality/Quotients.agdathrow an error?
When trying of reproducing the problem in Agda
stable-2.5branch I got the error:$ make up-to-date-std-lib ... cabal exec -- GenerateEverything GenerateEverything: src/Relation/Binary/HeterogeneousEquality/Quotients.agda is malformed. CallStack (from HasCallStack): error, called at GenerateEverything.hs:67:15 in main:Main GNUmakefile:16: recipe for target 'Everything.agda' failedMatthewDaggitt commented
on Apr 11, 2018 CollaboratorAuthorMore actionsHmm weird. I'm on a clean version of 2.5.3. I'm having the same problem with the
--without-Koption...MatthewDaggitt commented
on Apr 12, 2018 CollaboratorAuthorMore actionsTracked it down, it was another subtler instance of agda/agda#2487. As I'd compiled the whole library without
--safe, the.agdaifiles were lying to me when I subsequently recompiled it all with--safe. I'm rapidly coming to the opinion that the--safeoption currently doesn't provide anything much in the way of guarantees (either in-file or at the command line) 😞--safe+--ignore-interfacesis pretty reliable- On Thu, Apr 12, 2018 at 07:46:35AM -0700, G. Allais wrote: `--safe` + `--ignore-interfaces` is pretty reliableExcept that it cannot be resumed easily if there is a crash before completion (most likely due to starting Agda with insufficient heap space, which will in turn quite likely be due to hardware constraints... ). Admittadly, this should not be a problem for the standard library. But in general, removing all .agdai files is safer.
I'm rapidly coming to the opinion that the
--safeoption currently doesn't provide anything much in the way of guarantees (either in-file or at the command line)The
--safefeature was originally implemented by someone who is no longer contributing to Agda, and it seems as if no regular developer is eager to maintain it. Fixing the documented bugs related to--saferequires some work, but I think it should be doable for someone who is not intimately familiar with the internals of Agda. Contributions are welcome.MatthewDaggitt commented
on Apr 13, 2018 CollaboratorAuthorMore actionsI'm rapidly coming to the opinion that the --safe option currently doesn't provide anything much in the way of guarantees (either in-file or at the command line)
Fixing the documented bugs related to --safe requires some work, but I think it should be doable for someone who is not intimately familiar with the internals of Agda. Contributions are welcome.
While I would love to, I feel I already spend far too much of my time of my PhD on internet routing protocols playing around with Agda! Sorry, I realise that my original comment perhaps came off as a complaint, it wasn't meant to be.
MatthewDaggitt commented
on Apr 13, 2018 CollaboratorAuthorMore actionsI've started working on creating the various
Unsafemodules (very similar to @gallais work on #122). It's all mostly okay (although haven't got toData.Integeryet...) but the first problem I've come across is inData.Nat.DivMod.agda-stdlib/src/Data/Nat/DivMod.agda
Lines 29 to 35 in 5914224
record DivMod (dividend divisor : ℕ) : Set where constructor result field quotient : ℕ remainder : Fin divisor property : dividend ≡ toℕ remainder + quotient * divisor The problem is that the
remainderfield is of typeFin divisorand notℕ. For efficiency's sake (although apparently it doesn't work very well, see #150) we erase all the various proof that allows us to convert fromℕtoFin divisor. The erasure usestrustMewhich is, of course, unsafe. This then pollutes large parts of the following modules that depend onDivMod:Data.Nat.Divisibility Data.Nat.Primality Data.Nat.Show Data.DigitThere are three options that I see:
- Proceed as planned and stick all dependencies in
Unsafemodules.- Advantages: A little more backwards compatible (although obviously all module imports will have to change).
- Disadvantages: we label large sections of our
Natlibrary asUnsafe, including our primality test which is a tad embarrassing.
- Change the type of
_mod_and the fieldremaindertoℕrather thanFin divisor, and then create separate proofs that show they are all related in the right way.- Advantages: This would provide a faster and safe version of
_mod_, and hence avoid polluting the safety of the rest of the library with the use oftrustme. The proofs that the right properties hold, still exist, and users themselves can make the choice of whether or not to erase them for efficiency reasons. - Disadvantages: not backwards compatible
- Advantages: This would provide a faster and safe version of
- Simply remove the
erasecalls:- Advantages: Completely backwards compatible and all the files automatically become safe.
- Disadvantages: Performance gets even worse.
I'd prefer the 2nd option as it feels much more principled. Not all users will require the proofs, but those that do then have much finer grained control over how they use them. The non-backwards compatability is unfortunate but that's why we're labelling it v1.0?
- Proceed as planned and stick all dependencies in
Disadvantages: Performance gets even worse.
I am astonished by the performance part. Might I ask how important performance is, and how many people are relying on it? Sorry about my ignorance...
What about irrelevance? I've not seen the description in official doc, but from my understanding, this should be the secrete magic to tame the tradeoff between complex proof flow and performance, is it not?
MatthewDaggitt commented
on Apr 16, 2018 CollaboratorAuthorMore actionsI am astonished by the performance part. Might I ask how important performance is, and how many people are relying on it? Sorry about my ignorance...
No idea. I don't personally normalise any of my proofs. But given that Ulf has put significant effort into building agda-prelude with a strong focus on performance I gather it's definitely important for some people. Also issue #150 is is actually directly related to the performance of this part of the library.
What about irrelevance? I've not seen the description in official doc, but from my understanding, this should be the secrete magic to tame the tradeoff between complex proof flow and performance, is it not?
Again, as I never worry about actually normalising I've never had any need to dip into irrelevance. Maybe someone with more experience could chime in?
Overall, I think the consensus is that the standard library focuses on making proofs easier rather than on performance. Having said that, we should probably generally avoid making performance worse except for good reason. Hopefully we can come to some conclusion here whether or not safety is a good enough reason!
Reacted by G. Allais#350 which has just been merged provides a way forward on this problem. It adds a new "safe" modular arithmetic library. The old library can then be placed in the
Unsafemodule. The dependencies have been updated to use the new library.MatthewDaggitt commented
on Aug 30, 2018 CollaboratorAuthorMore actionsClosed with 8885d7a
As discussed in #122 it is currently impossible to typecheck a development relying on the standard library with the --safe option because it does not isolate properly its uses of unsafe features.
The agreed upon course of action is that there will be a Safe.* subset of modules. The current module names will be kept the same and will re-export exactly what they currently contain, ensuring full backwards compatibility whilst making it possible to only rely on the subset that is recognised as being safe by Agda.