stedolan / ppx_stage

Staged metaprogramming in stock OCaml
MIT License
150 stars 7 forks source link

scope of local variables: when does it extend into code blocks? #9

Closed nilsbecker closed 6 years ago

nilsbecker commented 6 years ago

hi, sorry likewise for using the bug format. i don't get something. i tried this:

let r = ref 4;;
let greeting =
    let s = Printf.sprintf "something computed: %d" (3 + !r) in
    [%code print_string s];;

this fails with unbound value s. i understand that i can fix this by

let greeting =
    let s = [%code Printf.sprintf "something computed: %d" (3 + !r)] in
    [%code print_string [%e s]];;

this one does not evaluate the !; later reassignment to r will change the result.

alternatively, i can use Lift.string on the first s definition, which will evaluate !.

so basically my questions are:

  1. why does the first version not work? is this an implementation problem or would it not be sound?

  2. why is this different from the map example in the README, where x does extend into the %code scope?

stedolan commented 6 years ago
  1. In ppx_stage, values defined outside of a [%code ...] block are not automatically available inside a [%code ...] block. To use something defined outside of a [%code ...] block inside one, you need to first turn it into a value of type 'a code (which as you've spotted can be done either with Lift.foo or with another [%code ...] block, which evaluate their contents at different times), and then splice that 'a code value into the block using [%e].
    It's a deliberate decision not to automatically let all values enter [%code ...] blocks, because I want every value of type 'a code to be printable as source without referring to unprintable values (MetaOCaml takes a different approach).

  2. In the map example, the variable x is actually defined inside a [%code ...] block and is being used inside another [%code ...] block.

I'm closing this because I don't think there's actually a bug here, but feel free to keep commenting and/or reopen if I haven't answered the questions.

nilsbecker commented 6 years ago

yes, closing is fine, thanks for the explanation. it was helpful to see the reasoning behind the scoping rules.

nilsbecker commented 6 years ago

about the map example: it's true that x is defined inside a %code block but then it is used in a further, nested %code block. this means the semantics are not the same within some nesting level of %code block >0, and outside of the outermost code block. this is what threw me off (and i find it not maximally elegant). as you explained there is a reason for it though.

stedolan commented 6 years ago

To understand the scoping rules for staged programs, it's helpful to assign "levels" to every expression. Level 0 is the program being run and level 1 is the program being generated. [%code exp] interprets exp at a level one higher than the surrounding code, and [%e exp] interprets exp at a level one lower. The scoping rule is that variables must be used at the same level at which they are declared. In the map example, x is declared and used at level 1 (once inside [%code ...], and once inside [%code [%e [%code ...]]]).

nilsbecker commented 6 years ago

ah, that's helpful thanks. now i'm getting somewhere.