Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

相談:returnを含むifやevalの扱いについて #276

Open
FineArchs opened this issue May 21, 2023 · 12 comments · May be fixed by #859
Open

相談:returnを含むifやevalの扱いについて #276

FineArchs opened this issue May 21, 2023 · 12 comments · May be fixed by #859
Assignees
Labels
bug Something isn't working

Comments

@FineArchs
Copy link
Member

#237 (comment) で言われているとおり、現状だと

@fff() {
  let a = eval {
    return 1
  }
  <: a // return<null>

  let b = if true {
    return 1
  }
  <: b // return<null>
}

のようなプログラムで見られるように、return(及びbreak・continue)を含むifやevalブロックが値を返す時の動作が未定義であり、意図しない挙動(returnのラッパーが取り出せる)が発生してしまいます。

考えられる新しい仕様として、

  • 値を返す・返さない関係なく、return系は現在のスコープを破棄し親スコープに処理を移動する
  • 値を返す・返さない関係なく、if/eval中で使われたreturnはif/evalの返り値になる(break・continueはエラー?)
  • if/evalが値を返す場合のみ、if/eval中で使われたreturnはif/evalの返り値になる(break・continueはエラー?)
  • if/evalが値を返す場合のみ、if/eval中でreturn(及びbreak・continue)が使用されたときエラーにする
    などが挙げられます。

私自身は最後の案で考えていますが、よければご意見をお願いします。

@syuilo
Copy link
Collaborator

syuilo commented May 21, 2023

個人的には(私の理解が正しければ)

値を返す・返さない関係なく、if/eval中で使われたreturnはif/evalの返り値になる(break・continueはエラー?)

が自然かと思いました

@salano-ym
Copy link
Member

if式に関しては値を取り出すかどうかで挙動が変わるのは一貫性が無いかも?と思ったけどevalも値を取り出さないと関数自体抜け出すのね

@FineArchs
Copy link
Member Author

@syuilo
確かにその方式は自然だと思いますが、returnがifに吸収されてしまうため

@someFunc(a) {
  if !(valid(a)) return null
  //...
}

のような冒頭での脱出が出来なくなるのが少し不便だと思います。
とはいえ、そのようなコードは大体

@someFunc(a) {
  let result=0
  if !(valid(a)) {
    result=null
  }
  else {
    //...
  }
  return result
}

のように書き換えられるので、致命的にはならないと思いますが…

@syuilo
Copy link
Collaborator

syuilo commented May 22, 2023

returnがifに吸収されてしまうため

あー

@syuilo
Copy link
Collaborator

syuilo commented May 22, 2023

とすると

値を返す・返さない関係なく、return系は現在のスコープを破棄し親スコープに処理を移動する

かな

@FineArchs
Copy link
Member Author

@saki-lere
はい、現状だとその通りで、if及びevalは値を取り出されない時のみ関数/ループを抜け出すようになっています。
加えて言うと、

@someFunc() {
  let ret= eval {
    return 1
  }
  ret
  return 0
}
// it returns 1

のように、取り出された値をスコープ直下で呼び出すとreturn文のように機能するようになっていたと思います。

@FineArchs
Copy link
Member Author

FineArchs commented May 22, 2023

@syuilo
一律関数抜け出しにしてしまうほうが一貫性があるのは確かですが、個人的には値が取り出されるif/eval内のreturn

// 例
@someFunc() {
  let ret= eval {
    return 1
  }
}

が関数抜け出しを意図していることはほぼなく、if/evalの値として返るものと期待(勘違い)されていると思われるので、意図しない挙動を防ぐためエラーを出すのが良いと考えています。

また、現状のreturnの実装は一律関数抜け出しとかなり相性が悪いように感じられます。

@yuriha-chan
Copy link

yuriha-chan commented May 26, 2023

if の返り値を使うような関数型言語のスタイルの書き方ではあまりreturnは使われないので、そのような場面で使われるreturnはプログラマーの誤解の可能性が高く構文エラーにするというのは一理はあるが、
(if (y == 0) {return null} else x/y)
は構文エラーにならないのに
[(if (y == 0) {return null} else x/y) x*y]
は構文エラーになるなど、直感に反する場面はいくつかあるかもしれないと思います。

なので、returnの実装は一律関数抜け出しとするほうがよいと私は考えます。

(蛇足ですが、関数型言語の立場からは、Common Lisp でいう return-from (どこの関数のスコープまで戻るか構文で指定する)構文まで用意すれば、map に渡した匿名関数などからも抜け出せるので一部の人は使うかも)

@yuriha-chan
Copy link

このインタプリタは、構文木のどこをいま評価しているのかの情報をJavaScriptのコールスタックの中にもつ実装なので、returnのような評価順序をいじる構文は本来実装しにくいのですが、例外機構を利用して、return文の評価時(case 'return')にthrow valして、関数呼び出しの評価(case 'call')でawait this.execFn(...).catch(x => x)と拾うようにすると一律関数抜け出しをわりとスマートに実装できるのではないかと思います。

@FineArchs
Copy link
Member Author

(if (y == 0) {return null} else x/y) は構文エラーにならないのに [(if (y == 0) {return null} else x/y) x*y] は構文エラーになるなど、直感に反する場面はいくつかあるかもしれないと思います。

@yuriha-chan さんの言うような使い方をする人がいるのであれば、やはり一律関数抜け出しの方が良さそうですね。そちらの方向で進めてみます。
例外キャッチを利用した実装は思いつきませんでした…試してみようと思います。
(それが完成してからこのissueを閉じます)

@marihachi
Copy link
Contributor

marihachi commented Sep 22, 2023

個人的にreturnは常に関数に対して適用するでいいと思いました
ブロックが返す値は常に最後の式にする感じで

@FineArchs
Copy link
Member Author

@marihachi 一度それで実装しようとしたのですが、いい実装が思いつかなく二の足を踏んでいます…

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants