おひとり

できる限りひとりで楽しむための情報やプログラミング情報など。

コンピュータ科学における抽象化とは何か

f:id:hitoridehitode:20200607011458p:plain:w500
抵抗、コンデンサ、Arduino、iPodとMacBook Pro

コンピュータ科学における抽象化について考えていきましょう!
この記事では、コンピュータの全体像、インターネットの仕組み、プログラミングの3つの「抽象化」の具体例を見ていきながら、コンピュータ科学における抽象化を考えてみます!

TL;DR(長すぎるので、概要だけ知りたい人へ)

この記事は長い、、、と思うかもしれませんが、、、たったこの1ページでコンピュータの全体像、インターネットの仕組み、プログラミングの3つ仕組みの概要を見ることができるんです。ってことは、逆にそれほど長くはないかも。。。と思いませんか??

コンピュータ科学における抽象化とは、

内部の詳細を隠して、その性質だけを外部に公開するようにすること

です。 そうすることによって、その性質をつかって、これまで難しかった複雑な機能や処理が簡単に、効率良く実現できるようになります。

おことわり

結構私見が入っているかもしれません。
基本的には初心者向きで、細かいことは「抜き」にしている部分があります。ご了承ください。

抽象化とは

コンピュータ科学における抽象化とは、

内部の詳細を隠して、その性質だけを外部に公開するようにすること

です。 そうすることによって、その性質をつかって、これまで難しかった複雑な機能や処理が簡単に、効率良く実現できるようになります。

ちょっと概念的ですね。
現実世界での例を見てみましょう。

例:クルマ

クルマを運転するときを思い浮かべてください。
クルマには3つの伝統的な装置があります。ハンドル、アクセスペダル、ブレーキペダルの3つですね。
これらを次のような性質として理解して運転しています。

装置 性質
ハンドル 右に回すと、クルマは右を向く。
左に回すと、クルマは左を向く。
ブレーキペダル 踏むと止まる。
アクセスペダル 踏むと加速する。

本来、クルマが曲がるには、以下のような仕組みがあります。

また、アクセルペダルだって、電気自動車とガソリン自動車で最終的に「何が起こって加速するのか」は違ってくるでしょう。

しかし、私たちは「ハンドルを回すとクルマの向きが変わる」、「アクセルペダルを踏むとクルマが加速する」という性質だけを知っていればよく、その内側にある詳細は知らなくても運転できます。

この例からは、「クルマの内部の仕組みは抽象化されていて、ハンドル等を介して私たちが簡単に使うことができる」ということがいえますね。
注意)知らなくても「使える」というだけで、「知らなくてもいい」というネガティブなニュアンスではありません。

例:ゴミ出し

f:id:hitoridehitode:20200607012004p:plain:w300
ゴミ出しにも抽象化が隠れています。
クルマのような装置だけではなく、人によるサービスにも抽象化を見つけることができます。
私たちは、次のようなルールを知っているだけで簡単にゴミを捨てることができます。

  • 所定の場所に出すこと。
  • 決められた曜日・時間に出すこと。
  • 指定袋などの決められた「出し方」でだすこと。

こうすることで、

  • どの業者が回収にくるのか。
  • ゴミはどのように処理されるのか。

などの詳細は知らなくてもゴミを出せます。
この例からは、「自治体やゴミの処理を行う会社が協力して、ゴミの処理について抽象化しているから、簡単なルールを守るだけでゴミが捨てられる」ということになりますね。

注意)知らなくても「使える」というだけで、「知らなくてもいい」というネガティブなニュアンスではありません。

ここまでのまとめ

さて、さきほどの2つの例のように、身の回りの家電や人によるサービスなど、さまざまな場所で抽象化がみられることが分かりました。
抽象化のおかげで、私たちは「そのものの詳細」を知らなくても、ルールや性質を知るだけで、簡単に使えることが理解出来たでしょうか。

言い方を変えると、抽象化とは、特定の詳細については「その道の専門家」に任せることともいえます。
私たちはクルマに乗りますがその仕組みはクルマの専門家に「任せて」います。そして、クルマが提供するハンドルなどの「性質」を理解することで運転しています。

f:id:hitoridehitode:20200607012315p:plain:w200
専門家に任せる!
そして、あなたも大人ならば多分何かの「専門家」であるはずです。ちょっと飛躍してますけど、それぞれの詳細を専門家に任せることで、私たちはとても高度な生活ができる「文明」を作ってきたんだともいえますね。
ちょっと壮大すぎかな。

こっからはコンピュータ、ネットワーク、プログラミングにおいて、それぞれどんなところに抽象化が出てくるか見ていきましょう。

コンピュータを実現する抽象化

全体像

私たちが普段使っているスマホもコンピュータです。そして、コンピュータは様々な抽象化のおかげで成り立っています。
私たちはスマホのYouTubeアプリで動画を見るとき、それがどのような抽象化の上にあるか見てみましょう。

f:id:hitoridehitode:20200606164935p:plain
スマホでYouTubeアプリを使うときの抽象化

回路

電子・電気回路の世界です。
ここでは電流を制御しています。

例えばこのトランジスタ。

f:id:hitoridehitode:20200606233633p:plain:w350
トランジスタ
これは言うなればちっちゃい「スイッチ」。
3本足の1つ「ベース」の電圧をHIGHにしたりLOWにしたりすることで電流の制御ができます。
こいつが、一般的なノートPCには10億個入ってると言われています。

akizukidenshi.com

このようなLチカ(LEDを点灯させるプログラム)も書けます。

f:id:hitoridehitode:20200606233846p:plain:w300
Arduino回路図

#define TRANSISTOR_BASE (13)

void setup() {
  pinMode(TRANSISTOR_BASE, OUTPUT);
}

void loop() {
  digitalWrite(TRANSISTOR_BASE, LOW);
  delay(500);
  digitalWrite(TRANSISTOR_BASE, HIGH);
  delay(500);
}

論理回路

論理回路は、1つ、または2つの信号を受け取って、対応する信号を返す機能を提供します。論理ゲートとも言いますね。
これらは電気・電子回路によって実現されています。(回路図を見るとわかります。)

さて、こいつはNANDとよばれる論理回路。

f:id:hitoridehitode:20200606165623p:plain
NAND

NANDであれば、次のような出力になるのはもう勉強しましたか?

入力 出力
A B C
1 1 0
0 1 1
1 0 1
0 0 1

論理回路を使う場合は、その内部が「どのような電気・電子回路になっているか」という詳細は気にせず、その性質(入力に対する出力のルール)を知っていれば使うことができます。

CPUの命令

CPUはたくさんの論理回路が組み合わさってできています。
例えば以下のような半加算器と呼ばれる論理回路があります。

f:id:hitoridehitode:20200606171732p:plain
半加算器

入力 出力
A B S C
1 1 0 1
0 1 1 0
1 0 1 0
0 0 0 0

CPUにはこのような論理回路がたくさん組み合わさって、「足し算」などの意味のある機能を実現しています。

そして、CPUを使う場合は、例えば足し算を行う場合、論理回路の詳細を知らなくても、その命令「ADDL」などを知っていれば良いことになります。
※実際にはコンパイラが必要。

OSのAPI

f:id:hitoridehitode:20200607000356p:plain:w200
帰属: lewing@isc.tamu.edu Larry Ewing and The GIMP

OSは、CPUの命令を使ってハードウェアを制御しています。
そして、APIを通じてハードウェアを簡単に使うための機能が使えるようにします。 ※API(Application Programming Interface)は、その機能にアクセスするための関数やメソッドのこと。

例えば、HDDにファイルを作りたい、といったときもOSのAPIである「CreateFile()」の使い方を知っていれば良いいことになります。つまり、HDD(ただのディスク)をどのように管理してファイルを配置するかなどはOSがやってくれているので気にしなくてもいい、ということになりますね。

YouTubeアプリ

f:id:hitoridehitode:20200607002240p:plain:w200

ここでようやくYouTubeアプリが登場します。
みなさんが普段つかっているYouTubeアプリはOSのAPIを使って実現されている、ということになります。もちろん、ソースコードは公開されていませんので、どのようなAPIを使っているかなどは知るよしはありませんが。
少なくとも、OSのAPIが使われていることは確かですね。

抽象化のすごさ

YouTubeアプリやWebアプリも、コンピュータの抽象の上に成り立っていることを見てきました。 つまり、アプリは最終的には「電圧や電流を制御する」ということを永遠とやっている、ということになります。

でも、「電圧や電流をどのように制御すればYouTubeアプリやWebアプリが作れるか」などと考えていてはいつになっても動くモノはできません。「電流から考えていては複雑すぎてとても無理だ」、ということですね。

