[MUSIC]
Okay. In this segment, I want to talk about shadowing.
Shadowing is when you add a variable to an environment when before you added it,
that variable was already in the environment.
So, to do this, we're going to end up having multiple variable bindings for the
same variable in the same file. that can be a little bit confusing.
It's usually poor style to do that, although it does come up with certain
idioms. But it's going to be a great way to make
sure that we really understand how environments work.
So, I'm not going to give you any new rules, there's no new syntax or no new
type checking or evaluation rules here. We're just going to use this idea of
shadowing to really explain how environment and variable bindings work so
that we have that solid foundation we need when we go to add things to the
language. So, let's just start here by adding this
binding, val a = 10 to our environment. And so, we know now that in the static
environment, a will have type int. And in the dynamic environment, a will be
bounded at the value of ten. And so, of course, if we then continued
in our file with a * 2 b would have the value 20.
I'm going to ignore the static environment here just for expediency but,
of course, that's, that's relevant as well.
[COUGH] So now, the interesting thing happens if we say, val a = 5,
alright? And the thing I really want to emphasize,
is that b is still bound to twenty.
And, in fact, if I next said val c = b, even though right here, I had an
environment where a maps to 5 and b maps to 20,
now I have an environment where a maps to 5, b maps to 20, and c maps to 20.
Because we know that the way you evaluate this expression b is you look it up in
the dynamic environment, you get 20 and then you extend the dynamic environment
so that now c maps to that result, alright?
So, the fact that b was earlier created by evaluating a * 2 is no longer
relevant. That was back in an environment where a
mapped to ten. We got twenty, we extended our
environment so that indeed a mapped to 10 and b mapped to 20.
And that's the end of the story. The fact that we had a * 2 is no longer
relevant. And I should emphasize as well, that this
val = 5, this is not an assignment statement,
okay? There's absolutely no way in ML to mutate or change the fact that a mapped
to 10 in the previous environment. All we get is in the subsequent
environment, a is now shadowed. We have a different mapping for a in a
different environment and a is now five. So, that's the idea.
Let's just do a little more examples to make sure we got it.
If I now say val d = a I'm going to end up with an environment with all the
things I did have and now d maps to five because a maps to five in this
environment where I am. If I now say, what about val a = a + 1.
Again, there's no such thing as an assignment and this makes perfect sense.
The way a variable binding works is we evaluate the expression in the current
dynamic environment. So, a maps to five, we add one to that,
we get six and then we create a new dynamic environment where a maps to six
along with everything else we had previously, but we're now shadowing the
fact that a maps to five in an earlier environment or a mapped to ten in an even
earlier environment, okay? So indeed, if we now say, val = a *
2 f will map to twelve. I should emphasize as well that something
you cannot do is a forward reference and we know why for the same reasons.
So, if I had this code here, it would not type check and the reason why is when I
have to go to type check, this expression f - 3, f is not yet in the environment.
So, when I look it up in the static environment, it's not there and now we've
got a type error message. So, that's the example I wanted to show,
let's real quick try it out and make sure that I typed everything in correctly.
Oh, semicolon. Let's there we go, we'll try this again.
Notice I didn't panic when I got an error message, there we go.
And we see all the values I saw but for those earlier a's the read of all print
loop is trying to be helpful and just say, you know, this value can't possibly
be relevant to you. I know it's shadowed in the dynamic
environment you now have down here where a is bound to six.
So, rather than show you what a was back up in this earlier environment, I'll just
print hidden value. Alright.
So, that is the code example, let's go back to the slides here and show you
again that in the key example here, when you have something like val, a = 1, b =
a, a = 2, that in the environment you end up with b is bound to one and a is bound
to two. There's actually two reasons for this,
either one of which would be enough. the first reason is that we evaluated
that expression we used for b, that expression a on the right hand side
of the equals, eagerly back when we evaluated the variable binding for b.
So, after we've done that expression evaluation, b is bound to one and it
doesn't matter how we got that one. It will never effect the fact that we got
a one and that's the end of the story. The second reason b will stay bound to
one is that that second val2 = 2 is not an assignment statement.
The earlier a, the a that leads to the environment that is used to evaluate b is
still one. All we do is create a second variable b
that shadows it. So, it turns out, this is the reason I'm
so insistent on not having use commands in your RPL more than once for the same
file. Because if you say use multiple times for
the same file, you're going to repeat whatever bindings were there the first
time you said use for the file and they are still there the second time you said
use for the file. And while it's legal in ML to have that
sort of shadowing and to reintroduce those bindings, it can be really
confusing, right? Maybe you took a binding out but it's
still there because of the previous use or maybe you have some strange shadowing
that makes it look like your code is correct when, in fact, you're it's, it's
wrong because of how the shadowing happened to work for the multiple use
statements.