Uploaded avatar of iHiD

Sexpsの夏です!

@iHiD
3年以上前
動画

はじめに

こんにちは! みなさん、お元気ですか。

Exercismでは、Exercism PremiumとExercism Insidersのリリースもあり、この数週間は本当にわくわくする日々が続いています。素晴らしいコミュニティコールもいくつかあり、サイトには多くの改善がデプロイされ、さらに今後も登場する予定です。今は楽しみなことがたくさんありますが、何よりわくわくするのは、#12in23の6か月目に突入したことです! それがS式の夏、より親しみやすく縮めて「Summer of Sexps」です。

いつも通り、プログラミング界の賢者であるエリクが参加してくれています。

今月は5つの言語を扱います。Clojure、Common Lisp、Emacs Lisp、Racket、Schemeです。これらの言語はどれもLispの方言なので、この動画では個々の言語に深く焦点を当てるのではなく、Lisp自体と、それをユニークにしているものについてもう少し見ていきます。そして最後に、それぞれの言語を簡単に紹介します。

その前に、いくつかお知らせです! Summer of Sexpsバッジを獲得するには、6月中にこれらの言語のいずれか1つで、好きな演習を5つ完了する必要があります。

バッジ

1年を通しての12in23バッジもあります。これを獲得するには、その言語で特集されている演習から5つ解く必要があります。6月より後にこの動画を見ている場合でも、この部分は年内いつでも取り組めるので、機会を逃したわけではありません。多くの人はLispに一度も触れたことがないだろうということで、Lisp言語がどんなものかを味わえる、比較的シンプルな演習を選ぶようにしました。

  • Leap: 真偽値の条件と、値が真とみなされるかどうかを扱います(任意でレキシカルスコープも)
  • Two-Fer: 文字列をフォーマットし、省略可能な入力を扱います
  • Difference of Squares: ユーザー定義の関数を呼び出し、前置記法で計算します
  • Robot Name: 乱数、アトム、構造化データを扱います
  • Matching Brackets: 再帰を使って文字列を検証します

これらの演習や過去数か月分の演習は、すべて#12in23のページで見つけられます。

各言語の概要

それでは、Lisp系の言語です。まずはLispについて少し理解することから始めましょう。Lisp全般について少し紹介するところから始めます。

Lisp

  • まず最初に押さえておきたいのは、Lispが最も古い言語のひとつだということです。

  • 1958年にMITのジョン・マッカーシーによって作られました。当時は、コンピューターがまだ部屋を丸ごと占めるほどだった時代です 🙂

  • Lispという名前はLISt Processing(またはLISt Processor)の略で、リストというデータ構造の重要性を表しています。

  • AI研究を行う目的で設計されました。

  • Lispは、アロンゾ・チャーチが発明したラムダ計算に基づいています。ラムダ計算は、(かみくだいて言えば)数学における計算を記述するための形式体系です。

  • Lispは、さまざまな理由から、信じられないほど影響力の大きい言語です。

  • 現在も広く使われている高水準プログラミング言語の中では2番目に古い言語です(Fortranの次)。

  • 最初の高水準関数型プログラミング言語であり、今では関数型プログラミングといえば連想する多くの機能を生み出しました。

  • ただし、Lispは命令型プログラミングもサポートしていたことに注意してください。

  • ガベージコレクターを備えた最初の言語で、手動でのメモリ管理から書き手を解放しました。

  • 構文が比較的少なく、意味論も比較的シンプルなので、教育用途にうってつけです。

  • そのため、Lisp(正確にはその方言のひとつ)はプログラミングの教育によく使われます。

  • 非常に多くの言語の方言を生み出しました(そして今も生み出し続けています)。そのうちExercismでサポートされているものについては後ほど触れます。

  • 言い換えれば、プログラミング言語の樹形図には、Lisp系言語のための枝が別にあります(C系言語の枝があるのと同じように)。

先日、Haskellの作者のひとりであるサイモン・ペイトン・ジョーンズと話をしたのですが、そのとき彼は、チューリングマシンを中心に据えた言語と、ラムダ計算を中心に据えた言語の違いについて語っていました。このあたりをもっと知りたい方は、そのインタビューをチェックしてみる価値があります。

括弧

Lispには丸括弧(())がたくさん登場しますが、それは必ずしも悪いことではありません(C系言語で波括弧({})が多いのが必ずしも悪くないのと同じです)。 Lispは、S式と呼ばれるものをもとにしています。 S式(symbolic expressionの略で、sexprやsexpと略されます。今月のチャレンジの名前もここから来ています)は、データを表すための式です。これは元祖Lisp言語のために発明され、広く知られるようになりました。 S式は、次の2つの形のどちらかです。

  • アトム(例:'x')。入れ子になっていない「値」、あるいは木構造の葉だと考えてください。
  • 式 x . y。ここでxとyはS式です。これはペアだと考えてください。yは(もしあれば)リストの次の要素、あるいは木構造のノードにあたります。これは再帰的な定義で、葉のレベルで終わることに注意してください。通常、このタイプのS式には括弧が使われます。

S式

S式は、Lispでデータとリストの両方を表すのに使われます。 そのため、リストを定義するときはいつでも括弧を使います。 これと、次の事実を組み合わせると: リストはLispの核となるデータ構造であり(名前もそこから来ています)、 Lispによっては唯一のデータ構造でもあり、 その結果、たくさんの括弧を書くことになります。 リストがどれほど核であるかを示す例として、Lispで関数を呼び出したいときは、リストを作ることでそれを行います。

おもしろいことに、リストの最初の要素(つまりhead)が呼び出される関数を表し、残りの要素(つまりtail)が引数として渡されます。 これは前置記法(演算子が被演算子よりも前に来る書き方)と呼ばれ、最初は少し奇妙に感じるかもしれませんが、実はとても便利です。

  • 演算子を繰り返さずに、複数の引数へ演算子を適用できます(例:(+ 1 2 3))
  • 別の演算子を呼び出すにはどのみち新しいS式を定義する必要があるため、演算子の優先順位が明示的になります

おもしろいことに、リストはソースコードを表すのにも使われますが、これについては後ほどまた触れます。

一般に、ほとんどのLispは構文がとても少なく、意味論も比較的シンプルなので、比較的学びやすく、コードの理解も容易になります。 この最小限の構文は、決して力が弱いという意味ではありません! これら2つ(最小限の構文とシンプルな意味論)が組み合わさることで、Lispはコンパイラーやインタプリターを書くのに理想的な言語になります。 いつか自分のコンパイラーを作りたくなったら、Lispを作るのは良い選択です!

Lispの魅力的な機能

前述のとおり、Lispはコードを表すのに、内部で同じデータ型とデータ構造を使います。 この性質はホモイコニシティ(またはホモイコニック)と呼ばれます。 言い換えれば、ある言語で書かれたプログラムを、その言語自身を使ってデータとして操作できるとき、その言語はホモイコニックであるといえます。そして、プログラムを読むだけでその内部表現を推測できます。 この性質は、その言語がコードをデータとして扱う、とよく要約されます。

各言語