そこで適切なレベルで「抽象化」することにより、少しずつ私たちが理解出来るレイヤーを作っていきます。
そのようにして、複雑なことが簡単にできるようになっていく、という訳ですね。

f:id:hitoridehitode:20200607012612p:plain:w300
抽象化は素晴らしい!

ネットワーク通信を実現する抽象化

では、もっともっと抽象化の例を見ていきましょう。今度はネットワーク通信です。

f:id:hitoridehitode:20200607012814p:plain:w300
インターネットにも抽象化が!
皆さんがこのページを見ているのは、インターネットにつながっているからですね。そして、インターネット(WWW)はTCP/IPというプロトコルスタックで実現されています。

※プロトコルというのは、通信するときの決まりのこと。抽象化のレベルに応じて適切な「通信の決まり」があります。プロトコルスタックとは、その「決まり」をどのように組み合わせて通信を実現するか、という技術の一覧です。

全体像

では、早速、インターネットを実現するTCP/IPの抽象化をいてみましょう。

f:id:hitoridehitode:20200606181646p:plain
TCP/IPの抽象

OSI基本参照モデルというのは、「インターネット通信を実現するためにはこのような機能が必要だよね」という「モデル」。ただし、あくまで「モデル」であり、実際に使われているTCP/IPは「それに近い」ものであり、イコールではありません。

見たように、インターネット通信も抽象化の上に成り立っていますね。
簡単に一つ一つみていきましょうか。

物理層

f:id:hitoridehitode:20200607002616p:plain:w300
オシロスコープ

物理層では、コンピュータのデータ(つまり1と0)を電気信号、もしくは光信号に変換する機能です。
光通信というのはここですね!!

データリンク層

f:id:hitoridehitode:20200607003444j:plain:w300
ハブ等を介してつながる、隣接したコンピュータ間の通信を実現

データはインターネットに出て行く前に、まずはあなたのコンピュータから出て行かないといけません!!
直接つながっているコンピュータ同士の通信は、このデータリンク層で担保します。
直接つながっている、というのは、LANケーブルでつながったあなたのWiFiルータや、WiFi経由でつながっているルータのこと。

まずはお隣のコンピュータにデータを送らないとということですね。
なお、ここではMACアドレス、というアドレスが使われます。

ネットワーク層

さて、ようやくあなたの家の外にデータが出て行きます。
TCP/IPでは、IPというプロトコルがこの役割を担います。

f:id:hitoridehitode:20200607004743j:plain
異なるネットワーク間の通信、、、すなわちインターネットな通信を実現!

ネットワーク層では、インターネットにつながっている全てのコンピュータとの通信を可能にします!!すごいですね。

ここではIPアドレスというアドレスを使います。聞いたことがありますか?インターネット上で、コンピュータを識別する番号です。(現状、実際には全てのコンピュータにインターネットで通用するアドレスがついている訳ではありません。)

ルーターは、あなたのデータを例えばGoogleのサーバに接続するにはどのような経路にデータを流せばいいか判断します。 そして、隣接するネットワーク機器がバケツリレーをしながら、目的のコンピュータにデータを届けています。

トランスポート層、セッション層、プレゼンテーション層

さて、コンピュータには様々なソフトウェアがインストールされています。通信相手のコンピュータにデータが届いたとしても、そのなかのどのソフトウェアにデータを届けたらいいのか分かりません。
TCP/IPでは、それを解決するのは、TCPというプロトコルが担います。 (UDPというプロトコルも使われます。)

f:id:hitoridehitode:20200607005250j:plain
トランスポート層では、ソフトウェア間の通信を実現!

TCPではポート番号というアドレスをつかって、ソフトウェア間の通信を実現します。
※本当はTCPには紹介しきれないくらいもっとたくさんの機能があります。

アプリケーション層

さて、ここまでで特定のアプリにデータを届けることができました。
しかし、まだアプリごとにどのような決まりで通信すればいいのか決まっていません。「なんとなく」自己流にデータを送っただけだと、相手のコンピュータは解釈できません。
このようなフォーマットでおくってね、という決まりが必要です。
それを行うのがアプリケーション層です。

ブラウザではHTTPというプロトコルを使っています。
HTTPには以下のようなルールがあります。

  • HTTPヘッダという、ヘッダを使ってね。
  • 実際のデータはHTTPボディに書いてね。
  • ヘッダとボディの間は空の改行を入れてね。

このサイトにアクセスしたときにはHTTPをつかって、この記事のHTMLファイルをダウンロードしていることになります。

