Skip to content
This repository has been archived by the owner on Dec 20, 2018. It is now read-only.

两种函数语法(fn)和(箭头=>)同时存在。有不同吗?你选哪一个? #13

Open
boringame opened this issue Jun 6, 2018 · 8 comments

Comments

@boringame
Copy link

No description provided.

@haifenghuang
Copy link
Owner

两种函数语法应该说没有太大区别。在parser.go文件中,parseFunctionLiteral返回的是一个 FunctionLiteral。同样parseFatArrowFunction函数返回的也是一个FunctionLiteral

使用=>语法在一定程度会更加方便。例如,下面的两段代码运行结果相同:

let x = (a, b) => a + b
printf("x(1,2)=%d\n", x(1,2)) //Output: x(1,2)=3
let x = fn(a, b) { a + b }
printf("x(1,2)=%d\n", x(1,2)) //Output: x(1,2)=3

但是使用=>有时候也会使代码更晦涩难懂一些。例如下面的两段代码运行结果也是一样:

let complex = {
   "add" :(x, y) => (z) => x + y + z, // function returns a closure
   "sub" : (x, y) => x - y ,
}
println(complex["add"](1,2)(3))  // Output: 6
println(complex["sub"](10, 2))   // Output: 8
let complex = {
   "add" : fn(x, y) { return fn(z) { x + y + z } }, // function returns a closure
   "sub" : fn(x, y) { x - y },
}
println(complex["add"](1,2)(3)) // Output: 6
println(complex["sub"](10, 2))  // Output: 8

=>这样的语法在函数式编程语言中比较常见。现在许多的语言也支持这种语法:

  • javascript
  • C# 从3.0版开始支持lambda表达式(=>)
  • java 从8.0版开始支持lambda表达式(->)

如果一定要说用哪一个好的话,我想每个人都有自己的想法。但是总体的原则大致如下:

  • 一个函数只在某个地方使用,即不存在复用的情况, 一般使用=>语法,这样代码更加紧凑。好处是你不用到处去找这个函数的实现。
  • 一个函数比较简单,一般使用=>语法能够使代码更简洁优雅。
  • 函数相对复杂,一般使用fn的形式
  • 如果使用=>的代码比较晦涩难懂,那么为了更易于理解,使用fn的形式较好
  • 一个函数是一个共通函数,一般使用fn的形式

=>这种语法主要意图是定义轻量级的内联函数。

@boringame
Copy link
Author

boringame commented Jun 7, 2018

感谢分享。

我认为你的原则很有参考价值,但是我认为例子中影响可读性的是运算优先级,如果加上花括号看起来也会很好。

let complex = {
   "add" :(x, y) => { (z) => { x + y + z } }, // function returns a closure
   "sub" : (x, y) => x - y ,
}
println(complex["add"](1,2)(3))  // Output: 6
println(complex["sub"](10, 2))   // Output: 8

我认为这个例子中内在的问题是:自由度大就会有人写出更晦涩的代码。

其实原题想问的是,有没有类似javascript上的两种函数的问题。
后来我做了些实验,发现箭头函数作为类的属性时表现很奇怪。

class Calc {
  let add = (x,y) => x + y ;
}
let c = new Calc();
c.add(1,2); //这里报错:未定义方法
c.add //这里报错:未定义参数

@boringame boringame reopened this Jun 7, 2018
@haifenghuang
Copy link
Owner

这里出错的原因是,我将所有let声明的变量作为一个类的Member变量,而非Method了。更改的话,只需要将parser.go文件的parseClassLiteral函数中处理LetStatement的地方更改一下即可,eval.go不需要变更。

具体更改如下:

// parser.go的parseClassLiteral函数的部分实现:
cls.Block = p.parseClassBody(false)
for _, statement := range cls.Block.Statements {
	switch s := statement.(type) {
	case *ast.LetStatement:  //class fields
		// For simplicity, we need 'let' statement's Names and Values are the same length.
		if len(s.Names) != len(s.Values) {
			msg := fmt.Sprintf("Syntax Error:%v- In class, Let-Statement's Names and Values must have the same length.", s.Pos())
			p.errors = append(p.errors, msg)
			return nil
		}

		//The 'let' statment might define a FunctionLiteral, like below:
		//    let add = (x, y) => x + y
		//so we need to treat it as a method
		for idx, name := range s.Names {
			value := s.Values[idx]
			switch v := value.(type) {
			case *ast.FunctionLiteral:
				//If it's a FunctionLiteral, we need to treat it as a method.
				FnStmt := &ast.FunctionStatement{Name: name, FunctionLiteral: v}
				cls.Methods[name.Value] = FnStmt
			default:
				cls.Members = append(cls.Members, s)
			}
		} //end for

	case *ast.FunctionStatement: //class methods
		cls.Methods[s.Name.Value] = s
	case *ast.PropertyDeclStmt: //properties
		cls.Properties[s.Name.Value] = s
	default:
		msg := fmt.Sprintf("Syntax Error:%v- Only 'let' statement, 'function' statement and 'property' statement is allow in class definition.", s.Pos())
		p.errors = append(p.errors, msg)
		return nil
	}
}