Scheme

  • 1970年代に、MIT AI Labのガイ・スティールとジェラルド・サスマンによって作られました。
  • カール・ヒューイットのアクターモデルを、小さなLispインタプリターを通じて理解しようとした試みとして始まりました。
  • 言語自体は、一連の研究用AIメモで紹介され、それらはまとめてLambda Papersと呼ばれるようになりました。
  • レキシカルスコープ(値がスコープに入るのは定義された場所だけ)を採用した最初のLisp方言で、第一級継続をサポートした最初期の言語のひとつでもあります。
  • IEEEの公式標準があり、また『Revised Report on the Algorithmic Language Scheme』(RnRS)という事実上の標準もあります。
  • 多くの実装があります。ChezScheme、Guile(どちらもExercismでサポートされています)、MIT/GNU Scheme、Racketです。
  • 非常にミニマルな言語で、構文もごくわずかですが、それは意図したものではありませんでした。
  • 作者たちは複雑なものを作ろうとしましたが、結局、当初の意図よりずっとシンプルなものを設計することになりました。
  • 正しい末尾再帰。繰り返しを行う際の慣用的な方法は、再帰を用いることです。
  • Schemeは末尾再帰呼び出しを最適化し、スタック領域やその他のリソースを消費しないようにします。つまり、どれだけ大きなデータでも、どれだけ長い計算でも、再帰を使えます。
  • 有理数や複素数を含む、強力な数値データ型。
  • 遅延評価。プロミスのようなものです。
  • 強力なマクロシステム。
  • 衛生的マクロは、マクロを定義するときに予期しない結果が起きる可能性を減らします。

Common Lisp

  • Common Lispの作業は、ARPAのマネージャーだったボブ・エンゲルモアの呼びかけにより1981年に始まりました。当時使われていたさまざまな方言はしばしば互換性がなく、コードや知識を共有できないことが多かったため、コミュニティ共通の単一のLisp方言を開発することが目的でした。
  • 最初の標準は1984年に、最終版は1994年に公開されました(非常に安定した仕様です)。
  • 標準であるため、この標準には複数の実装があります。たとえばSteel Bank Common Lisp(Exercismのデフォルトです)やCLispなどです。
  • 商用実装もあり、たとえばAllegro CLやLispWorksがあります。また、Cプログラムに埋め込めるECL(Embeddable Common Lisp)や、Java仮想マシン上で動くABCLもあります。
  • 標準(ANSI INCITS 226-1994)で定義されているので、30年前に書かれたコードも今なお問題なく動きます。
  • 豊かで拡張可能な型システム。
  • イメージとREPLでの開発を想定して設計されているので、非常に内省しやすいです。

Emacs Lisp

  • 1985年に、テキストエディターを拡張するための効率的な言語として開発されました。
  • 動的型付け。
  • Emacsの約80%はEmacs Lispで書かれています(残りの20%は性能のためにCで書かれています)。
  • 他のLispとは少し違います。
  • 標準化されておらず、今もゆっくりと進化し続けています。
  • 末尾呼び出しの自動的な除去はありませんが、named-letマクロでサポートされます(whileループに変換されます)。
  • デフォルトでは動的スコープで、新しいコードではレキシカルスコープが推奨されます。
  • エディター内に充実したドキュメントがあります。
  • マルチプラットフォーム(Emacsが動くところならどこでも動きます)。
  • 日々使っている機能のコードを読むことで言語を学べます(Emacs本体とパッケージ)。
  • cl-libパッケージを通じて、Common Lispのサブセットが利用できます。Emacs Lispはかなりミニマルですが、Common Lispはずっと高機能です。cl-libパッケージは、CLのサブセットを利用できるようにします。

Racket

  • マティアス・フェライゼンがPLT Inc.を設立し、1995年1月にSchemeをベースにした教育用のプログラミング環境を開発することを決めました。当初はPLT Schemeという名前でしたが、後にRacketへ改名されました。
  • 教育用のプログラミング環境であるだけでなく、プログラミング言語の設計と実装のためのプラットフォームとしても設計されました。
  • モダンなLISPで、Schemeの子孫です。
  • 論理プログラミングもサポートしています!
  • シンプルで表現力のある構文は、初心者にとって理想的であり、熟練者の手のなかでは強力です。
  • 多くのプログラミングパラダイムをサポートしています。関数型プログラミング、オブジェクト指向プログラミング、契約による設計、論理プログラミング、メタプログラミングです。
  • 包括的な標準ライブラリ。
  • 学習と探索のために設計された、手間のかからない本格的なIDEであるDrRacketが付属しています。
  • 豊富な背景情報と例を備えた、優れたドキュメント。

Clojure

  • リッチ・ヒッキーによって、JVM上で動き、優れた並行性を備えたモダンなLISPを目指して開発されました。
  • LISPの方言ですが、暗黙の末尾再帰をサポートしていない点や、リスト以外にもマップ、セット、ベクターといったより多くのデータ構造を持つ点で、他のLISPとは少し異なります(これが何かわからなくても心配いりません)。これらのデータ構造には、それぞれ独自のリテラル構文があります。
  • それらはすべてイミュータブルですが、O(log32 n)の探索という「実質的に」定数時間の優れた性能を持っています。
  • マルチメソッドとプロトコルによる実行時ポリモーフィズム。
  • 優れたJVM相互運用。
  • Clojure Specはデータ仕様システムで(コンパイル時ではなく実行時)、データの構造を定義したり、データを生成したり、プロパティベースのテストを行ったりできます。

ユースケース

Scheme

  • コンピューターサイエンスの教育に使われています(影響力の大きい『Structure and Interpretation of Computer Programs』はSchemeを使っています)。
  • AIで使われています。スクリプト言語としても使われており、たとえばGIMP(グラフィックエディター)、CADツール(Computer Aided Design)、さらには映画でも、"Final Fantasy: The Spirit Within"のレンダリングエンジンの管理スクリプトに使われました。

Common Lisp

  • Common Lispは多くの場面で使われています。たとえば人工知能や研究の分野ですが、商用アプリケーションでも使われています。NASAは宇宙船Deep Space Oneの自動操縦ソフトウェアをCommon Lispで書き、ViawebはCommon Lispで書かれ、後にYahooに買収されてYahoo Store!としてブランド変更されました。また、Redditの最初のバージョンもCommon Lispでした。

Emacs Lisp

  • Emacs Lispは、そう、Emacsで使われています!
  • 根っこでは、EmacsはEmacs Lispのインタプリターです。Emacs LispはLispプログラミング言語の方言ですが、テキスト編集をサポートするための拡張が加えられています。

Racket

  • 教育で使われています。Racketは、言語の作成、単純化、分析を支援することに重点を置いて設計されたからです。
  • 研究でも使われています。拡張可能な構文と意味論のおかげで、新しい言語や言語機能の設計とプロトタイピングに適しているからです。
  • ゲームでも使われています。たとえば(『Doom』で知られる)ジョン・カーマックがVR向けのインタラクティブなスクリプト環境に使い、開発元のNaughty Dogはスクリプト(たとえば『Uncharted』)に使いました。Hacker NewsはArcで書かれていますが、ArcもまたLispであり、それ自体がRacketで書かれています。

Clojure

  • Clojureはさまざまなことに使われています。たとえば、2022年にDockerがAtomistを買収しましたが、これはClojureで実装されたコンテナセキュリティと自動化のプラットフォームです。
  • 世界最大のClojureユーザーは、新しい銀行であるNubankです。Nubankは数年前にそれを買収し、現在はClojureのコアチームを雇用しています。
  • 動的で非常にインタラクティブなので、迅速なプロトタイピングに多用されています。

プログラミングの観点

どの言語も、関数型、命令型、シンボリックの各パラダイムをサポートしています。 なかにはOOPもサポートするものもあり、とくにCommon Lispが代表的です。

Lispはおおむね動的な言語ですが、Racketは静的型付けもサポートしています。

ただし、すべてがインタプリターで実行されるわけではありません。インタプリター方式(コンパイルの段階を挟まない)、バイトコードにコンパイルしてからインタプリターで実行する方式、マシンコードへ直接コンパイルする方式が混在しています。

Scheme

  • ミニマルで、明確でシンプルな意味論を持ち、式を組み立てる方法もごくわずかです。
  • 言語を学び、コードを理解するのが容易になります。
  • そのため、Schemeは多くのコンピューターサイエンス入門コースでもよく使われます。
  • 第一級継続。
  • 継続とは、プログラムの状態を表したものです。
  • 継続は、制御フロー(たとえばreturn構文)やコルーチン(マルチタスクを可能にします)をモデル化するのに使えます。