telnetを使ってHTTPを手動でやってみよう。

$> telnet lambda.tokyo 80
Trying 192.30.252.153...
Connected to lambda.tokyo.
Escape character is '^]'.

ここまで来ると、サーバとのコネクションが確立された、ということ。ここでHTTPリクエストを送信してみよう。

GET / HTTP/1.1
host: lambda.tokyo

すると、HTMLが返ってくる。もし、リクエストのフォーマットを間違えると、Bad Requestが返ってくる。
これは、HTTPのルール(アプリケーション層のプロトコルのルール)に違反しているということ。
ブラウザはリンクをクリックするたびに、このようなリクエストをいつも自動でやってくれているんですね。

ほかにはメールをやりとりするためのSMTPなどのプロトコルがココに当たります。

まとめ

私たちが普段使っているネットワーク通信にも抽象化がありました。

  • 物理層がデータを電気や光の信号に変換してくれているから、データリンク層がとなりのコンピュータにデータを送信できる。
  • ネットワーク層では、「隣のコンピュータにデータを渡せる」というデータリンク層の機能を使うことで、バケツリレー的に世界中のコンピュータとの通信を実現できる。
  • トランスポート層では、「世界中のコンピュータとの通信できる」というネットワーク層の機能を使うことで、ソフトウェア間の通信を実現できる。
  • アプリケーション層では、「ソフトウェア間の通信ができる」というトランスポート層の機能を使うことで、Webブラウザなどのアプリが使うデータの通信を実現できる。

各レイヤーが適切な抽象化をしてくれているから、その上のレイヤーのプロトコルがもっと壮大な機能を実現できている、ということですね。

プログラミングでの抽象化

これまでは比較的、壮大な抽象化の例を見てきました。
ここでは、普段書いているプログラミングにも抽象化はあります!ということを見ていきましょう。

ここでは、Arduinoのプログラム(C言語)をつかって見ていきましょう。
※C言語分からなくても読めます!大丈夫。

LEDの例

f:id:hitoridehitode:20200607010647j:plain:w200
LED
通常、ArduinoでLEDを点灯させるには指定のポートの電圧をHIGHまたはLOWにしますよね。
例えば以下のようなコードになります。

#define LED (13)

void setup() {
  pinMode(LED, OUTPUT);
}

void loop() {
  digitalWrite(LED, HIGH);
  delay(500);
  digitalWrite(LED, LOW);
  delay(500);
}

これを抽象化しましょう。
電圧をHIGHにする、またはLOWにするというのは、「実装の詳細」といえます。

そこで、「LEDを点灯する」もしくは「LEDを消灯する」というように、詳細を隠して抽象化しましょう。
以下のように2つの関数ledOn()ledOff()を作りました。

#define LED (13)

// LEDを点灯させる。
void ledOn(int pin) {
  digitalWrite(LED, HIGH);
}

// LEDを消灯させる。
void ledOff(int pin) {
  digitalWrite(LED, LOW);
}

void setup() {
  pinMode(LED, OUTPUT);
}

void loop() {
  ledOn(LED);
  delay(500);
  ledOff(LED);
  delay(500);
}

今回は同じファイルに分けました、通常は別のファイルに分けたり、構造体を使うなどしてリファクタリングした方がいいでしょう。

もっと複雑な例

さっきの例は簡単すぎました。もっと複雑な抽象化の例を見てみましょう。
次はLCD、つまり小さな液晶を制御するプログラムを考えます。

f:id:hitoridehitode:20200607010748j:plain:w400
LCD

akizukidenshi.com

さて、まずはいきなりLCDを制御するプログラムを見てみましょう。

#include <Arduino.h>
#include "lcd.h"
#include "Wire.h"

#define LCD_I2C_ADDR (0x3e)
#define LCD_INSTRUCTION_EXEC_TIME_MS (2)

void lcd_wait_instruction_ended() {
    delay(LCD_INSTRUCTION_EXEC_TIME_MS);
}

void lcd_cmd(byte cmd) {
    Wire.beginTransmission(LCD_I2C_ADDR);
    Wire.write(0x00);
    Wire.write(cmd);
    Wire.endTransmission();
}

