About documentation, immutability, first-class functions and dynamic and static environments
One of the most important things I’ve learned during the Programming Languages Part A course is the idea of dynamic and static environments. Considering I’ve never seen this explained anywhere else so far, I’ll try my best to explain this.
let x = 5 /* static: x: int dynamic: x = 5 "At this point, there's an x of type int and the value of x is 5" */ let y = 10 /* static: x: int, y:int dynamic: x = 5, y = 10 "We now have both x and y with their corresponding types and values in the environments */ let addX = (num) => num + x /* static: x: int, y: int, addX: int -> int dynamic: x = 5, y = 10, addX = fun "Introducing functions as value as any other that gets put inside the dynamic environment with all other values makes it clear we can pass functions around as arguments. What also happened is that the function addX "takes" this current environment with it whenever it will be used. Example of this will come later */ let x = "whatever" /* static: x: string, y: int, addX: int -> int dynamic: x = "whatever", y = 10, addX = fun Now it's clear what "shadowing" actually means. We're creating new static and dynamic environments after every binding, so the type and value of x at inside the environments at this point of the program is different */ let z = addX(50) /* static: x: string, y: int, z: int, addX: int -> int dynamic: x = "whatever", y = 10, z = 55, addX = fun This is the important part. Because in the environment in which addX was defined x = 5, this function still adds 5 to its argument */ let list = List.map(~f:addX [1,2,3,4,5]) /* static: ...everything else, list: list(int) dynamic: ...everything else, list = [6,7,8,9,10] I think this now makes complete sense. We could pass the function addX to List.map because it's a value and it added 5 to every element of the list, because in the environment in which it was defined x = 5 */
Now that I think of any ML program as this series of static and dynamic environments, immutability also makes perfect sense. Of course you can’t change a value of a variable, you’re creating new environments after every binding.
The problem is immutability is rarely explained any further than “you can’t change the values of variables”. But if I saw this example from Exploring ReasonML just a couple months ago, that would be utterly confusing to me. What ARE we doing then?
# let x = 1; let x: int = 1; # x; - : int = 1 # let x = 2; let x: int = 2; # x; - : int = 2
The official documentation isn’t much better
let message = "hello"; print_endline(message); /* Prints "hello" */ let message = "bye"; print_endline(message); /* Prints "bye" */
Didn’t we mutate the value? Technically we didn’t, but it certainly looks like we did. The problem is it’s never actually explained why this isn’t mutation.
I wasn’t sure where I was going with this actually…
I guess I want ReasonML to succeed as much as all of you probably do, but I believe the approach to teaching it needs to be fundamentally different. I don’t think the official documentation can start with lambda calculus, so I want to make something of my own.
I’ve already had some ideas of how to explain what I just did semi-interactively rather than writing or making videos about them. The problem is that I will need a lot of help with things. I’ll try to make the semi-interactive version of what I explained above. If there’s someone who understands ReasonML deeply and would be willing to help me, then please let me know.
Mostly I just want to start a bit of a discussion around this. What do you think?