Common Lisp

  • プログラマブルなメソッド結合を備えた拡張可能なオブジェクト指向システム(サブクラス/スーパークラスのメソッドをどのように結合するか、またbefore・after・aroundメソッドによって、システムを変更せずに拡張できるようにするか、その両方)。
  • プログラマブルなコンディションシステム(『例外』の上位集合)で、コンディションの認識と、それをどう扱うかの選択を切り離せます。コンディションシステムは例外システムよりも柔軟です。エラーを通知するコードと、それを処理するコードという2つの部分に分ける代わりに、コンディションの通知、処理、再開という3つの責務に分けるからです。
  • マクロは、定型コードを生成するだけでなく、言語の構文を拡張できます。これにより、ドメインを言語に合わせるのではなく、ドメインに合った言語を構築しやすくなります。

Emacs Lisp

  • 優れたエディターサポートと統合。
  • Emacsの実行中にカスタマイズできます(「自分で自分の脳を手術するようなもの」 :))。
  • バッチモードでも使え、そのモードではエディターのテキスト処理機能(バッファーや移動コマンドなど)をすべて利用できます。

Racket

  • 強力なマクロシステム。スレッディングマクロのような糖衣構文もこれをもとに作られています。マクロは衛生的でもあり、それは次のシンプルな問いへの答えです。マクロは、別の場所に配置されるコードを生成します。そのコードが評価されるとき、内部の識別子の束縛はどう決めればよいのでしょうか? 衛生的マクロは、マクロを定義するときに予期しない結果が起きる可能性を減らします。
  • 言語指向。
  • Racketには、Racketのマクロを土台にして、自分だけのプログラミング言語やDSLを書くためのツールが付属しています。
  • 組み込みの言語もいくつかあります。たとえばtyped Racket(静的に検査される型注釈をサポート)、datalog(Prologに似た言語。DrRacket IDEがサポートしています)、そしてHTMLやPDF形式の文書を作成するためのツール、scribbleです。
  • REPLは開発ワークフローの核であり、単に試したりドキュメントを調べたりするだけのものではありません。

Clojure

  • 強力なマクロシステム。
  • スレッディングマクロのような糖衣構文もこれをもとに作られています。
  • REPLは開発ワークフローの核であり、単に試したりドキュメントを調べたりするだけのものではありません。

どれを試すべきか

  • Lispに一度も触れたことがないなら、SchemeとRacketが良い選択です。どちらも構文がとても少ないからです。
  • とはいえ、Common LispとClojureにはLearning Modeがあるので、Exercismで学ぶならおそらくこの2つが最適です。
  • すでにEmacsを使っているなら、Emacs Lispは自然な選択です。
  • 同様に、JVM言語を使っているなら、Clojureは自然な選択肢です。
  • Emacs Lisp(Emacs経由)、Clojure(IntelliJ経由)、Racket(DrRacket経由)はどれも優れたIDEサポートがあります。
  • もちろん、Common LispとSchemeにも優れたIDEがあります。
  • 本当に高機能なLispが欲しいなら、Common Lisp、Clojure、Racketは非常に充実しています。
  • 少し変わったLispが欲しいなら、ClojureはLispとしてはかなり独特な構文を持っています。
  • マクロやメタプログラミングに興味があるなら、基本的にどれも良い選択です! ただし、新しい言語を作りたいなら、とくにRacketが最適です。

もちろん、時間があるなら、いくつか試してみることをおすすめします! また、括弧を恐れないでください! 私自身も以前はそうで、だからこそLispの学習をずいぶん長いこと後回しにしていました。 でもすぐに慣れますし、私のように、いつかその良さがわかるようになるかもしれません。 実際、今の私はLisp言語が大好きです。最小限の構文、やさしい意味論、それでいて非常に表現豊かです。

2023年06月01日(木) · 役に立ちましたか?