修改已经反映在最新上传的代码中。你可以下载最新的代码确认一下。

@boringame
Copy link
Author

从新获取了一下,可以正常运行了。

class Calc {
  let add = (x,y) => x + y ;
}
let c = new Calc();
c.add(1,2); //可以正确执行了

还想讨论一下不带参数的成员方法调用问题。

class Calc {
   let add = (x,y) => x + y ;
}
let c = new Calc();
let exec = (op,x,y) => op(x,y);
exec(c.add,x,y); //错误了

这里想传递add函数本身作参数,代码是上符合直觉的。
但是monkey的方法调用可以省略括号这个机制,被成员字段在存储函数的时候特殊处理了。

我个人建议定义方法需要更特殊的语法,提示定义的是方法而不是普通字段,类似这样:

class Calc {
  method add(x,y) { x + y }
}

普通字段不要特殊处理更符合直觉。

这只是我的个人意见,你考虑一下。

@haifenghuang
Copy link
Owner

如果我的理解没有错误的话,对于普通字段:

class Calc {
   let add = (x,y) => x + y ;
}

直接解析报告类似如下的错误:

Syntax Error: Could not define function-literal in class's let-statement

关于定义方法需要特殊的语法,我认为这个建议也可以接受。不过这样的话,需要加入不少的代码。这个我会考虑。如果时间允许的话,考虑会加入此功能。

不过如果你能够贡献代码的话,非常欢迎您的pull requests

@haifenghuang
Copy link
Owner

haifenghuang commented Jun 7, 2018

关于你说的“定义方法需要更特殊的语法”这一点,我有一个自认为更好的方法,就是在方法后面加'&'。像下面这样:

class Calc {
   let add = (x,y) => x + y ;
}
let c = new Calc();
let exec = (op,x,y) => op(x,y);
w = exec(c.add&,1,2); //注意add后面的'&'符号
printf("w=%d\n", w)

这样可能更易于理解(自我认为),而且代码改动量也比较小。代码我已经实现了,达到了预期的效果。

不过我需要再仔细考虑考虑是否有更好的解决办法。

但是代码暂时还没有上传,希望听听你的意见!

@boringame
Copy link
Author

对于你提到的在使用字段的时候用特殊语法,我的观点是:
成员字段应该和普通变量表现一致,存了什么东西,取出来还是什么东西。
如果存放的是函数,要取出来的时候,也应该能得到函数本身,而不是立即执行函数,也不希望用特殊语法。
假设有人设计了这么一个简单实现的懒加载器:

class Lazy {
  let factory;
  let created = false;
  let value;
  fn init(factory) {
    this.factory = factory;
  }
  fn getValue() {
    if (!this.created) {
	  this.value = this.factory();
	  this.created = true;
	}
	return this.value;
  }
}

看起来这个懒加载器是通用的,但是遇上函数就出问题了。
比如这个例子,懒加载一个算法,需要从数据库里读取一个因子:

let lazyFunc = new Lazy(()=> {
  let salt = readFromDb();
  return (x) => x + salt ;
});
let func = lazyFunc.getValue();
fun(1);

设计者不容易想到要写特殊语法 return this.value&,字段语法在使用时需要考虑是否可能存放函数。

我又回忆了其他语言在这方面,有参考的价值的的处理方式。

ruby中,访问对象成员只有一种形式,就是方法调用,
字段是私有的只能在内部分访问,属性是方法调用的语法糖。

class Box
  def value
    @field #字段只能在内部访问
  end
  def value=(v)
    @field = v
  end
end
box = Box.new
box.value = 1 #等价与javascript中 c["value="](1)
box.value #等价于javascript中 c["value"]()

这种方式对于monkey已有的语法不兼容,monkey字段语法在外部是可以访问的。

javascript中,方法调用强制要加括号。

scala中,区分了方法和成员变量的定义语法。

希望你能选择一种合理的设计,
为此花费的改动代价是值得的。

我乐意贡献代码,我也能借此学习你的经验。
但这不会很快实现,我还需要花时间研究已有的代码。

@haifenghuang
Copy link
Owner

haifenghuang commented Jun 8, 2018

谢谢你的思路,你说的很有道理。至于代码,你有任何不懂的地方,请随时联系我, 我也很乐意提供帮助。下面是我的邮箱地址:

工作邮箱 163邮箱
haifeng.huang@hpe.com fenghai_hhf@163.com

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants