Open10

Rustベースで開発されているRoc言語について調べてみた #1

eisukeeisuke

きっかけ

最近は色んな技術にあっちこっちに手を出してしまい浅い人間になってしまいそうで怖く感じていました。ある日ふと暇つぶしにYoutubeを開いていたら希望に満ち溢れたおじさんのサムネを見て興味のまま見てみました。

内容

  • サンドボックス上だったらI/Oプリミティブが操作できる
  • メモリ管理が楽
  • Roc言語専用のエディタも制作している
    • 参照先の情報が分かりやすいようなものにしたい(画像はゲームを作っているとき)
  • 質問
    • elm言語に触発されている
    • JSの過小評価されている部分を取り入れたい
eisukeeisuke

公式ドキュメントを軽くまとめ

https://www.roc-lang.org/

はじめに

  • 高速で使いやすく機能的な言語になることを目指している

  • 絶賛開発中

  • ウェブサイトは言語が言って水準まで達したら洗練する

  • Rocはmachineコード、WebAssemblyにコンパイルされる

  • 最終的には高品質のサーバー、コマンドライン アプリケーション、グラフィカル ネイティブ デスクトップ ユーザー インターフェイスなどのアプリケーションを構築できるようにすること。

  • 現在はコマンドライン インターフェイスのみである。

  • Roc言語

  • 自動メモリ管理に仮想マシン不要

  • C言語関数の呼び出し

    • 任意の言語からRoc関数を直接呼び出し可能
    • プラグイン実装に適した言語
    • レガシーコードベースの段階的な移行
    • 他言語からRocへの移行方法提供
  • 概念の実証段階は終わっている

  • バグや実装されていないものが多々ある

  • ドキュメントも不完全

  • 開発が進むまでRocに依存したプロジェクトはまだ作らない方がよい

速い言語を目指している

  • 業界で主流に使用されている非システム言語 (C、C++、Rust、Zig など) よりも高速にする

  • JavaやPythonなどような言語のようにがーべじコレクションの仕組みを導入せずに自動メモリ管理を行う

  • Rocは、アンボックスされたデータ構造やアンボックスされたクロージャを使用

  • コンパイラのバックエンドとしてLLVMを使用

  • アンボックスされたクロージャとモノモーフィズム化は、C++やRustなどのシステムレベルの言語で見られる最適化

  • 主流のガーベジコレクション言語にはこれらの最適化が存在しない

  • Rocクロージャは、ガーベジコレクション言語のクロージャ(通常はボックス化されている)と同じくらい使いやすく、システム言語のクロージャ(通常はアンボックス化されているが、型がより複雑)と同等の性能を持つという特徴がある

  • C、C++、Zig、Rust などの言語よりも厳密に Roc の方が実行時のオーバーヘッドが大きい場合もある

  • 最もコストがかかるのは、Roc が自動参照カウントを使用して実装する自動メモリ管理

  • 省略や再利用 ( Morphic と Perceusのおかげ) などの静的な参照カウントの最適化を行ってはいる

  • オーバーヘッドを完全になくすには、他の設計目標を犠牲にする必要があり

    • 配列境界チェック
    • 循環参照の禁止
    • メモリアンセーフな操作の導入
    • コンパイル時のライフタイムエラー
    • 自動的な機会主義的なインプレース変更の導入(直接変更の代わりに)
  • これらのオーバーヘッドの原因がすべて完全に排除されたとしても、典型的な Roc プログラムのパフォーマンスが特に大幅に向上するとは考えにくいらしい

  • RocはC++と同様のパフォーマンスになると予想している
    以下の動画ではRocの最適化による初期の結果について説明してる。
    https://youtu.be/vzfy4EKwG_Y

親しみやすい言語

  • フレンドリーなユーザー コミュニティを持つユーザー フレンドリーな言語を目指しているようだ。(情熱さえあれば僕みたいなよわよわでも受け入れてもらえるかも)

  • プログラミング言語は、ソフトウェアを作成するためのツール以上のものになる可能性がある。また、経験を共有することで人々が集まり、互いに教え、学び合い、新しい友達を作る方法にもなり得る。(より英語を苦なく使えるようになりたいと思った。)

  • 互いが親切を示し参加するのが楽しくなるようなコミュニティしたい

  • 特に初心者に対して、また他のプログラミング言語を好む人々に対しても、親しみやすさを感じられるようなものにしたい

  • 技術レベルでは、Roc は使いやすさが最優先されるツールセットを出荷することを目指している

  • Elmという言語並みにエラーメッセージの質を高める

    • わかりやすさ:エラーメッセージは明確で、具体的な説明が含まれており、開発者が問題を理解しやすくなっています。
    • コードの箇所を示す:エラーが発生したコードの箇所を正確に指摘し、修正が必要な場所を開発者に示してくれます。
    • 解決策を提案:エラーメッセージは、問題の原因となるコードの修正方法やアプローチに関する具体的な提案を含んでおり、開発者が問題を迅速に解決できるようサポートします。

