-
Notifications
You must be signed in to change notification settings - Fork 44
Smart constructors #137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Smart constructors #137
Conversation
alphaRename = (\case | ||
Forall_ s1 v p -> go Forall_ s1 v p | ||
Exists_ s1 v p -> go Exists_ s1 v p | ||
_ -> error "Input must be an Exist or Forall at the top level" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make this function total? For example, if its argument is not a binder, could it just return the argument unmodified? Or, can we encode this restriction in the type?
@@ -0,0 +1,124 @@ | |||
{-| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it necessary to implement substitution ourselves, or could we outsource this to a library like bound?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. Maybe. There was already a substitution function in Kore/Substitution, but I needed one that substitutes terms for terms and not just terms for variables.
-> CommonPurePattern level | ||
-> CommonPurePattern level | ||
-> CommonPurePattern level | ||
subst a b (Forall_ s1 v p) = handleBinder a b Forall_ s1 v p |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to write this as subst a b = \case ...
since the first two variables are always bound the same.
-> CommonPurePattern level | ||
subst a b (Forall_ s1 v p) = handleBinder a b Forall_ s1 v p | ||
subst a b (Exists_ s1 v p) = handleBinder a b Exists_ s1 v p | ||
subst a b pat = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intention here might be clearer using guards rather than if
.
subst a b (Forall_ s1 v p) = handleBinder a b Forall_ s1 v p | ||
subst a b (Exists_ s1 v p) = handleBinder a b Exists_ s1 v p | ||
subst a b pat = | ||
if pat == a then b else Fix $ fmap (subst a b) $ unFix pat |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think subst
could actually be rewritten as a catamorphism using embed
. Maybe that's a little tricky if we're doing whole-term substitution instead of only variables. Although, maybe not: I don't see that pat
could contain a
as a subterm and also be equal to a
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the test coverage for the new code?
Rewrites_ s2 a b -> Rewrites_ <$> o s2 <*> c a <*> c b | ||
Top_ s2 -> Top_ <$> o s2 | ||
Var_ v -> Var_ <$> var v | ||
other -> error $ "Unknown constructor " ++ show other |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does one still get warnings about not matching all possible cases when using pattern synonyms? If yes, then it may be better to drop the "other" case, because we use --pedantic on Jenkins, which will transform that into an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pattern Synonyms + GADTS = GHC definitely can't tell if it's complete or not, so without the "other" it doesn't compile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, wow, I was waiting to use these synonyms in my condition-evaluation pull request, but now I'm not so sure anymore.
One of the reasons we chose Haskell is because all of the built-in safety, but it seems to me that Haskell programmers don't usually want it that much.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PatternSynonyms
are a big problem for completeness checking. For our case, we really only want to be using the synonyms for construction, not matching. The benefit of using Fix
is that we should be able to write algebras that only need to match on the underlying functior!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, in this case the COMPLETE pragma works. So writing a complete list of cases with
Fix ( ApplicationPattern (Application ...
Fix (AndPattern (And ...
is equivalent to
App_ ...
And_ ...
and the compiler will know it's complete.
Var_ v -> Var_ <$> var v | ||
other -> error $ "Unknown constructor " ++ show other | ||
|
||
inputSort f = patternLens f pure pure pure |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add function signatures? Also, could you document all exported functions?
pattern Or_ s2 a b = Fix (OrPattern (Or s2 a b)) | ||
pattern Rewrites_ s2 a b = Fix (RewritesPattern (Rewrites s2 a b)) | ||
pattern Top_ s2 = Fix (TopPattern (Top s2)) | ||
pattern Var_ v = Fix (VariablePattern v) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why didn't you add patterns for strings and chars?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
@u-- can we use better commit messages? Try to make them useful for code reviewers. Someone somewhere said "given the commit message, the reviewer should be able to re-implement your commit". Also, I recommend squashing fixup commits into the commit which introduced the feature. The story "I introduced feature A and then " is a lot simpler than "I introduced feature A and then and then a fixup A.1". |
@ehildenb what do you mean w/ squashing? Like branch off the original commit that introduced the feature? |
Also, I didn't mean for the Hashable instances in Common.hs to be in this branch... but since it relates to the AST I guess it fits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know I was a reviewer! Most of my comments are about understanding what you did and about having a style that's consistent with the existing code.
-- -> CommonPurePattern Meta | ||
|
||
-- No way to make multiline pragma? | ||
{-# COMPLETE And_, App_, Bottom_, Ceil_, DV_, Equals_, Exists_, Floor_, Forall_, Iff_, Implies_, In_, Next_, Not_, Or_, Rewrites_, Top_, Var_, StringLiteral_, CharLiteral_ #-} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this make sense: could you add a comment near Pattern in Common.hs that one should update this list when updating the Pattern case list?
_ -> False | ||
|
||
|
||
-- Flexible patterns are those which can be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of these comments do not have a "|". Does haddock produce documentation from them?
import Data.Kore.ASTUtils.SmartConstructors | ||
|
||
|
||
subst |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Could you document exported methods?
- One-letter variable names don't usually help people reading your code. Sometimes that does not matter much, but, as an example, here, the meaning of "a" and "b" can be sort of inferred only when reaching the line
| pat == a -> b
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what to call the "phi_1" and "phi_2" in something like phi[phi_1/phi_2]
Substituted term? Substituting term?
It's made worse by the fact that some papers write it the opposite way, phi[phi_2/phi_1]
I guess I can call them "old" and "new"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Old and new seems fine. Substituted and subtituting is also fine. Wrapping them in data types so that people calling your method do not mix the arguments is even better, but I can't usually convince others that it's worth doing. Anyway, the issue was that you had three patterns as arguments and it was not obvious which is which.
go binder s1 v p = | ||
binder s1 (replacementVar v p) | ||
(subst (Var_ v) (Var_ $ replacementVar v p) p) | ||
replacementVar v p = head $ alternatives v \\ (S.toList $ freeVars p) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious, you don't have to fix this: why not filter with ('member' freeVars p)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thing about code like filter (member freeVars p)
is, if you later change to a situation where asymptotics matter, it's easy to make a linear operation accidentally quadratic that way.
For instance if alternatives
were also a Data.Set, set union/intersect/subtraction are O(m+n), but filtering on membership would be O(m log n).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it O(m*n) right now, where m is the first index that was not already used, and n is the free var count?
Even if my estimate is correct, I guess it may be reasonable if m is likely to be small (which, by default, it should be). I can't tell right now if it would increase when doing many similar substitutions as we execute code and produce proofs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, I thought \ assumes sorted. Yeah, it is slower in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I just noticed: freeVars p
is being called over and over in the "filter" case. That's another accident that can't happen when you use operations over whole datastructures like union/intersect/difference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. As I said, I was just curious. We're not supposed to optimize things right now, and we should prefer code that is safer/easier to read/whatever.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I'm just explaining why I think it's a good default. For now I filtered with S.member, in the future maybe substitution could be outsourced to kmett's bound or something.
|
||
substitutions :: TestTree | ||
substitutions = testGroup "substitutions" | ||
[ testCase "subTrivial" $ assertEqual "" subTrivial subTrivialSolution |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This project uses 4 spaces for indentation almost everywhere, "where" and "=" (only sometimes and only when used in function definitions) are the only exceptions I can think of right now. The main reasons for using 4 spaces are uniformity and the fact that "where" and "=" use half of a normal indentation and it looks bad when they are indented by only one space. Note that function signatures are also indented by 4 spaces instead of 2.
BTW, I think that there are places above where you're using a one-space indentation.
@@ -0,0 +1,129 @@ | |||
{-| | |||
Module : Data.Kore.ASTUtils.Substitution | |||
Description : Substitute phi_1 for phi_2, avoiding capture |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a short comment or example saying why this would be useful (as opposed to substituting variables)?
@@ -101,14 +109,16 @@ data Id level = Id | |||
{ getId :: !String | |||
, idLocation :: !AstLocation | |||
} | |||
deriving Show | |||
deriving (Show, Generic) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious, not asking for a fix: Where is this generic thing used (for any of the instances defined here, not necessarily this one)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generic is needed to derive classes like Hashable.
@@ -0,0 +1,217 @@ | |||
{-# LANGUAGE MultiParamTypeClasses #-} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't get an answer to one of my previous questions, so I'll try asking it this way: What's the test coverage for the new code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean hpc report
? About 70-80%
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We usually use stack test --coverage
and look at the html report, I guess that may use hpc
behind the scenes.
That number, by itself, does not mean much. We are aiming for 100% coverage. Until now we allowed the coverage to be lower for things like automatic instance derivations and struct getters, but we usually want close to 100% for everything else. So, if the missing 20-30% are things like that, it should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please take a look at the new comments before submitting ans see if they make sense.
|
||
-- subst2 a b = cata (\pat -> if Fix pat == a then b else Fix pat) | ||
|
||
-- handleBinder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can leave it if it makes sense, but I'm not a fan of code that's commented out. If you allow time to pass, the code will be more and more out of date, but people will avoid deleting it since they assume it has a purpose.
@@ -0,0 +1,217 @@ | |||
{-# LANGUAGE MultiParamTypeClasses #-} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We usually use stack test --coverage
and look at the html report, I guess that may use hpc
behind the scenes.
That number, by itself, does not mean much. We are aiming for 100% coverage. Until now we allowed the coverage to be lower for things like automatic instance derivations and struct getters, but we usually want close to 100% for everything else. So, if the missing 20-30% are things like that, it should be fine.
No description provided.