aiscript
aiscript copied to clipboard
空ブロックと空オブジェクト式の干渉
#179 の無名関数() => {}を実現する上での問題点についての検討。
空ブロックと空オブジェクト式が干渉してしまう。
関係する仮仕様
- 代入式でブロックが使える
- 代入式でオブジェクト式が使える
- 代入式で
{}を渡した場合はオブジェクト式と解釈する - 無名関数でブロックが使える
- 無名関数でオブジェクト式が使える。 その場合は
({})と指定する。 - 無名関数で
{}を返した場合はブロックと解釈する
懸念
- 対象によって式の優先度が変わるのはユーザーにとっても優しくなさそう。
解決策1
ブロックをブロック文とブロック式の2つに分ける:
- ブロック文:
{} - ブロック式: ? (別で考える)
※ブロック式は仮称。全然別の名前をつけても良さそう。
{}はブロックの文として扱って、ブロックの式としては解釈しないようにする:
- 式
{}は常にオブジェクト。 - 文
{}は常にブロック。 - よって、文
({})はオブジェクト。 - 式としてブロックを使いたい場合はブロック式を使う。
(2022/08/08更新)
名前を決定。
ブロック式→eval式eval {}
ブロック式
let result = do {
1 + 2
}
みたいなのどうじゃろ
doも良さそう。 ただ、doはdo-whileから繰り返しのイメージがあるから混乱させる恐れがある。
scope とかはどう?
let result = scope {
let x = calc(2)
let a = calc(1)
2 * x + a
}
名詞じゃないほうが良いのかな... :thinking:
evalとかexecとかでも良さそう
let result = ${
let x = calc(2)
let a = calc(1)
2 * x + a
}
みたいに記号使うのも良さそう
C#のLINQみたいにfrom とかどうだろう
let result = from {
let x = calc(2)
let a = calc(1)
2 * x + a
}
ほむん from はあまり馴染みなかった
記号は何の記号だったか忘れるかも 初見だと意味を把握しづらいかも (直感的なものがあればあり)
${}はテンプレート文字列でも使われる構文だからその点は分かりやすいと思った
evalとかexecとかでも良さそう
この2つは普通にアリ
fromは、続くブロック"から"その式の値を評価するって意味で良いかなと思った
個人的には 1位 from 2位 eval って感じかも
あとは他の人の意見も参考にしたい感じ(アンケートなど)
遅延評価とかも考えると、evalの方が良いのかな
仮に遅延評価を、
lazy { }
とかにする場合に、即時評価を
eval { }
とするのは自然な気がする
evalにするか
eval式追加してみるか
書きながら思ったけど、eval式を追加するとブロック文(文{})があまり要らなくなるかも。
ブロック文は値を返せないため、スコープが生成されるだけの構文になる。
ブロックを触ってmatch式にも影響が出てるな
解決策1ダメかも これまでのブロックはmatchなどの構文の一部としてごく一般的に使用されてる。 これを変えると、ちょっと整合しなくなるな...
matchでstatementを取るようにすれば良いかも?
- ブロックが式である限りはオブジェクト式と干渉する
- ブロックが式でなければifやmatchでブロックを用いて値を返すことができない
詰んできた
ifやmatchの構文を変更してブロックを使わないようにすれば何とかなるかもしれないけど、あまり美しくはないな
ブロックが式でなければifやmatchでブロックを用いて値を返すことができない
これどういうこと?
例えば
<: match 2 {
1 => "a"
2 => "b"
3 => "c"
}
のような一般的なコードは今回の変更に影響されない気がした
ブロックを文に変更するとして、
=>の右側は式を受け取ってるけど、ここで文を受け取るようにする必要があると思う。
ただ、文に値を返す機能はない(と現状ぼくは思っている)ためダメだと思った。
=>の右側は式を受け取ってるけど、ここで文を受け取るようにする必要があると思う。
これはなぜ?
ブロックが文になるから
mtach構文にブロック関係なくない?
https://github.com/syuilo/aiscript/blob/master/src/parser/parser.peggy#L382 ここで式を受け取ってて、
<: match 2 {
1 => {
"a"
}
2 => {
"b"
}
3 => {
"c"
}
}
をしたい場合にブロックの式が使われると思うけど...
https://github.com/syuilo/aiscript/blob/master/src/parser/parser.peggy#L382 ここで式を受け取ってて、
<: match 2 { 1 => { "a" } 2 => { "b" } 3 => { "c" } }をしたい場合にブロックの式が使われると思うけど...
この場合は
<: match 2 {
1 => eval {
"a"
}
2 => eval {
"b"
}
3 => eval {
"c"
}
}
と書くようになると思ってた
それなら可能だけど、見た目が微妙じゃない?
let a = if true eval {
1
} else eval {
2
}
とかになる
あー matchに関して言えば、ブロック式が使われることはあまりないと思うから
<: match 2 {
1 => eval {
"a"
}
2 => eval {
"b"
}
3 => eval {
"c"
}
}
で良いと思ってたけど、if はなあ
「ifは式である」というのを一旦やめる(=ブロック必須にする)とか やめた後でやっぱり欲しくなったらif文とは別に三項演算子的な構文を設けるとか
ifやmatchの構文を変更してブロックを使わないようにすれば何とかなるかもしれないけど、あまり美しくはないな
これをするとか そもそも構文として、その部分が「ブロック式」とは別物の構文であることにすれば解決するかも
こんな感じで
ifContent = "{" Statements "}" / Statement
オブジェクト式の構文を変更したら解決する感じがしてきた
それできたら一番楽なんだけど、JSONをそのまま解釈したいという思いがある
ブロックを文に変更して、 ブロック文は値を返せることにするとか
良いかも
文が値を返すのを良しとするかって話にはなるけど
そもそも構文として、その部分が「ブロック式」とは別物の構文であることにすれば解決するかも
これで良さそう
解決策2
- オブジェクト式は現状維持
- ブロック式を廃止し代わりにeval式を追加
- 構文規則にブロック
{}を直接追加 (ifやmatch、無名関数、他...) - 代入ではブロックではなくeval式を使用する
解決策2を採用した場合の要件カバー状況
- [x] 代入で~~ブロックが使える~~ eval式が使える
- [x] 代入でオブジェクト式が使える
- [x] 代入で
{}を渡した場合はオブジェクト式と解釈する - [x] 無名関数でブロック
{}が使える - [x] 無名関数でオブジェクト式が使える。 その場合は
({})と指定する。 - [x] (無名関数でeval式を返すことも可。あんまり意味はないけど)
まとめた こんな感じかな
👍🏻👍🏻
実際の作業についてのissueを立てたのでcloseします