Looking into the Haskell/JS hybrid Roy, and one of the proposed features are lenses. They are basically sugar over attribute getters and setters, and look like this:
set .x 3 {x: 1, y: 2} == {x: 3, y: 2}
get .name {title: "Mr.", name: "Bob"} == "Bob"
This is much like Python's getattr and setattr, except that these are static (no dynamic property names, required for typechecking) and composable:
let person = {
name: "Bob",
"address": {
"number": 123,
"street": "Candyland Rd.",
},
}
let streetLens = (compose .address .street)
get streetLens person == "Candyland Rd."
# Shouldn't compile
# let badLens = (compose .address .doesntexist)
# get badLens person
The following definitions should be placed in the Prelude, with the Lens
type
constructor hidden to prevent dynamically created lenses.
type Lens = Lens name :: String
composeLens :: Lens -> Lens -> Lens
let composeLens (Lens a) (Lens b) =
Lens (a + "." + b)
getLens :: Lens -> a -> b
macro getLens attr obj =
[| obj&(attr) |]
setLens :: Lens -> b -> a -> b
macro setLens attr value obj =
[| obj&(attr) = value |]
The following rewrites must be done by the compiler:
.attr
--> Lens "attr"
get .attr obj
--> get (Lens "attr") obj
--> getLens (Lens "attr") obj
--> obj.attr
set .attr value obj
--> set (Lens "attr") value obj
--> setLens (Lens "attr") value obj
--> obj.attr = value
I'm not 100% sure about this method, because having an actual Lens type could lead to run-time typing, which is exactly what Roy is trying to avoid. This is potentially alleviated by not exporting the Lens constructor in the Prelude and leaving it just to the sugar. At that point, however, should we really have a system like this underneath?
What's important here though is that something like get .attr
should carry
type information that requires the object it's passed have that attribute. I'm
not sure how to represent that in Haskell type notation, and probably isn't
carried in the system I'm proposing.