void lcd_init(const Lcd *lcd) {
    // reset
    delay(500);
    pinMode(lcd->pin_reset, OUTPUT);
    digitalWrite(lcd->pin_reset, LOW);
    delay(1);
    digitalWrite(lcd->pin_reset, HIGH);
    delay(10);

    // LCD initialize
    delay(40);
    Wire.begin();
    lcd_cmd(0x38);                       // function set
    lcd_cmd(0x39);                       // function set
    lcd_cmd(0x14);                       // interval osc
    lcd_cmd(0x70 | (lcd->contrast & 15));     // contrast low
    lcd_cmd(0x5c | (lcd->contrast >> 4 & 3)); // contrast high / icon / power
    lcd_cmd(0x6c);                       // follower control
    delay(300);
    lcd_cmd(0x38); // function set
    lcd_cmd(0x0c); // display on
    lcd_cmd(0x01); // clear display
    delay(2);
}

void lcd_data(byte data) {
  Wire.beginTransmission(LCD_I2C_ADDR);
  Wire.write(0x40);
  Wire.write(data);
  Wire.endTransmission();
}

void lcd_puts(const char *str) {
    while(*str) lcd_data(*str++);
}

int lcd_puts_diff(const char prev[9], const char str[9], int row) {
    int cnt = 0;
    for (int i=0; i<9; i++) {
        if (prev[i] != str[i]) {
            cnt++;
            lcd_set_cursor(i, row);
            lcd_data(str[i]);
        }
    }

    return cnt;
}

void lcd_set_cursor(byte col, byte row) {
    lcd_cmd(0x80 | (row * 0x40 + col));
    lcd_wait_instruction_ended();
}

void lcd_off() {
    lcd_cmd(0x08);
    lcd_wait_instruction_ended();
}

void lcd_on() {
    lcd_cmd(0x0c);
    lcd_wait_instruction_ended();
}

void lcd_clear() {
    lcd_cmd(0x01);
    lcd_wait_instruction_ended();
}

簡単でしたか?難しいですか?
おそらくほとんどの人が「難しい」と感じたはず。

それもそのはず。このプログラムは今回用意したLCDのデータシートと呼ばれる仕様書を元に作ったもの。つまり、この仕様書を読んだことがない人は「なにをやってるのかさっぱり分からない」と言うことに。

f:id:hitoridehitode:20200607010934j:plain:w600
今回使ったLCDのデータシート

しかし、一方でこのプログラムを使えば、LCDを制御するのはとても簡単。
これが、LCDをつかったプログラムです。

#include "lcd.h"

Lcd lcd;
void setup() {
  // LCDの設定
  lcd.contrast = 18; // コントラストの設定
  lcd.pin_reset = 13; // 13番pin
  lcd.pin_sda = SDA; // ArduinoのSDAポート
  lcd.pin_scl = SCL; // ArduinoのSCLポート

  // LCDの初期化
  lcd_init(&lcd);
  
  // LCDへ文字の表示
  lcd_puts("Abstract");
}

void loop() {
}

単にlcd_puts()関数に文字列を渡してやるだけでLCDに文字が表示されました。

f:id:hitoridehitode:20200607013002j:plain:w400
LCDの詳細を知らなくても簡単に使える。

今回作ったLCDを制御するライブラリを使えば、ちょっとライブラリの使い方を覚えるだけでLCDに文字を表示させることができます。
つまり、LCDの細かい仕様はLCDの制御ライブラリが抽象化しているため、あなたは知る必要がないということですね。

まとめ

コンピュータ科学における抽象化とは、

内部の詳細を隠して、その性質だけを外部に公開するようにすること

です。 そうすることによって、その性質をつかって、これまで難しかった複雑な機能や処理が簡単に、効率良く実現できるようになります。

そして、コンピュータは抽象化によって成り立っています。

  • コンピュータそのもの
  • ネットワーク通信
  • プログラミング(ソフトウェア)

至る所に抽象化が現れてきます。

そして、新しい技術が登場したら、それはもしかしたら既存の「何か」を抽象化したものかもしれないということです。
新しい技術を学ぶ時は、「何を抽象化したか」に注目すると、どのような技術かだけではなく、「なぜその技術を使うのか」が理解出来ることでしょう。

そして、より抽象度の低いものほど「変わりにくい」という性質があります。なぜなら、抽象度の低い技術の仕様を大きく変更すると、それに依存する上位の技術の仕様も変更しないければならない時があるから。
そのため、「知らなくても使える」抽象度の低い技術であっても学ぶ価値は大いにあり、学べば時代に左右されず、かつ新しい技術を習得するための基礎知識を得ることができると考えています。

ここまで読んでくれてありがとうございます。お疲れ様でした〜!

参考文献

www.amazon.co.jp