-
Notifications
You must be signed in to change notification settings - Fork 217
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
Make isNormalized consistent with normalize #1115
Conversation
Currently the test fails with
There appear to be multiple things wrong with dhall-haskell/dhall/src/Dhall/Core.hs Lines 1938 to 1949 in 81ef514
|
Ah, that's an evil one in
Clearly the last line checks whether we're projecting any fields at all, right? Meeeeep! |
What should I do about cases like
Of the course the expression didn't typecheck in the first place. Should Can we allow a mismatch between |
@sjakobi: They should both behave the same, regardless of whether the expression type-checks. The standard is ambiguous about what the behavior is for an ill-typed expression like this, so any behavior we choose is fine here and we can probably go with whatever behavior is cheaper to compute. If we really wanted to be pedantic we might interpret the standard to mean that the normal form is |
We should never consider normalization on ill-typed terms. It is highly important to throw internal errors on occasions when normalization encounters ill-typed expressions. Raw terms don't mean anything until checked. This is very strictly enforced in all the compilers out there which support some dependent types (Agda, Idris, Coq, GHC), and should be likewise enforced here, eventually. It happens that normalization on ill-typed terms is possible right now, but only because of the coincidence of Going back a bit, I prefer that normalization does not silently compute on bad record projections. I doubt that it's performance-critical, but if we want to make it faster, then I am sure that it can be optimized significantly without silently breaking type safety. |
@AndrasKovacs: I think that's fine, as long as the two functions behave the same (i.e. if they need to |
dhall/src/Dhall/Core.hs
Outdated
@@ -346,6 +346,9 @@ instance Pretty Var where | |||
pretty = Pretty.unAnnotate . prettyVar | |||
|
|||
-- | Syntax tree for expressions | |||
|
|||
-- NB: If you add a constructor, please also update the Arbitrary instance in |
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.
Double-check that this comment doesn't get included in the generated haddocks
If it does, then you could move it underneath the Expr
type (since that's where people are most likely to insert new constructors anyway)
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's not included in the haddocks.
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've moved the note anyway.
dhall/tests/Dhall/Test/QuickCheck.hs
Outdated
=== (errorToMaybe (Dhall.Core.normalize expression) == Just expression) | ||
where | ||
{-# NOINLINE errorToMaybe #-} | ||
errorToMaybe :: a -> Maybe 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.
There's a spoon
package which provides utilities for doing this:
hackage.haskell.org/package/spoon/docs/Control-Spoon.html
In this case errorToMaybe
is the same as teaspoon
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! I made the switch.
I had to use the spoon
variant though, as there sometimes were nested errors that weren't caught in WHNF.
@AndrasKovacs I agree that it would be desirable that normalizing an ill-typed expression fails, and I hope that NbE gets us there! We could even have a property test for this! As long as type-checking and evaluation is separate though, I think it's ok not to repeat the type-checking during normalization. I look forward to your post! :) |
2628ee6
to
db8bd25
Compare
-- Given a well-typed expression @e@, @'isNormalized' e@ is equivalent to | ||
-- @e == 'normalize' e@. | ||
-- | ||
-- Given an ill-typed expression, 'isNormalized' may return 'True' or 'False'. |
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.
@Gabriel439 What do you think about this "contract"?
Otherwise I'd propose returning a Maybe Bool
that is Nothing
when normalization should fail.
Or maybe a custom type like data Normalized = NormalForm | NonNormalForm | IllTyped
or so?
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've played around with a Maybe Bool
-version of isNormalized
a bit. It seems rather tricky to nail down the consistency with normalize
though. I think this should wait until normalize
itself is more principled, possibly after the NbE changes.
isNormalizedIsConsistentWithNormalize expression = | ||
case Control.Spoon.spoon (Dhall.Core.normalize expression) of | ||
Just nf -> Dhall.Core.isNormalized expression === (nf == expression) | ||
Nothing -> Test.QuickCheck.discard |
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 have updated the test to simply discard if normalize
returns an error. I'd argue that this covers the cases that we care about the most.
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.
Thank you for restoring that property test! 🙂
To be more precise (citing the haddocks): Given a well-typed expression e, (isNormalized e) is equivalent to (e == normalize e). Given an ill-typed expression, isNormalized may return True or False. An even closer correspondence between isNormalized and 'normalize' is currently tricky to achieve as 'normalize' returns errors only for some ill-typed expressions. Once 'normalize' is more principled in this regard, isNormalized could be changed to return a (Maybe Bool). This re-enables a property test checking this consistency. Since 'normalize' returns errors for some ill-typed expressions, we catch have to catch these, which requires an NFData for Expr.
c23cff8
to
0926881
Compare
To be more precise (citing the haddocks):
An even closer correspondence between isNormalized and 'normalize' is
currently tricky to achieve as 'normalize' returns errors only for some
ill-typed expressions. Once 'normalize' is more principled in this
regard, isNormalized could be changed to return a (Maybe Bool).
This re-enables a property test checking this consistency. Since
'normalize' returns errors for some ill-typed expressions, we
catch have to catch these, which requires an NFData for Expr.