-
Notifications
You must be signed in to change notification settings - Fork 2
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
Add Typing Environments #78
Conversation
727bc04
to
8458a75
Compare
This should fix some problems with #76, but it doesnt have assignments yet. |
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 should fix some problems with #76
I thought that problem is already fixed? The problem was that we were renaming programs with loops and we were implicitly assuming that a loop always evaluates at least once, so every variable bound in the loop body gets introduced into the scope. But that was a false assumption, so we removed loops from the typed language. No loops in the typed language = no scoping problems.
There's still ambiguity in the untyped language, but that doesn't matter, at the moment at least.
but it doesnt have assignments yet.
What do you mean by assignments?
Now we have proper scoping rules for for loops, meaning that the discussion for the value of the for loop variable is moot, as the following program is ill typed
What sort of ambiguity you refer to?
Since we add a scope for the for loop, the following programs are ill typed
and
Furthermore, the last assertion in the following program will be true
I want to add an assignment operator to modify a variable without adding a new binding, which would make the following program's assertion true
|
I misunderstood what you're after. I've responded in the Loops issue. |
withVar var kont = do | ||
tyEnv <- ask | ||
case Map.lookup var tyEnv of | ||
Just tVar -> kont tVar |
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 doesn't look right. The point of MonadScope
was to assign the same unique to all variables that share a name. Regardless of whether those variables are distinct or not. That logic doesn't work for mapping untyped variables to typed ones as here we need to track scopes properly.
In particular,
let x = 0;
let x = 1;
assert x == 1;
now elaborates to the wrong
let x_0 = 0;
let x_1 = 1;
assert x_0 == 1;
which is obscured by the fact that we recover the correct scoping in the renamer.
This suggests that we should have two kinds of golden tests: the program that we get right after type checking and the program that we get after renaming. Unless I'm wrong, it'll show that we now do weird things in the type checker.
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.
which is obscured by the fact that we recover the correct scoping in the renamer.
Hm, I looked into the renamer and I don't think it can recover scoping. I'm really confused now, how is this whole thing working? Golden tests suggest that we do have correct scoping, but looking at the code I don't see how that can possibly be the case. Can we have a unit test with
let x = 0;
let x = 1;
assert x == 1;
where we only type check things and don't rename them?
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, I think I see what's going on. So taking
let x = 0;
let x = 1;
assert x == 1;
we call
checkStatement (R.ELet var m) kont =
case uniOfVar . R.unVar $ var of
Some uni -> do
tM <- T.withKnownUni uni $ checkExpr m
withVar var $ \ tVar -> kont [T.ELet (UniVar uni tVar) tM]
over the first and then the second let, so we extend the initially empty environment first, but then we don't extend it and instead use the already existing binding. So the program elaborates to
let x_0 = 0;
let x_0 = 1;
assert x_0 == 1;
which then gets renamed correctly.
I'm not entirely sure if it should be causing us any problems right now, but it certainly will in future. For example, we'll need to inline the value of a loop variable and if the loop variable has the same name than some previously referenced variable, then we can't just use the value of that variable, we have to actually update the environment instead of reusing the existing entry that just became shadowed. Or in future we may have stuff like
let x = T;
let x = 1;
assert x == 1;
and here we also need to update the environment and can't reuse the existing entry, because the type of the new binding differs from the type of the old 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.
Ok, this actually gave me slight headache as well, because there are two notions of environment conflated into one:
- tracking which uniques are assigned to which identifier, and
- tracking which variables are in scope.
I do not think that untangling them together should be an issue.
Explanation
- I refactored
withVar
to pass the typed version of the variable to the continuation in order to encapsulate the process of changing fromR.Var
toT.Var
. - To keep the old behaviour of giving the same unique to the same identifier, we use a
Map R.Var T.Var
to track which uniques are assigned to which identifiers. - We reuse the same map to track which variables are in scope.
- As variables are intrinsically typed, we use the name of variable to infer its type/uni.
Once we switch to extrinsically-typed variables, we will probably need to:
- Use a different map for tracking which uniques have been assigned (
Map (R.Var, Uni) T.Var
) - Use a proper typing environment, which similar to
Map R.Var (T.Var, Uni)
.
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 had this:
tracking which uniques are assigned to which identifier, and
and not this:
tracking which variables are in scope.
Now that we have the latter and given that I can always get the list of external variables of a program, I don't think we need the former anymore. So once we switch to extrinsically-typed variables I propose to only have Map R.Var (T.Var, Uni)
(or Map R.Var T.UniVar
for that matter).
Or do you have a use case for Map (R.Var, Uni) T.Var
?
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.
Or do you have a use case for
Map (R.Var, Uni) T.Var
?
Not really.
Given that we relax our requirement for uniques, maybe we should parameterise the typed ast by the type of variable as well. Personally I would like to work without uniques and supply. Especially, since we will perform a renaming step in the compiler anyways.
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 still does not look right.
Why do we need the
case Map.lookup var tyEnv of
Just tVar -> kont tVar
part? We needed this kind of logic previously, but I'm currently failing to see why we need it now.
What happens if we remove that and do
tVar <- freshVar . R.unVar $ var
local (Map.insert var tVar) $ kont tVar
(the Nothing
case) unconditionally?
Is this something that we're going to have in the long run? If the plan is
to inline the value of a loop variable, then we can unscope the loop
variable after the loop (and only inline in the body of the loop then).
Hmm, I need to think about it. Yes, we could technically inline the loop
variable and erase it completely.
I am still not sure how to do it properly, as there are programs in the
spec that depend on variables bound in the body of a for loop.
Lemme think about it for a bit.
|
Why is that a problem?
will elaborate to
|
Hmm, maybe I am overdoing it. |
7df0eb1
to
76e1b87
Compare
- Add ext keyword to Raw.Core language, - Add ext field to Core.Program, - CPS the TypeChecker, - Add Type Environments, - Generate exts statements in progToString, - Add test case from issue 76, - Deprecate TinyLang.Generator, - Add intersection to Env.
76e1b87
to
6f59874
Compare
withVar var kont = do | ||
tyEnv <- ask | ||
case Map.lookup var tyEnv of | ||
Just tVar -> kont tVar |
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 still does not look right.
Why do we need the
case Map.lookup var tyEnv of
Just tVar -> kont tVar
part? We needed this kind of logic previously, but I'm currently failing to see why we need it now.
What happens if we remove that and do
tVar <- freshVar . R.unVar $ var
local (Map.insert var tVar) $ kont tVar
(the Nothing
case) unconditionally?
Ah yes, we can do that, then we do not need to do renaming in type checking. |
Adding typing environemnts
progToString
to generateext
keywords,for
loops.Program
section specifying allext
variables.freeVars
toextVars
,these are not the free variables that you are looking for.
At the moment there is no test case for for loop shadowing an
ext
variable.