mattn/anko

Scoping question

floren opened this issue · 13 comments

I'm not particularly good with dynamic scoping, I've pretty much never used it, so I've got some questions about how I should be writing my code. Basically, any variables I use in functions are now "off-limits" from using elsewhere... here's a simplified example:

var strconv = import("strconv")
func parseBase10(s) {
  r, _ = strconv.ParseInt(s, 10, 64)
  return r
}
r = ["5", "4", "3"]
for i = 0; i < len(r); i++ {
  println(parseBase10(r[i])
}

The call to parseBase10 changes r from a slice of strings to an int64. Whoops! I thought I might do this:

 func parseBase10(s) {
  var r, err = strconv.ParseInt(s, 10, 64)
  return r
}

But that doesn't work quite right:

> parseBase10("5")
[]interface {}{5, interface {}(nil)}

What do I do in this situation? Start using more unique variable names? Or is there a correct way to use 'var' in that sort of multiple assignment situation?

In Javascript, can see the scope/output is different

https://jsfiddle.net/kg35efrk/

var divData = document.getElementById('divData');
divData.innerHTML = "";

function test(s) {
	 var r = s + "z"
   return r
}

var r = "a"
divData.innerHTML += r + "<br />";
divData.innerHTML += test("b") + "<br />";
divData.innerHTML += r + "<br />";

Output:

a
bz
a

In Anko:

package main

import (
	"fmt"
	"log"

	"github.com/mattn/anko/vm"
)

func main() {
	env := vm.NewEnv()

	err := env.Define("println", fmt.Println)
	if err != nil {
		log.Fatalf("Define error: %v\n", err)
	}

	script := `
func test(s) {
	r = s + "z"
	return r
}

r = "a"
println(r)
println(test(r))
println(r)
`

	_, err = env.Execute(script)
	if err != nil {
		log.Fatalf("execute error: %v\n", err)
	}

}

Output:

a
az
az

So can see the scope is not acting the same in Anko as in JavaScript.

You can replicate the Javascript behavior by saying "var r" in the function (http://play-anko.appspot.com/p/f5c402dab08863dd97bf57d0409063085ece26b1)

func test(s) {
	var r = s + "z"
	return r
}

r = "a"
println(r)
println(test(r))
println(r)

But using var in that manner doesn't work if you are trying to set two variables at once:

var r, err = strconv.ParseInt("5", 10, 64)

That simply creates r as a []interface containing 5 and nil (the result and the error).

Can get the same output either way by the following:

Case 1:

JavaScript:

var divData = document.getElementById('divData');
divData.innerHTML = "";

function test(s) {
   r = s + "z"
   return r
}

var r = "a"
divData.innerHTML += r + "<br />";
divData.innerHTML += test("b") + "<br />";
divData.innerHTML += r + "<br />";

Anko

func test(s) {
   r = s + "z"
   return r
}

r = "a"
println(r)
println(test("b"))
println(r)

Output:

a
bz
bz

Case 2:

JavaScript:

var divData = document.getElementById('divData');
divData.innerHTML = "";

function test(s) {
   var r = s + "z"
   return r
}

var r = "a"
divData.innerHTML += r + "<br />";
divData.innerHTML += test("b") + "<br />";
divData.innerHTML += r + "<br />";

Anko:

func test(s) {
   var r = s + "z"
   return r
}

var r = "a"
println(r)
println(test("b"))
println(r)

Output:

a
bz
a

Yes, I think we're talking past each other here. I want to use 'var' to isolate the values returned from a multiple return function such as strconv.ParseInt (simply a convenient example). However it doesn't work to say:

var r, err
r, err = strconv.ParseInt(s, 10, 64)

nor does it work to say

var r, err = strconv.ParseInt(s, 10, 64)

I can see no way to reproduce in the multiple return case what your examples demonstrate for the single case.

I think this works and is what you want.

strconv = import("strconv")

func test(s) {
	var r = 0
	r, _ = strconv.ParseInt(s, 10, 64)
	return r
}

var r = "1"
println(r)
println(test("2"))
println(r)

Output:

1
2
1

That being said, I think there is a bug with var multiple case.

strconv = import("strconv")

func test(s) {
	var r, err = strconv.ParseInt(s, 10, 64)
	return r
}

var r = "1"
println(r)
println(test("2"))
println(r)

Output:

1
[2 <nil>]
1

But I would expect the output to be:

1
2
1

Yep, you've hit the nail on the head! I was concerned I might just be doing it wrong, but I think there is indeed a bug in the behavior of var.

mattn commented

No.

var r, err = strconv.ParseInt(s, 10, 64)

This must be

var r = 0
var err = nil
r, err = strconv.ParseInt(s, 10, 64)

Doing it that way requires knowledge of the types returned by ParseInt, in a dynamically-typed system that tries to avoid exposing types.

@mattn

Can't we update ast.VarStmt to be similar to ast.LetsStmt so that var r, err = strconv.ParseInt(s, 10, 64) will work?

mattn commented

Yes, but we must consider it to be separated to

var a, b

define a and b

var a, b = something()

define a and b, and assign both from something()

Does PR #275 fix this issue?

Looks like it should, I'll have to test when I get a moment but the tests & code look right

@floren

Close this?