About documentation, immutability, first-class functions and dynamic and static environments


#1

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.

There’s many more places in the documentation and other explanation of ReasonML (and other Functional Programming concept in general) with which I have issues. I’ve mentioned Destructuring on Discord already, which is something that’s made up for the JavaScript developers, but in reality isn’t actually a thing. It’s pattern-matching. Technically, even a let binding is pattern-matching. And as I also said on Discord, I totally understand why it’s explained that way.

I understand it’s trying to simplify things for JavaScript developers. The truth is I’m a beginner JavaScript developer and I don’t understand 95% of ReasonML. I believe the problem is with how it’s explained and the documentation structured rather than it being difficult. What made me understand things are these very fundamental concepts that I learned about from somewhere else. That even includes the basics of lambda calculus.

My point

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?


#2

Thank you for not wanting to start with lambda calculus :slight_smile: Your point about the “seemingly mutable” variables is valid.

Miscellaneous thoughts:

  1. The documentation is distributed in many places (see Thoughts on unifying ReasonML Documentation)
  2. There are separate audiences for documentation: people who have never programmed before, those who have JS knowledge (but possibly not functional programming), and those who have FP knowledge (but possibly not JS).
  3. https://github.com/jaredly/docre/ will help a lot with the interactivity; I used it for this: http://langintro.com/reasonml/using_belt_set/

I will be happy to assist with any of this as soon as someone can point me in the proper direction or assign a task. I also enjoy proofreading and doing tech review.


#3

I think you need to read about scoping rules for any language you comfortable with.
Redeclaring variable again with the same name called shadowing. Most languaged do not allow shadowing in the same scope (as in examples you showed from docs), but allow shadowing in deeper scopes like:

let x = 1;
{
  let x = 2; /* locally shadowed */
};
/* x is 1 again here */

The reason why shadowing is allowed in more cases in Reason/OCaml is because it’s safe, when you have explicit syntax for recursion and good compiler warnings about unused variables.

The difference between shadowing and mutabillity is that code, that uses first value still uses it and doesn’t affected by variable being shadowed, just like you would declare variable with not the same name, but something different, like:

let message1 = "hello";
print_endline(message1); /* Prints "hello" */
let message2 = "bye";
print_endline(message2); /* Prints "bye" */```