POSTD PRODUCED BY NIJIBOX

POSTD PRODUCED BY NIJIBOX

ニジボックスが運営する
エンジニアに向けた
キュレーションメディア

A. Castro-Castilla

本記事は、原著者の許諾のもとに翻訳・掲載しております。

常に世界のどこかで誰かが、この世で一番のプログラミング言語は何かというトピックで投稿し、忘れ去られた言語のすばらしい一面や、新しい言語の有用性を主張しています。どうやら、その順番が私に回ってきたのかもしれません。そろそろ私も、プログラミング言語についての自分の考えを皆さんにお伝えしようと思います。

始めに少し言い訳をさせてください。30以上の言語で開発した経験があり、他の人が書いた多くのコードと悪戦苦闘をしてきた開発者でもない限り、「自分の意見には客観性がある」とはとても言えないと思います。そんなわけで、このトピックを取り上げる他の多くの人と同じように、私の意見も偏っています。多くの言語に精通した開発者がこの話題自体を不毛だと感じるのは、このせいかもしれませんね。

要約: すばらしい言語

早速、このブログ限定ということで、私が考える”すばらしい言語”を発表しましょう。

  • アセンブリ言語: マシン語
  • C言語: システム開発のための言語
  • JavaScript: Webのための言語
  • Scheme: 軽量、組み込み可能で、C言語にもJavaScriptにもコンパイルできる極めて柔軟な言語

各コードのサンプルは Rosetta Code で参照できます。

妥当な言語

ここでいう”妥当な言語”というのは”最高の言語”という意味ではありません。単に使っているプログラマの数が多く、そのせいでソフトウェア開発の現場でよく使われるというだけです。これから各言語について私の意見を述べますが、こまごまとした議論がわずらわしければ、これ以降は読まなくてもかまいません。

Ada

