プログラミング言語の構文の複雑さについて考える 〜Java vs. Scala〜

  • 23
    Like
  • 0
    Comment

プログラミング言語同士の比較をするとき、ある言語が別の言語より複雑であるということがよく批判の対象になります。たとえば、C++は複雑過ぎる、Scalaは複雑過ぎる(Javaと比較して)、といった意見をよく耳にします。

しかし、実際に客観的な指標に基いて比較してみたことがある人はあまり居ないのではないでしょうか。自分がみる限り、その人がこれまで学んできた言語と比較して、「難しく感じた」ことをもって「複雑だ」という主張をしていることが多いように思います。

しかし、複雑である <=> 難しい(と感じる)も必ずしも正しくありませんし、単純である <=> 簡単であるとも限りません。

このエントリでは、JavaとScalaの構文を比較して、よく言われる「Scalaは複雑である」という主張に反して、「Scalaの方が単純である」ということが言える(ただし、Scalaの方が簡単であることは意味しない)ということを說明してみたいと思います。

複雑さと単純さについて明快な定義を与えるのは簡単ではありませんが、ひとまず、「より多くの(種類の)要素から構成されているものはより複雑である」ということにして話を進めます。

さて、このような考え方において、構文の複雑さの指標として何をもってくるべきでしょうか。一般的なプログラミング言語の文法はおおむね(E)BNFに基いて定義されるので、BNFの規則数を構文的複雑さの近似として用いることができそうです。そこで、今回は、JavaとScalaのBNFによる表現の規則数を比較してみることにします。

幸い、JavaもScalaも言語仕様書に、(E)BNFによる構文の表現があるため、ここから構文規則数をカウントすることができます。それぞれの言語の構文規則は、以下の箇所から取得しました。

結果は

  • Scala: 105
  • Java: 209

となりました。目視で確認したので、若干の誤差があるにしても、予想以上に圧倒的にJavaの構文規則の数が多いという結果になりました。

Scalaの場合、中置演算子の優先順位が構文規則に含まれていないことや、Javaの構文規則の方が、より多くの規則にくくりだされている、といった事もありますし、完全に公平な比較と言えない点に注意する必要がありますが、それにしても、かなり大きい差です。

これは、巷の、Scalaの構文が難しい、という評価と反する結果にように思えます。何故そうなったのでしょうか?両者の構文規則を観察したところ、Scalaの構文規則は、再利用性が高く、Javaの場合は再利用性が低い、ということが大きな要因のようです。

たとえば、(try文 | try式)の構文規則を比較してみます。

Scalaの場合、try式は次のように定義されています。

‘try’ (‘{’ Block ‘}’ | Expr) [‘catch’ ‘{’ CaseClauses ‘}’] [‘finally’ Expr]

一方、Javaの場合、try文は次のように定義されています。

TryStatement:
    try Block Catches 
  | try Block [Catches] Finally 
  | TryWithResourcesStatement;
Catches:
  CatchClause {CatchClause};
CatchClause:
  catch ( CatchFormalParameter ) Block;
CatchFormalParameter:
  {VariableModifier} CatchType VariableDeclaratorId;
CatchType:
  UnannClassType {| ClassType};
Finally:
  finally Block;

Javaの方が、構文規則を細かくくくりだしているという点で公平性の観点から注意しなければいけませんが、Scalaの場合は、catchの中身をCaseClauses にまとめていて、これは、パターンマッチやパターンマッチ無名関数の規則からも再利用される、つまり、文法的には同じことが書けるのに対して、Javaの場合、catchの文法は他と独立して定義されており、再利用できない事によって全体の規則数が増える原因になっています。

別の例として、型の定義に関する構文を比較してみます。以下はScalaの例です。

  ClassDef          ::=  id [TypeParamClause] {ConstrAnnotation} 
                         [AccessModifier]
                         ClassParamClauses ClassTemplateOpt
  TraitDef          ::=  id [TypeParamClause] TraitTemplateOpt
  ObjectDef         ::=  id ClassTemplateOpt
  ClassTemplateOpt  ::=  ‘extends’ ClassTemplate | [[‘extends’] TemplateBody]
  TraitTemplateOpt  ::=  ‘extends’ TraitTemplate | [[‘extends’] TemplateBody]
  ClassTemplate     ::=  [EarlyDefs] ClassParents [TemplateBody]
  TraitTemplate     ::=  [EarlyDefs] TraitParents [TemplateBody]

これをみると、

  • クラス定義とオブジェクト定義は、 extends 以降は構文的に同じ
    • ClassTemplateOpt
  • クラス定義とオブジェクト定義とトレイト定義の本体は、構文的に同じ
    • TemplateBody

となるように定義されていることがわかります。

一方、Javaの場合を見てみます。

TypeDeclaration:
    ClassDeclaration 
  | InterfaceDeclaration;

ClassDeclaration:
    NormalClassDeclaration 
  | EnumDeclaration;

NormalClassDeclaration:
  {ClassModifier} class Identifier [TypeParameters] [Superclass] 
    [Superinterfaces] ClassBody;

EnumDeclaration:
  {ClassModifier} enum Identifier [Superinterfaces] EnumBody;

InterfaceDeclaration:
    AnnotationTypeDeclaration
  | NormalInterfaceDeclaration;

NormalInterfaceDeclaration:
  {InterfaceModifier} interface Identifier [TypeParameters] 
    [ExtendsInterfaces] InterfaceBody;

InterfaceBody: { {InterfaceMemberDeclaration} };
InterfaceMemberDeclaration:
    ConstantDeclaration 
  | InterfaceMethodDeclaration 
  | ClassDeclaration 
  | InterfaceDeclaration;

ClassBody: { {ClassBodyDeclaration} };
ClassBodyDeclaration:
    ClassMemberDeclaration 
  | InstanceInitializer 
  | StaticInitializer 
  | ConstructorDeclaration;

列挙型はScalaには言語要素として存在しないので、その分の構文規則が追加で必要になるのは仕方がないですが、他の部分をみても、たとえば、 ClassBodyInterfaceBody の定義は全く異なっていることがわかります。

さて、世間的な評価(?)に反して、Scalaの構文の方がより単純だと言える、ということを說明しましたが、だからといってScalaの構文の方がより簡単に学べる、かどうかについては別です。これは、学習者がどのようにして言語を学ぶかに依存する問題です。

私の場合、まず、ある箇所に対して構文的に許されるコード→型的に許されるコード→意味的に許されるコードという風に考えるので、Scalaの構文の方がJavaより簡単であると感じる場面が多いです。

一方で、Scalaの(構文が)難しい、という声を聞く時、構文規則の再利用性の高さが障害となっていることも多いように見受けられます。たとえば、 { case ... }try 式であらわれるときと match 式であらわれるときに、書けることの違いに関する質問を何度か聞いたことがあります。構文をトップダウンに理解するアプローチではこのような疑問はあまり抱くことがないように思いますが(構文的には同じことが見ればわかるので)、そうでない人が多いのだ、ということだと思います。

これは、単純に言語を学ぶ際のアプローチの違いによって生じる差なのであって、客観的な指標としてはあまり適切ではないでしょう。簡単である、あるいは難しい、という感想を即座に、単純である、あるいは複雑である、と言い換えることには問題があるのです。

今回は構文についてのみ考えましたが、型システムや意味論について複雑さを比較してみるとまた違う結果が得られることもあります。この記事をきっかけとして、単純さ、複雑さ、についてより突き詰めて考える人が増えると嬉しいです。