-
Notifications
You must be signed in to change notification settings - Fork 218
unify Combine and CombineTypes #2651
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
base: main
Are you sure you want to change the base?
Conversation
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.
@winitzki I am sympathic to the change, but I think we should change the standard first. Otherwise we risk that this Dhall implementation diverges from it, which may result in confusing errors: For example, dhall <<< '{foo : Text} /\ {bar : Text}'
would work, but the same expression yields an error in another -- yet standard-compliant -- implementation.
dhall/src/Dhall/TypeCheck.hs
Outdated
VRecord xRs' -> | ||
return xRs' | ||
-- Make sure both are on the Left (both record values) or on the Right (both record types). | ||
rightTypeOrRecord <- case (leftTypeOrRecord, _R', r') of |
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 it would be better to first construct leftTypeOrRecord
, then rightTypeOfRecord
, and do the check that they are both Left
or both Right
afterwards in a separate step.
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 it would be better to first construct
leftTypeOrRecord
, thenrightTypeOfRecord
, and do the check that they are bothLeft
or bothRight
afterwards in a separate step.
I'm not sure I understand your comment. It appears to me that my code already does what you say: it first constructs leftTypeOrRecord
, then rightTypeOfRecord
, and then checks that they are both Left or both Right in a separate expression.
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 I mean is that you match on leftTypeOrRecord
when you construct rightTypeOrRecord
.
I think something like the following results in better error messages:
let isTypeOrRecord t = do
_T <- loop ctx t
let t' = eval values t
case (_T, t') of
(VRecord xs, _) -> return (Left xs)
(VConst _T', VRecord xs) -> return (Right (_T', xs))
_ -> do
let _T'' = quote names _T'
case mk of
Nothing -> die (MustCombineARecord '∧' l'' _T'')
Just k -> die (InvalidDuplicateField k t _T'')
leftTypeOrRecord <- isTypeOrRecord l
rightTypeOrRecord <- isTypeOrRecord r
case (leftTypeOrRecord, rightTypeOrRecord)
(Left ..., Left ...) -> ...
(Right ..., Right ...) -> ...
(Left ..., Right ...) -> die (TriedToCombineLitWithType ...)
(Right ..., Left ...) -> die (TriedToCombineTypeWithLit ...)
I will make a PR for the standard. |
Started working on a PR for the language standard. Still lots of new tests need to be added. |
Added new tests to this dhall-lang/dhall-lang#1404 @mmhat My question is - how can I run tests for dhall-haskell with the new tests that I have added to dhall-lang in that PR? |
@winitzki you'll have to update the submodule at |
I see. Let me first try to run tests by manually copying dhall-lang over. |
@mmhat I have rewritten the code in that place. I no longer use any extra Left / Right, the code is a lot simpler now. Could you please take a look and let me know if there are further improvements, especially as concerns error messages? |
@mmhat I have run this PR against all regression tests from the PR dhall-lang/dhall-lang#1404 and all passed. (I made sure new tests were getting run.) So, at the moment this looks good. |
The error is due to http pastebin not available:
I had previously an idea that it would be good to stand up temporary local servers during test, so that tests no longer depend on actual external services being available. This pertains to pastebin and also to the random string service. |
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 think we can refactor the code base such that the cases for Combine
and CombineTypes
use the same code path? After all, the former is supposed to be an alias for the latter if it is used with record types.
dhall/src/Dhall/TypeCheck.hs
Outdated
@@ -845,7 +833,45 @@ infer typer = loop | |||
|
|||
return (VRecord xTs) | |||
|
|||
combineTypes [] xLs' xRs' | |||
let combineTypesCheck xs xLs₀' xRs₀' = do |
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.
Maybe we can pull this out of look
and use it in both the Combine
and CombineTypes
cases?
Also, can we rename this to something more meaningful like checkForFieldCollisions
?
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.
Will do.
-- If both sides of `Combine` are record terms, we use combineTypes to figure out the resulting type. | ||
-- If both sides are record types, we use combineTypesCheck and then return the upper bound of two types. | ||
-- Otherwise there is a type error. | ||
case (_L', l', _R', r') of |
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.
Previously we threw a CombineTypesRequiresRecordType
if we were combining types. Do you think its feasible to retain that behaviour, i.e. throw that error when we combine types and one of the other errors when we combine values?
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.
Let's think about what error behavior we need. The new operator /\
should report an error if it is applied to a record term and a non-record term; or to a record type and a non-record type; or to a term and a type.
{ a = 0 } /\ 123
Error: Must combine records, instead got123
.{ a : Bool } /\ Natural
Error: CombineTypes requires a record type, instead gotNatural
.{ a : Bool } /\ { b = 0 }
. What should be the error? CombineTypes requires a record type but instead got a record term{ b = 0 }
, or Must combine records but instead got a record type{ a : Bool }
? I think we can just assume that the first operand is correct and the second one is wrong, and reportCombineTypes requires a record type but instead got a record term { b = 0 }
.
I will work on this tomorrow. It's definitely feasible to implement detailed error messages here.
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.
For the last case, maybe something like:
The combine operator `/\` works either on two record literals or two record types. You provided expressions
* `{a : Bool}`, which is a record type, and
* `{b = 0}`, which is a record literal of type `{b : Natural }`.
Also, for the other cases we probably want a similar error message if both arguments are neither record term nor record type; For example if the expression was 1 /\ 2
.
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 we need unit tests for all those cases. I don't see any unit tests.
Right now, an incorrect use of /\
gives an error CombineTypesRequiresRecordType
that talks about //\\
instead. I'll work on fixing this now.
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.
Yes, that would be great! Thank you for working on this!
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 changed the error messages, making them more detailed. I also added some unit tests to verify that those error messages are actually being generated. For instance, the tests verify that errors for the ∧
operator describe examples involving ∧
, while errors for the ⩓
operator describe examples with ⩓
.
Allow the
/\
operator to work on record types, exactly as in the functionality of//\\
.This PR follows up on discussions elsewhere:
dhall-lang/dhall-lang#1079 (comment)
dhall-lang/dhall-lang#1378 (comment)
This is a change that keeps all type-correct programs still type-correct but adds new correct programs.
Examples:
None of these expressions would be type-checked by the old Dhall code because the operator
∧
was not supposed to work on record types (only on record values). However, there seems to be no good reason to have two different operators for merging record values and for merging record types.Recent discussions move towards making Dhall have similar facilities for record values and record types. As long as there is no ambiguity between them, we can keep the same operators (dot,
//
,with
) to work with both record values and record types.Combine
operation (/\
) should work on record types just like theCombineTypes
operation (//\\
)Any comments welcome; I am fairly new to this code base and to Haskell.
Open questions:
/\
operator is inserted automatically when a record has duplicate fields. For example,{ a = { x = 1 }, a = { y = 2 } }
is translated into{ a = { x = 1 } /\ { y = 2 } }
at parsing stage and then reduced to{ a = { x = 1, y = 2 } }
at evaluation stage. Do we want the same behavior for record types? At the moment,{ a : { x : Natural }, a : { y : Bool } }
is a type error ("duplicate field: a"). Is there any reason not to rewrite this as{ a : { x : Natural } //\\ { y : Bool } }
like it is rewritten for record values? Why is it permitted for record values, what was the rationale? Perhaps it is related to the next question:{ a.b = 1 }
is equivalent to{ a = { b = 1 }}
but{ a.b : Natural }
is not equivalent to{ a = { b : Natural } }
, and in fact{ a.b : Natural }
is not allowed (it is a parse error). I see that there is an ambiguity:{ a.b : Natural }
could mean{ a : { b : Natural } }
or{ a = { b : Natural } }
. Perhaps we do not want to support the syntax{ a.b : Natural }
at all, because of that ambiguity?//\\
in the future and to remove that operator from the language, if the entire functionality of//\\
is reproduced by/\
after this PR?