私は、メモリセーフを念頭に置いた言語設計というものにずっと関心を持ってきました。リアルタイムOS上のアプリケーションやクリティカルなシステムの開発全般で重要な要素ですよね。ただAdaを使おうと考えるくらいの人なら、既に技術的な土台はお持ちのはずですので、あえてこの記事を読む必要はないかもしれません。なぜなら、この言語は、いったん使えるようになると”これが最適”と思わせてしまうような力があるからです。以下は、Adaのコード例です。

       function Best_Shuffle(S: String) return String is
      T: String(S'Range) := S;
      Tmp: Character;
   begin
      for I in S'Range loop
         for J in S'Range loop
            if I /= J and S(I) /= T(J) and S(J) /= T(I) then
               Tmp  := T(I);
               T(I) := T(J);
               T(J) := Tmp;
            end if;
         end loop;
      end loop;
      return T;
   end Best_Shuffle;

すごく安全そうに見えますよね。

Bourne(Again)シェル

シェル言語でLinuxのシェルスクリプトを書いていると、本当にシェルを使わなくてはいけないのだろうかといつも疑問を感じてしまいます。なぜならシェルで書いたところで結局はスクリプトに向き合うことになるからです。あげくには、Stack Overflowなどなかった時代にはどうしていたのだろう、などと思い始めます。それはともかくとして、適切な参考書籍に目を通せば、この言語には構成(と一貫性)が欠けているということが分かってくると思います。言語自体に特筆すべきところはありませんし、プログラマの意識や生産性を高めるわけでもなく、ビジネス的な観点から見て利点があるということもありません。ただUNIX系のシステムに広く浸透しているというだけです。しかしシステム管理者には必須の言語ですし、実際に使ってみるとそれほど悪くありません。JavaScriptもそうですが、習得するには他の言語と比べてある程度の予備知識が必要になります。

さて、UNIXのシェルが適しているのはどんなシーンでしょう。

  • Mac OS X/Linux/POSIXのシステム管理
  • タスクを自動化するため
  • 強力なコマンドラインを利用したい場合

以下はBourneシェルのコードです。ブーリアンの処理に注目です。

#!/usr/bin/env sh

l="1"
while [ "$l" -le 5 ]
  do
  m="1"
  while [ "$m" -le "$l" ]
    do
    printf "*"
    m=`expr "$m" + 1`
  done
  echo
  l=`expr "$l" + 1`
done

C言語

たとえどんなに毛嫌いしていようとも尊敬に値する言語があるとすれば、それがC言語です。迷うことなく”すばらしい言語”の一つに選びました。Cは(モデルではなく)システムレベルのプログラミングができる言語です。またUNIXやC系列言語の父であり、システム開発の共通言語でもあります。他のライバル言語たちの間で埋もれることなく、長い時を経て今なお現役で多くの人に使われ続けており、開発、デバッグ、分析、C言語開発のサポートといった作業をするためのツール群も数多くそろっています。その豊富さは言語自体の欠点を補って余りあるほどです(欠点もそんなに多くないと私は思いますが)。また、プロセッサーを選ばない汎用性の高いアセンブリ言語を作るという目的は、達成していると言えます。最近では一風変わったアーキテクチャでも事実上のアセンブリ言語として使用されており、Cコンパイラで生成されたコードを超えるのはかなり難しくなっています。

このようにC言語はとてもパワフルなツールであり、ぜひともマスターしたい言語でもあります。ただし、この言語は情け容赦ありません。あなた自身が自分のやることを常に自覚して、C言語に伝えなければなりません。そうすることで、C言語とマシンの交流が可能となります。このことは、ある種の美しさを感じさせると同時に、言語が持つ実用性を示しています。なぜなら、C言語の持つ低水準という特徴を抜きにしては解決できない問題もあるからです。C言語のプログラマたちは、やりたいことの道筋をしっかりと把握する必要があります。それが長期的には堅実なソフトウェアの道へとつながるのです。仮にCを超える言語が出てくるとしたら、並行処理を強力にサポートした低水準プログラミング言語でしょう。もしくはいつか、HaskellのプロパティとC言語の広汎性を兼ね備えた神話的な言語が登場するかもしれませんね。

以下は、Linuxカーネルから引用したCのコードです。

    int next_pidmap(struct pid_namespace *pid_ns, unsigned int last)
{
    int offset;
    struct pidmap *map, *end;

    if (last >= PID_MAX_LIMIT)
        return -1;

    offset = (last + 1) & BITS_PER_PAGE_MASK;
   map = &pid_ns->pidmap[(last + 1)/BITS_PER_PAGE];
   end = &pid_ns->pidmap[PIDMAP_ENTRIES];
   for (; map < end; map++, offset = 0) {
       if (unlikely(!map->page))
           continue;
       offset = find_next_bit((map)->page, BITS_PER_PAGE, offset);
       if (offset < BITS_PER_PAGE)
           return mk_pid(pid_ns, map, offset);
   }
   return -1;
}

C++

モンスターです。私が初めて習得した言語だったので、他の多くの言語を試してみるまで、C++を使うと開発者の生産性が下がり、能力が制限されるという事実に気がつきませんでした。C++の悪評は 有名なプログラマたち によって広まっていますが、私もまったく同感です。まるでビャーネ・ストロヴストルップが思いつくままに一つまた一つと機能をC言語に追加していったらできあがってしまった代物のように見えます。C++に課されたコードの読みにくさは、開発者の生産性を80パーセント以上低下させると言っていいでしょう。次のように考えてみてください。あなたは容量Xの脳を持っています。どのくらいの容量かということは別にして容量には限りがあるため、あなたはできる限り多くの容量を重要な事柄のために使いたいと思っています。こうした場合、言語 “それ自体” のために消費するエネルギーを減らし、問題解決やアルゴリズムのエンコードに脳の大部分を使うのが賢明でしょう。言語が複雑な場合、あなたがどんなに賢くても、言語のシンタックスやセマンティクスに多くの容量を使うことになり、自分の考えを効率的に反映するための容量は少なくなります。

C++は、得るものが少ない割に複雑すぎる言語の典型です。確かにC言語で大規模なプログラムを構築するのは難しいと思います(ただし間違いなく選択肢の一つではあります。Linuxカーネルを見てください)。C++が実際に世界中で使われているという事実を除けば、GoやRust、Dの方があらゆる点で優れています。

これは、テンプレートを使ったC++の例です。C++の特徴は、テンプレートやクラスの定義より、このようなユーザコードの方がはるかに理解しやすいと思います。

#include <fstream>
#include <string>
#include <iostream>

int main( int argc , char** argv )
{
    int linecount = 0;
    std::string line;
    std::ifstream infile( argv[ 1 ] );
    if( infile )
    {
        while( getline( infile , line ) )
        {
            std::cout << linecount << ": " << line << '¥n';
            linecount++;
        }
    }
    infile.close();
    return 0;
}

下記は、テンプレートの定義の非常に簡単な例です(これ以上複雑になると、ひどいコードになる傾向があります)。

namespace rosettacode
{
  template<typename T> class queue
  {
  public:
    queue();
    ~queue();
    void push(T const& t);
    T pop();
    bool empty();
  private:
    void drop();
    struct node;
    node* head;
    node* tail;
  };

  template<typename T> struct queue<T>::node
  {
    T data;
    node* next;
    node(T const& t): data(t), next(0) {}
  };

  template<typename T>
   queue<T>::queue():
    head(0)
  {
  }

  template<typename T>
   inline void queue<T>::drop()
  {
    node* n = head;
    head = head->next;
    delete n;
  }

  template<typename T>
   queue<T>::~queue()
  {
    while (!empty())
      drop();
  }

  template<typename T>
   void queue<T>::push(T const& t)
  {
    node*& next = head? tail->next : head;
    next = new node(t);
    tail = next;
  }

  template<typename T>
   T queue<T>::pop()
  {
    T tmp = head->data;
    drop();
    return tmp;
  }

  template<typename T>
   bool queue<T>::empty()
  {
    return head == 0;
  }
}

C\

プログラマの創造性を低下させるべく作られた、企業のための言語です。創造性は大きな組織では邪魔ですからね。オブジェクト指向で、静的な型づけができ、冗長で、重いライブラリと多くの定型句が存在します。C#誕生の背後にはマイクロソフトの影があります。しかし誤解しないでください。決して悪い言語ではないのです。ただマイクロソフトが当初目指していたはずの魅力がないというだけです。少なくともVisual Basicと比べると抜本的な改善が見られます。次のような場合に使います。

  • Windowsの開発
  • ゲームの開発(たいていはマイクロソフトに使用を強制されるからですが、私は古きよきCやC++の方が好きです)
  • Unity3D、Xamarin、.NET、XNAなど、C#を使った大型プロジェクトの開発

以下がコードです。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

class Program
{
    static SortedDictionary<TItem, int> GetFrequencies<TItem>(IEnumerable<TItem> items)
    {
        var dictionary = new SortedDictionary<TItem, int>();
        foreach (var item in items)
        {
            if (dictionary.ContainsKey(item))
            {
                dictionary[item]++;
            }
            else
            {
                dictionary[item] = 1;
            }
        }
        return dictionary;
    }

    static void Main(string[] arguments)
    {
        var file = arguments.FirstOrDefault();
        if (File.Exists(file))
        {
            var text = File.ReadAllText(file);
            foreach (var entry in GetFrequencies(text))
            {
                Console.WriteLine("{0}: {1}", entry.Key, entry.Value);
            }
        }
    }
}

Objective-C

私はC++(およびC#)よりObjective-Cを気に入っています。構文を見ると美しくはありませんが、言語としてObjective-Cが好きなのです。Objective-CにはNeXTSTEPをベースにしたすばらしいライブラリセットがあり、C言語に本当の意味での改善が加えられていて、手に負えないくらい成長しすぎたり、親言語のキーワードとごっちゃになって混乱することもありません。前述のとおり、特に関数をネストする時は少し読みにくいですが、その美しさはシンタックスではなく概念的なアプローチの中にあります。このネストされたコールを見てください。

char bytes[] = "some data";
NSString *string = [[NSString alloc] initWithBytes:bytes length:9 encoding:NSASCIIStringEncoding];
これはObjective-Cのいわゆる"ブロック"を利用した、C言語から生まれた美しいコードです。
    #import <Foundation/Foundation.h>

typedef NSArray *(^SOfN)(id);

SOfN s_of_n_creator(int n) {
  NSMutableArray *sample = [[NSMutableArray alloc] initWithCapacity:n];
  __block int i = 0;
  return ^(id item) {
    i++;
    if (i <= n) {
      [sample addObject:item];
    } else if (rand() % i < n) {
      sample[rand() % n] = item;
    }
    return sample;
  };
}

int main(int argc, const char *argv[]) {
  @autoreleasepool {

    NSCountedSet *bin = [[NSCountedSet alloc] init];
    for (int trial = 0; trial < 100000; trial++) {
      SOfN s_of_n = s_of_n_creator(3);
      NSArray *sample;
      for (int i = 0; i < 10; i++) {
        sample = s_of_n(@(i));
      }
      [bin addObjectsFromArray:sample];
    }
    NSLog(@"%@", bin);

  }
  return 0;
}

Clojure

私は Schemeプログラマ なので、Clojureには敬意を払っています。Clojureは、いわゆる “最新のLisp” の一つで、いくつか独自の機能を持っています。Clojureの強みはコア言語におけるJavaとの相互運用性や並行処理ユーティリティだと思います。Scalaの兄弟ですが少し毛色が違い、さしずめ「Lisp」対「OOPと関数型のハイブリッド」といったところです。ただClojureは括弧が多いためあまり人気がありません。どちらを採用するかは単に好みの問題でしょう。なぜなら、両方ともJVM上で動作するもののJavaやPHPと比べると歴史が浅く、長期にわたり本番稼働しているアプリの例が少ないため、実績に裏づけられた技術とは言いづらいからです。また、JVMベースの言語ですので、仮想マシンの起動時間も考慮すべきでしょう。小さなタスクをこなすだけのソリューションには適さないと思います。私は次のような状況でClojureを使います。

  • Web開発。いいオプションがあり、Clojureコミュニティでも、この分野の議論が活発に行われているようです。
  • Java系統の言語を使わずにJVMの技術を利用したい時。プログラマは気分よく仕事ができるので、生産性が向上します。
  • 試験的なプログラミングだが、実稼働のコードに移行する可能性がある場合。これこそLispの性質が光る分野ですが、ClojureはJavaのスタックに依存していて、多くのプロダクションコードを公開しています。
  • Android開発も? Android開発のGUI開発モデルはクラスの継承に大きく依存します(実際、プラグインのライブラリとしては使えず、強制的に特定の構造に従わされます)。あえて使うことはできますが、Javaの継承ほど自然にはいかないでしょう。

以下は、標準的なClojureコードです。

(defn divides? [k n] (= (rem n k) 0))

(defn prime? [n]
  (if (< n 2)
    false
    (empty? (filter #(divides? % n) (take-while #(<= (* % %) n) (range 2 n))))))

そしてLispの手法を使った単純なキュー定義です。

(defn make-queue []
  (atom []))

(defn enqueue [q x]
  (swap! q conj x))

(defn dequeue [q]
  (if (seq @q)
    (let [x (first @q)]
      (swap! q subvec 1)
      x)
    (throw (IllegalStateException. "Can't pop an empty queue."))))

(defn queue-empty? [q]
  (empty? @q))

D言語

私は昔、D言語のことが大好きでした。D言語はよくできたC++のようなものです。またD1については低水準化したPythonという印象でした。要するにPythonナイズしたCといったところです。本当にすばらしいのですよ。コードが読みやすいのでアルゴリズムの設計に集中でき、開発スピードが上がります。それに、必要な時に低水準な処理を制御することができます。D1の開発系として分離されたD2は、著名なC++プログラマであるアンドレイ・アレキサンドレスクによって革新的な修正を加えられ、C++に負けないくらい複雑になってしまいました。これはD2の並行処理能力をより高めるためでしたが、コミュニティの一部を失望させることになりました。D2はもはや簡潔な言語ではなく、テストされていない機能を多く含んだ実験的な言語になってしまったようです。それでもまだ私はDが好きですが、同じように複雑な言語であるC++が先行して普及していることを考えると、Dを使うべき利点は薄れていると思います。またGoがDにとって代わったのかな、とも思います。Dの最初の開発者であるウォルターやアンドレイが、非常にクールな機能を次から次へと実装したとしても、Googleを打ち負かすことはできないのでしょう。ひょっとしたらあなたも、私と同じようにDを気に入るかもしれません。でも、Dに輝かしい未来があるようには思えないのです。今まで通りC++を使い続けるか、より充実したネイティブ並行処理がサポートされているGoに乗り換えるべきでしょう。Dを使うとしたら、次のようなケースでしょうか。

  • プロジェクトをゼロから開発するに当たって、Cのインターフェースが使えて、場合によってはC++も使えるとうれしい場合。ただし事前に、これらのインターフェースが実際はどう連携していくのか検討しておくことは必要です。例えばC++のGUIライブラリを使う必要がでてくるようなら、Dの使用はお勧めできません。このライブラリを必要とするということは、たいていの場合C++の継承に依存するということになりますから、Dを使うメリットが一切なくなってしまいます。プラグインライブラリとしてC++を必要とする場合だけ(オブジェクトを作成し関数機能を使う場合。テンプレートや継承の使用はNG)、Dを使うことにしてください。
  • 高速バイナリで低水準のプログラミングが必要な場合。繰り返しますが、いっそスタンドアローンのプログラムのようにDを使うことをお勧めします。
  • 優れた並行処理がネイティブサポートされている言語を使いたい場合。

D2の特徴的な例をいくつか見てみましょう。単純な関数の処理とイミュータブルを宣言する処理です。

uint grayEncode(in uint n) pure nothrow {
    return n ^ (n >> 1);
}

uint grayDecode(uint n) pure nothrow {
    auto p = n;
    while (n >>= 1)
        p ^= n;
    return p;
}

void main() {
    import std.stdio;

    " N     N2      enc     dec2 dec".writeln;
    foreach (immutable n; 0 .. 32) {
        immutable g = n.grayEncode;
        immutable d = g.grayDecode;
        writefln("%2d: %5b => %5b => %5b: %2d", n, n, g, d, d);
        assert(d == n);
    }
}

ちなみにこれは、リストの最大要素を返すコードです。

[9, 4, 3, 8, 5].reduce!max.writeln;

どう見ても、C++よりずっと分かりやすく簡潔ですよね。

Erlang

Erlangは、目的が非常に明確な言語です。 Erlangの公式ホームページ には、目的が大変はっきりと書かれています:(Erlangは)高いアベイラビリティが要求される大規模リアルタイムシステムにも対応できるソフトウェアを構築できます。電話通信、金融、eコマース、コンピュータテクノロジー、そしてインスタントメッセージ等のシステムに用いられています。Erlangのランタイムシステムは、並行処理、分散処理、そして耐障害性などをビルトインサポートしています。Erlangは充分に有効性が証明されており、WhatsAppのような非常に要求度の高いアプリにも採用されています。コード自体はまさに関数型言語といった感じで、構文は簡潔でとても読みやすいです。
それでは、簡単な並列処理プログラムのコードを見てみましょう。

-module(hw).
-export([start/0]).

start() ->
   [ spawn(fun() ->  say(self(), X) end) || X <- ['Enjoy', 'Rosetta', 'Code'] ],
   wait(2),
   ok.

say(Pid,Str) ->
   io:fwrite("~s~n",[Str]),
   Pid ! done.

wait(N) ->
   receive
       done -> case N of
           0 -> 0;
           _N -> wait(N-1)
       end
   end.

Go言語

私自信はこの言語を使ったことがありません。まだ、という意味です。それでも、Go言語がGoogleの言語であるということ、CにC++のいいところを加えて作られたということ、そして並行処理のサポートに関してはCおよびC++双方よりも優れているということは間違いありません。C++の上を行く機能を兼ね備えていて、しかもずっとシンプルです。また、ポインタ演算やクロージャ、第一級関数、ガベージコレクションといった機能の中に、非推奨なものはありません。Goは近い将来、サーバサイドの言語になり得るかもしれませんね。では、いったいいつ私はGoに挑戦するべきなのでしょう?

  • Webアプリのように、大変高い信頼性とパフォーマンスを必要とするサーバアプリケーションを構築する時
  • 低水準でのコントロールが必要とされる高次の並行処理コードを書く必要がある時(でなければ、私はErlangを選ぶでしょう)

Goの並行処理コードは以下のとおりです。

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    words := []string{"Enjoy", "Rosetta", "Code"}
    rand.Seed(time.Now().UnixNano())
    q := make(chan string)
    for _, w := range words {
        go func(w string) {
            time.Sleep(time.Duration(rand.Int63n(1e9)))
            q <- w
        }(w)
    }
    for i := 0; i < len(words); i++ {
        fmt.Println(<-q)
    }
}

Haskell

このHaskellという言語は、今回取り上げた言語の中では最も考え抜かれた高度なツールではないかと感じています。おおよそどんなニーズにも対応できるライブラリを有しており、ハードコアなコミュニティが存在します。敷居が高く、とっつきにくい言語であることは間違いありませんが、それでも私はHaskellがあなたの思考を押し広げ、プログラミング言語コミュニティに参加しているすばらしい頭脳を持った開発者たちに引き合わせてくれるはずだと考えます。

実際にプログラムを組まなくても、Haskellを学ぶ価値は充分にあると思います。他の言語と比べると無名ではありますが、私はHaskellを”妥当な言語”に分類しました。実際、Haskellは さまざまな分野 、特に金融業界でよく採用されているのです。Haskellは逐次型というより概念的な処理を行う関数を多く必要とするので、多少抽象的ではあります。しかし、それにもかかわらずHaskellのコードはたいていは非常にコンパクトで分かりやすいのです。個人的には、Haskellのシンタックスはあまり気に入っていません(構文が多すぎます)が、少なくともそれぞれの目的は明確なので、混乱しているようには見えません(Perlとは大違いですね)。美しく、一貫性のある言語です。実際に下のコードで確認してみてください。

binarySearch :: Integral a => (a -> Ordering) -> (a, a) -> Maybe a
binarySearch p (low,high)
  | high < low = Nothing
  | otherwise =
      let mid = (low + high) `div` 2 in
      case p mid of
        LT -> binarySearch p (low, mid-1)
        GT -> binarySearch p (mid+1, high)
        EQ -> Just mid

Java

C#と非常に似ていますが、Java 仮想マシンで実行される言語です。オブジェクト指向をとった言語の先駆けであり(実際C#はJavaのコピーから始まりました)、業界ではオブジェクト指向の”標準言語”とみなされています。Webアプリから ゲーム にいたるまで、あらゆる用途でJavaが使われています。使われていないのは、組み込み型のデバイスソフトウェアか、ハイパフォーマンスを要求される並列コンピューティングのソフトウェアぐらいではないでしょうか。Javaはまた、他の多くの言語の基礎ともなっています(特に仮想マシンにおいて)。 Processingの公式ホームページ には、このラッパー言語(砂糖でコーティングしたJavaのようなものです)が教育やデジタルアートの分野でどのように使われているのかを示す、興味深いプロジェクトが紹介されています。実際は、次のような場面でJavaをお勧めします。

  • 多くの開発者やナレッジベースにアクセスしたい場合。つまり、ソフトウェアの保守を外部に依頼する場合
  • できるだけ多くのデバイスにマルチプラットフォームの仮想マシンが必要な場合

以下は、Java 7のファイルを読み込むコードです。

import java.util.List;
import java.nio.charset.Charset;
import java.nio.file.*;

public class ReadAll {
    public static List<String> readAllLines(String filesname){
        Path file = Paths.get(filename);
        return Files.readAllLines(file, Charset.defaultCharset());
    }

   public static byte[] readAllBytes(String filename){
       Path file = Paths.get(filename);
       return Files.readAllBytes(file);
   }
}

Javaのコードはよくご存知だと思うので、クラス定義まで踏み込んで皆さんを煩わせるつもりはありません。

JavaScript

2010年代の共通語であり、Webのための言語です。面白いことに、以前は欠陥だらけで質の悪い言語とされていたにも関わらず、最近では、一連のベストプラクティスに従い、さまざまなテクニックを駆使すれば、JavaScriptが実は優れた言語となることが証明されてきました。そのライブラリ群や実装手法は、JavaScriptの設計ミスや足りない機能(モジュールシステムなど)を埋め合わせて余りあるものです。このおかげで、サーバサイドでのJavaScriptの利用が可能になり、ついに美しく調和のとれたバックエンド/フロントエンドが実現しました。

JavaScriptのパフォーマンスやJavaScriptにコンパイルする派生言語を改良するために、多くの研究と取り組みがなされています。これはまさに、コミュニティが言語にとって最大の資産の一つ(もちろん”唯一の”ではない)であることを証明していると言えるでしょう。面白いのは、同じような処理を行うライブラリが無数に存在するため、ライブラリ開発者にとっては最も競争の激しい言語だということです。例えばGruntとGulpを比較してみてください。あるいは山ほどあるJavaScriptの派生言語(Coffeescript、Typescript、Livescriptなど)でもいいです。クレイジーですよね。

以下のコードは、JavaScriptのプロトタイプベースのクラスと継承の例です。

function Car(brand, weight) {
  this.brand = brand;
  this.weight = weight || 1000;
}
Car.prototype.getPrice = function() {
  return this.price;
}

function Truck(brand, size) {
  this.constructor(brand, 2000);
  this.size = size;
}
Truck.prototype = new Car;

var cars = [
  new Car("Mazda"),
  new Truck("Volvo", 2)
];

for (var i=0; i<cars.length; i++) {
    console.log(cars[i]);
    console.log("brand: "+cars[i].brand+". weight: "+cars[i].weight+"." + (( cars[i].hasOwnProperty('size') ) ? " size: "+cars[i].size : ""));
    console.log("Car: %s. Truck: %s\n", cars[i] instanceof Car, cars[i] instanceof Truck);
}

こちらは、Lo-dashライブラリを使った関数型のコードです:

var characters = [
  { 'name': 'barney',  'age': 36 },
  { 'name': 'fred',    'age': 40 },
  { 'name': 'pebbles', 'age': 1 }
];

var youngest = _.chain(characters)
    .sortBy('age')
    .map(function(chr) { return chr.name + ' is ' + chr.age; })
    .first()
    .value();

JavaScriptの特殊性(例えば”this”の使い方など)を理解し始めたら、上記2つのスタイルを簡単にミックスできて、かなりいい感じになると思います。

OCaml

Haskellに似ていますが、プログラマの要求により柔軟に応えてくれる言語だと思います。例えば手続き型/オブジェクト指向のアプローチがベストだと思われる場合などは、より簡単なソリューションを実現するために、その純度を多少妥協することができます。OCamlを採用した 企業 は、この点がHaskellより優れていると判断したのではないでしょうか。以下のコードを見てください:

let n_arrays_iter ~f = function
  | [] -> ()
  | x::xs as al ->
      let len = Array.length x in
      let b = List.for_all (fun a -> Array.length a = len) xs in
      if not b then invalid_arg "n_arrays_iter: arrays of different length";
      for i = 0 to pred len do
        let ai = List.map (fun a -> a.(i)) al in
        f ai
      done

Haskellによく似ているでしょう? でもforループのあたりに、命令型の匂いがします。

PHP

PHPを最悪な言語だと決めつけないでください。ひたすら真面目に取り組めば、楽しいPHPの世界にどっぷり浸かることができます。いい点は、PHPでのプログラミングを楽しめるようになれば、真のプログラマになれるということでしょうか。またPHPはフリーのプログラマが受けるような安価な仕事で使われる傾向があります。私がPHPを使うとしたら、次のような場合です。

  • 多くのWeb開発者を使いたい場合
  • 以上。他にはありません

PHPの簡単なコードです。ドルがお好きだといいのですが。

function hashJoin($table1, $index1, $table2, $index2) {
    foreach ($table1 as $s)
        $h[$s[$index1]][] = $s;
    foreach ($table2 as $r)
      foreach ($h[$r[$index2]] as $s)
        $result[] = array($s, $r);
    return $result;
}

$table1 = array(array(27, "Jonah"),
           array(18, "Popeye"),
           array(28, "Alan"));
$table2 = array(array("Jonah", "Whales"),
           array("Jonah", "Spiders"),
           array("Alan", "Ghosts"),
           array("Bob", "foo"));

foreach (hashJoin($table1, 1, $table2, 0) as $row)
    print_r($row);

Python

美しい言語です。私は特にブロック構造をインデントで示すところが気に入っています。いちいち面倒くさいセミコロンをつけなくてすみますからね。あまり気に入っているので、JavaScriptを書く時もこの方法で書いてしまうほどです。しかしこれは完全に好みの問題で、実際そのせいでPythonが嫌いだという人は大勢います。コードが読みやすく、シンタックスという負担から開発者をできる限り解放すべく設計されています。それが成功しているかどうかは議論の余地がありますが、この言語がすばらしいコミュニティに支えられているのは確かで、そのおかげで仲間のRubyよりも確固たる地位を築いています。この2つの言語のどちらを選択するかは常に難しい決断です。ただPythonの方が広く普及していているので、分野やアプリケーションの種類を選ばないという意味では、より理にかなった選択と言えるかもしれません。私がPythonを使うのは次のような場合です。

  • Web開発
  • 科学的計算やデータ分析
  • システム管理とツール
  • ゲーム/3Dアプリケーションのスクリプティング
  • クロスプラットフォーム対応

洗練されたPythonのコードです。

from itertools import islice

def hamming2():
    '''\
    A text documenting this function (stripped)
    '''
    h = 1
    _h=[h]    # memoized
    multipliers  = (2, 3, 5)
    multindeces  = [0 for i in multipliers] # index into _h for multipliers
    multvalues   = [x * _h[i] for x,i in zip(multipliers, multindeces)]
    yield h
    while True:
        h = min(multvalues)
        _h.append(h)
        for (n,(v,x,i)) in enumerate(zip(multvalues, multipliers, multindeces)):
            if v == h:
                i += 1
                multindeces[n] = i
                multvalues[n]  = x * _h[i]
        # cap the memoization
        mini = min(multindeces)
        if mini >= 1000:
            del _h[:mini]
            multindeces = [i - mini for i in multindeces]
        #
        yield h

Ruby

Ruby on Rails。Rubyがこのリストに名を連ねた理由は、これがすべてです。もちろん今では多くのプロジェクトでRubyが使われていますが、それもこれもRailsが誕生してからのことです。それ以前は、Rubyは日本発の無名のプログラミング言語にすぎませんでした。これは、「この手の言語はこれで決まり」と思われているところに、別の言語のキラーアプリケーション/フレームワークが登場して、その言語の人気を高めていくという完ぺきな例です。そのプロセスの中で優れたコミュニティが生まれ、さらに優れたキラーアプリケーション/フレームワークが作られていくのです。

多くのRuby開発者から聞かされることであり、私自身も経験のあることですが、この言語のいいところは、その使い勝手にあります。言い換えると、これはイライラする言語の対極にある言語なのです。これがこの言語自体の特徴なのかRailsのおかげなのかは分かりませんが。 metasploit の開発者たちは、 当初から この長所を明確に感じていたようです。

以下はPythonでお見せしたものと同じアルゴリズムをRubyで書いたものです。異なるアプローチをとっていて、Rubyの方がわずかに関数型寄りであることが分かります。

hamming = Enumerator.new do |yielder|
  next_ham = 1
  queues = { 2 => [], 3 => [], 5 => [] }

  loop do
    yielder << next_ham   # or: yielder.yield(next_ham)

    [2,3,5].each {|m| queues[m]<< (next_ham * m)}
    next_ham = [2,3,5].collect {|m| queues[m][0]}.min
    [2,3,5].each {|m| queues[m].shift if queues[m][0]== next_ham}
  end
end

idx = 1
hamming.each do |ham|
  case idx
  when (1..20), 1691
    p [idx, ham]
  when 1_000_000
    p [idx, ham]
    break
  end
  idx += 1
end

(訳者注)この記事は途中までの翻訳になります。ScalaやPerlを含めたさらに多くの言語の紹介は コチラ からどうぞ