#はじめに
僕はFlutterからプログラミングを始めたのですが、最近JavaScriptを触る機会が増えてきました。
これら二つはすごく似た言語である一方で、違いも目につくようになってきました。
そんな中で「そもそもDartってどんな言語なんだろう?」という疑問が沸々と湧いてきたので調べてみました。「オブジェクト指向言語」「コンパイラ言語」「スクリプト言語」などややこしい概念がたくさん出てきたので、それらを頭を捻りながら整理してみました。
✔︎ プログラミング初心者で概念的なことよく分かってない
✔︎ 「静的型付け」とか「オブジェクト指向言語」とかよく聞くけど、イマイチよく分かっていない
✔︎ とにかく言語が好き!
という人に読んでもらえたら嬉しいです。
私たちがプログラミング言語を記述してから実際にアプリが動くまでの流れを図式化すると、上の図のような感じになるかと思います。1
以下より、
- 言語的側面(そもそものプログラミング言語の話)
- プログラム的側面(実際にアプリが動くまでの話)
に分けて話していきたいと思います。
#言語的側面
「え、そんなところから?」というくらい、ものすごく前提になるところから話を始めます。プログラミング言語も普段僕たち人間が使っている言語(自然言語)と同じ言語の一種です。自然言語が人間同士のコミュニケーション手段なのに対して、プログラミング言語は人間とコンピュータの間のコミュニケーション手段と言えるかと思います。なので、自然言語同様に大きく二つの要素(構文論と意味論)に切り分けることが出来ます。
###構文論 (Syntax)
文を構成する語の並びなどのルール、いわゆる「文法」のことです。日本語には日本語のルールが、英語には英語のルールがありますよね。自然言語の場合は、ルールが複雑で様々な考え方があったりします。例えば英文解釈にも学校で習った五文型(SVOとかに分類する考え方です)以外にも様々なものがあったりします。一方で、プログラミング言語はもっと形式的です。随時更新される公式のドキュメントに則って厳密にルールが定められています。
###意味論 (Semantics)
「りんご」という単語を目にすると、私たちは丸くて赤い食べ物を想起します。このようにある単語とその単語から想起される意味の関係性のことを意味論と言います。ただ、人間の言葉は複雑なので必ずしも単語の意味するものが常に同じとは限りません。締め切り直前のタスクを目の前に頭を抱えて「やばい!」と言っている人を見れば、ネガティブな意味だと分かります。話題のコンビニスイーツを食べて「やばい!」と言っている人はポジティブな意味でしょう。このように人間の言葉では文脈によって変化します。
一方で、プログラミング言語においてはそのように文脈によって意味が変わってしまっては困ります。この後説明しますが、最終的にはプログラミング言語は数字の羅列に翻訳されるため、正確性がとても重要です。そこで、多くの言語では「単語はこういう意味ですよ」というのが明確に決まっています(形式的意味論)。「a + b」という言葉は(当然だと感じると思いますが)「aの値とbの値を加算した値である」という意味になります。
###プログラミングパラダイム
上記で述べたSyntaxとSemanticsは、もちろんそれぞれの言語によって異なります。しかし、言語をまたいで共通の土台となっているものが存在します。ポルトガル語話者とスペイン語話者はお互いになんとなくで意思疎通がある程度出来るそうです。これと同じように、JavaScriptを知っている人はDartを見てもなんとなくで結構読めてしまうと思います。このような共通の土台のことをプログラミングパラダイムと言います。Dartは、「オブジェクト指向プログラミング」「関数型プログラミング」「命令型プログラミング」という複数のパラダイムにより出来ているようです。一番よく耳にするオブジェクト指向は、Dartにおいてクラスの概念や継承、カプセル化といったところに現れています。このクラスや継承の概念的な理解は、こちらの一連の動画がとても分かりやすくてオススメです。
カプセル化とは、データの保護を目的として外部からのアクセスを制限することです。
下の方で貼っているサンプルコードの中に
title: Text(widget.title),
という箇所がありますが、このwidgetの中身を見てみると、以下のような記述が出てきます。
T get widget => _widget!;
T? _widget;
このような処理をすることでデータの保護をしながら外からの参照を可能にしています。
プログラミング言語を通してアプリを動かす際に、コンピュータは私たちの書いた言語を読むことが出来ないため機械が認識出来るように翻訳する必要があります。
これを一括して行う言語をコンパイラ言語、実行時に逐一行う(同時通訳するイメージ)言語をインタプリタ言語と言います。インタプリタは以下のようなイメージです。
このインタプリタをする言語は一般に動的型付け言語、コンパイルを行う言語は一般に静的型付け言語と呼ばれます。
ではDartはどちらの言語なのかと言うと、コンパイルを行うものの動的型付けの側面もハイブリッドに持っています。実際、メジャーな言語にはこのコンパイラ言語の側面とインタプリタ言語の側面両方を持ち合わせているものが少なくないようです。2
ではここから、Dartの静的型付けの側面と動的型付けの側面をこちらのサンプルコードとともに見ていきたいと思います。
####完成図
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
home: MyHomePage(title: 'Flutter大学'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({required this.title});
final String title; //constは使えない
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _counter = 0; //正しくは、 int _counter = 0;
// var _counter = 0; 型を明示しなくても推論してくれる
// dynamic _counter = 0; 型を特定しない
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('出席回数'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
エラー検知
もし仮に以下のようにミスをしてしまったとします。
String _counter = 0; //正しくは、 int _counter = 0;
Dartでは、コンパイル時に間違っていることを教えてくれます(コンパイルエラー)。一方で、動的型付け言語であるJavaScriptの場合は、実際に実行してみないとエラーが起こっているかが分かりません(実行時エラー、Runtime error)。この点は、スクリプト言語3と呼ばれているJavaScriptを改良したというDartの優れている点なのかなと感じています。
####変数
同様の処理は以下のように書くことも出来ます。
var _counter = 0;
このように型を明示せずともコンパイル時に、文脈から判断してくれることを型推論と言います。
また、以下のようにしてもエラーなく動作します。
dynamic _counter = 0;
この場合、この変数の型は特定されず、動的な状態が保たれます。
このdynamicという型は、他APIからデータを取得する時によく用いられます。
定数
定数はその名の通り一定の値を示します。一度値が代入されると再代入することは出来ないため、先の例の_counterのようにボタンを押すたびに変更することはできません。Dartにはfinalとconstという二つの定数が存在します。この二つの違いは、動的か静的かの違いです。つまり、「プログラム実行時に値が決まっているかどうか」が重要になってきます。
MyHomePage({required this.title});
final String title; //constは使えない
このようにクラスのプロパティにはfinalが使われています。このtitleはプログラム実行時(インスタンス生成時)に他のクラス(MyAppクラス)から渡された際に始めて「Flutter大学」であると分かるため、この記述段階では値が決まっていないからです。
一方で、こちらではconstが使われています。
const Text('出席回数'),
「出席回数」という文字列は、もうこのソースコードを書いている段階で決定しているからです。
尚、Widgetの頭にconstを明示すると再描画時(ボタンを押した際)にパフォーマンスが向上するのですが、それはconstが「ここはずっと同じだからわざわざもう一度描かなくてもいいよ」ということを伝えているためです。
#最後に
最後までお読みいただきありがとうございました。
今回は全体像を図解して大まかな部分を押さえたつもりですが、Dartの特徴全ては全然カバー出来ていないので、引き続き勉強していきたいです。
間違いや分かりづらいところ等あれば、ご指摘お願いします。