https://elm-lang.org/

  • Roc はまた、コンパイラだけでなく、 REPL、パッケージ マネージャー、テスト ランナー、デバッガー、静的アナライザー、コード フォーマッター、およびフル機能のエディターを含む単一のバイナリーを出荷することも目指している。

  • パッケージ マネージャー、静的アナライザー、デバッガー、またはホット コード ロード システムの作業はまだ開始されていない

  • エディターの作業も開始されている

  • 標準ライブラリは機能的にはおそらく 80% 完成している

  • REPLが用意してある(REPL (Read-Eval-Print Loop) とは、入力・評価・出力のループのこと。インタプリタにおいて、ユーザーとインタプリタが対話的にコード片を実行できるもの。)

  • ウェブ上でREPLが利用できるサービスもあります(https://roc-lang.org/repl)

  • コンパイラの対応OS:macOS(IntelおよびApple Silicon)、Linux(現時点でx86-64のみ)、Windows(最近サポート開始、まだデバッグ機能やテスト機能が使えず、未発見のバグがある可能性あり)

  • コンパイラの制約:インクリメンタルコンパイルやホットコードローディングに対応していない、ビルド時間は対象マシンによって異なる

  • LLVMバックエンドの利用:ほとんどのコンパイラがLLVM以前に行っていたように、Rocの内部表現から直接機械語にコンパイルする開発バックエンドを利用することで、ビルド時間を大幅に削減可能

  • バックエンドの状況:LLVMバックエンドが最も機能が充実しており、WebAssemblyバックエンドがそれに続く。x86およびARMバックエンドはまだ改善の余地がある

  • サージカルリンカー:LinuxとWindowsでは、高速なサージカルリンカーが利用され、リンク時間がほぼ無視できるほど短縮される。ただし、現在は実行可能ファイルのみ対応で、動的ライブラリは対応していない

  • テストランナー:標準的な非効果的テストに対しては一級のサポートがあるが、効果的テスト、プロパティベースのテスト、スナップショットテスト、シミュレーションテストなどはまだ一級のサポートがない

  • コードフォーマッター:ほぼ機能が完成しているが、コメントが特定の場所に置かれるとエラーが発生することがある。フォーマッターはバグの数が少ないため、ファジングが有効な方法でバグを発見するのに役立つ

関数型言語

  • 純粋な関数型のプログラミング言語を目指している

  • マネージドエフェクトを採用しているため副作用を考える必要がなくなる

    • 純粋関数型プログラミング言語では、副作用を伴う操作(例えば、ファイルへの書き込みやネットワーク通信など)を直接行うことができません。その代わり、マネージドエフェクトを使用して、副作用を伴う操作を言語の実行環境が管理ができる(async,awaitを利用して処理待ちのことを考えたなくてよいということかな?)
  • ツールの利便性向上のために純粋関数を使用

  • テストランナーは変更されないテストを再実行しない

  • 純粋関数のみのテストは並行して簡単に実行でき、フレークが発生しない

    • フレーク(flake)とは、ソフトウェアテストの文脈で使われる用語で、一貫性のない、予測不可能なテスト結果を指します。同じテストコードを複数回実行しても、パスしたり失敗したりすることがあります。これは、テストが不安定であることを示し、コードや環境の問題によって引き起こされることがあります。純粋関数を使うことで、テスト結果が一貫しており、フレークが発生しないようになります。
  • デバッグツールの信頼性が向上(例:タイムトラベル、レトロアクティブトレース)

  • Rocの独自のマネージドエフェクトアプローチ

  • Rocでは、アプリケーションはプラットフォーム上に構築される

  • プラットフォームは、I/Oプリミティブとメモリ管理も提供するフレームワークのようなもの

  • Rocの標準ライブラリはデータ構造のみを含む

  • Rocの使用感は、フレームワークを使用する他のプログラミング言語と似ている

記事の内容はここまででした。まだまだドキュメントはあるのでそちらも適当にまとめていきたいと思います。
めちゃくちゃ長かったけど大体日本語として落とし込めました。

eisukeeisuke

自動メモリ管理に仮想マシン不要について

メリット

  • 高速な実行: ネイティブコードに直接変換されるため、実行速度が高くなります。
  • リソース効率: 仮想マシンのオーバーヘッドがないため、システムリソースの消費が少なくなります。
  • 組み込みシステム向き: リソースが限られた組み込みシステムに適した言語となります。
  • シンプルなランタイム環境: 仮想マシンが不要なため、ランタイム環境がシンプルで、デプロイが容易になります。
  • 直接的なハードウェアアクセス: ネイティブコードを使用することで、ハードウェアに直接アクセスし、高度な制御が可能になります。

デメリット

  • プラットフォーム依存: 仮想マシンがない場合、プログラムは直接ネイティブコードに変換されて実行されるため、異なるプラットフォーム間での互換性が低くなる可能性があります。
  • エラーの特定が困難: 仮想マシンが提供するランタイムエラーチェックやデバッグ機能が利用できないため、エラーの特定や修正が困難になることがあります。
  • セキュリティリスク: 仮想マシンが提供するセキュリティ機能(例えば、サンドボックス環境)が利用できないため、セキュリティリスクが高まる可能性があります。
  • 開発者コミュニティやサポートが限定的: 仮想マシンを必要としないプログラミング言語は、一般的にマイナーな言語であることが多いため、開発者コミュニティが小さく、サポートや資料が限定的な場合があります。(これはありそう)
  • 最適化が難しい: 仮想マシンを利用している言語では、JITコンパイラ(Just-In-Timeコンパイラ)などの最適化技術が利用できることがありますが、仮想マシンがない言語では、これらの最適化技術が利用できない場合があります。これにより、パフォーマンスの最適化が困難になることがあります。

デバッグ機能やランタイムエラー機能が利用できない理由

  • 仮想マシンはソースコードとハードウェアの間に抽象化のレイヤーを用意するのでバグ検知が用意
    イメージとしては可読性の低い機械語に直接アクセスするため間違った操作をしていても読みずらいので気が付きにくくなっているかんじ。抽象化レイヤーを用意すると仮想machineが機械語にアクセスするために可読性の高い起点を置いてくれるので検知がしやすい。
eisukeeisuke

C言語関数の呼び出しについて

  • C言語からRocを呼び出せるようなラップ関数を作成するとC言語と互換性のある言語(Python, Ruby, JavaScript など)からRoc言語の関数を呼び出せるようにできる。
eisukeeisuke

アンボックスされたデータ構造やアンボックスされたクロージャを使用とは

  • メモリ上でデータが直接格納される形式のデータ構造で、ポインタを介さずにアクセスできるため、パフォーマンスが向上する

    • ポインタはデータの場所を示すもの
    • ポインタを参照する処理がない
  • クロージャとは関数とその関数が参照する外部変数(自由変数)をキャプチャしたもの(関数のイメージがよい)プログラムの一部としてデータと振る舞いを組み合わせることができる。

  • このクロージャに関してもポインタを介さずに直接アクセスできるので早い

eisukeeisuke

コンパイラのバックエンドとしてLLVMを使用について

  • LLVM(Low Level Virtual Machine)の略称

  • バックエンドとして利用される手順

  • 抽象構文木の生成で以下のようにコードの要素を分解していく処理の認識がよさそう。

class HelloWorld
{
    static void Main()
    {
        if (DateTime.Now.Hour < 12)
        {
            Console.WriteLine("Good morning!");
        }
        else
        {
            Console.WriteLine("Hello, World!");
        }
    }
}

このように分解していくことで複数の言語やプラットフォームで利用しやすくなる。

eisukeeisuke

ポリモーフィックコードの単形化とは

ポリモーフィックコードとは、複数の型に対応できるコードのことを指します。

ポリモーフィックコードには、主に以下の2種類があります。

  • パラメトリックポリモーフィズム:このタイプのポリモーフィズムは、ジェネリクスやテンプレートとしても知られています。関数やクラスに型パラメータを指定することで、異なるデータ型に対応するコードを生成することができます。例えば、C++のテンプレートやJavaのジェネリクスがこれに該当します。
  • アドホックポリモーフィズム:このタイプのポリモーフィズムは、オーバーロードされた関数やオーバーライドされたメソッドを使用して、異なるデータ型に対応するコードを実現します。オーバーロードは、同じ名前の関数を異なる型の引数で定義し、オーバーライドは、派生クラスで基底クラスのメソッドを再定義することで実現されます。

Roc言語ではモノモーフィゼーション化しているので上記の両方の種類をもち合わせているので、そのコードごとに最適な処理を選択できるようになっている。これはC++やRustなどが同じような構造である。
ただRocはリソースの自動開放を持ち合わせているかつモノモーフィゼーション化を実現している言語はほとんどない。代表的なガーベジコレクション機能を持つ言語はないようだ。

eisukeeisuke

Morphic と Perceusについて

Morphic と Perceus は、Roc言語の実行時オーバーヘッドを軽減するための最適化技術です。Roc は自動参照カウントを用いた自動メモリ管理を実装しており、これが実行時のオーバーヘッドの主な原因となります。Morphic と Perceus は、静的な参照カウントの最適化を行うことで、このオーバーヘッドを緩和しようとするものです。

  1. Morphic:Morphic は、Rocのコンパイラにおいて、参照カウントの最適化を行う技術です。コンパイル時に、不要な参照カウントの増減を省略したり、参照カウントの更新を再利用することで、実行時のオーバーヘッドを削減します。Morphic による最適化は、実行時の参照カウントのオーバーヘッドを軽減するものの、完全には解決しきれません。

  2. Perceus:Perceus は、Roc言語のメモリ管理の実装を改善するための技術で、参照カウントの更新を効率化します。Perceusは、オブジェクトのライフサイクルを追跡し、参照カウントの更新が最も効率的なタイミングで行われるようにすることで、実行時のオーバーヘッドを削減します。

これらの最適化技術のおかげで、Roc言語は実行時のオーバーヘッドを軽減できますが、C、C++、Zig、Rust などの言語に比べて、まだ一部のオーバーヘッドが残ります。このため、高いパフォーマンスが求められるシステムやアプリケーションでは、他の言語を選択することが適切な場合もあります。ただし、Rocは自動メモリ管理や他の開発者にとって便利な機能を提供しているため、開発効率や保守性の観点からは魅力的な選択肢となっています。

オーバーヘッドとは

オーバーヘッドとは、コンピュータシステムやプログラムの実行において、本来の目的やタスクの遂行以外に発生する追加的なコストや作業のことを指します。オーバーヘッドは、時間やリソースの無駄になることが多いため、システムやソフトウェアのパフォーマンスや効率に悪影響を与えることがあります。

例えば、プログラムの実行時オーバーヘッドは、メモリ管理やガーベジコレクション、関数呼び出しのコスト、例外処理など、プログラムの主要な機能やタスクとは直接関係のない処理によって発生します。オーバーヘッドが高いほど、プログラムの実行速度が遅くなり、リソースの消費が増えることがあります。

オーバーヘッドを低減することは、システムやソフトウェアのパフォーマンスや効率を向上させるために重要です。プログラミング言語やコンパイラの設計においては、オーバーヘッドを最小限に抑えるような最適化が行われることが一般的です。

eisukeeisuke

自動的な機会主義的なインプレース変更の導入(直接変更の代わりに)について

  1. 機会主義的なインプレース変更
  • 元のデータが格納されているメモリ領域を直接変更
  • 特定の条件下でのみインプレース変更が行われる
  1. 通常のデータ変更
  • 新しいメモリ領域へのデータのコピー
  • 他の場合でデータ変更が行われる

機会主義的なインプレース変更と通常のデータ変更は、特定の条件下でどちらか一方が選択され、適切な方法でデータの変更が行われます。これにより、パフォーマンスの向上とプログラムの安全性を両立させることができます。

eisukeeisuke

サージカルリンカーとは

コンパイルされたオブジェクトファイルを効率的に実行可能ファイルに結合するためのもの。

コンパイルされたオブジェクトファイルとは、プログラムのソースコードがコンパイラによって機械語に変換された中間的なファイルです。これらのオブジェクトファイルは、実行可能ファイルを作成する。

複数のオブジェクトファイルを実行可能ファイルににまとめるものがサージカルリンカーの役割である。