ラベル programming の投稿を表示しています。 すべての投稿を表示
ラベル programming の投稿を表示しています。 すべての投稿を表示

2018年9月10日月曜日

System.Reflection.Metadataを使用する

みなさん、こんにちは、旧HAZAMAです。他の場所では既にハンドルネームを昔のものに戻しているんですが、こちらでも戻すことにしました。改めましてtrain12です。

こちらのブログの投稿としては4ヶ月ぶりになってしまいました。今回は表題の通り、System.Reflection.Metadataというライブラリについての投稿です。
.NETが変化し始めてもう数年でしょうか、.NET Coreが2.1になり、だいぶ成熟していますが、これを使う上で問題になることが一つあります。そう、標準では式木などで生成したコードをアセンブリファイルに保存できないのです。.NET Frameworkでは、アセンブリを生成する際にオプションを指定することで保存も可能になるのですが、.NET Coreにはそんなオプションは用意されていません。そこで使用することになるのが表題のライブラリです。System名前空間にあることからわかる通り、将来的にはCoreに添付されるようになるのかもしれません。現状は、NuGet経由でインストールすることになります。
さて、肝心のこのライブラリの使用方法ですが、驚くことにドキュメントがありません。オープンソースなのでソースコードを見に行くとそこにはXMLコメントがあるのですが、その程度。基本的な使い方ですら、いろいろ試行錯誤して捻り出さなければならないという始末になっています。MSが開発しているようなのですが、ちょっとここらへんはお粗末としか言いようがありません。
さて、単独でアセンブリを生成する方法はまだこれから試行錯誤しなければならない段階にあるのですが、この記事では既にSystem.Reflection名前空間で吐いたアセンブリを読み込んで編集してファイルに保存し直す方法をお見せします。ただ、コードを全部載せると長くなりすぎてしまう(具体的には、System.Reflection.Metadataで動く状態のインスタンスを作るコードだけで360行ありました)ので、ここでは概要だけを示すに留めます。単純に既にあるアセンブリをいじって保存したいだけなのにできない人たちの手助けになればいいでしょう。


  1. まずは何はともあれ、PEHeaderBuilderを作成します。基本的に元の値をそのまま入れれば大丈夫なはずです。
  2. ILをコピーします。ここがちょっと曲者で、なしでも動くかもしれませんが私は、Roslynのコードを参考に多少変形をかけています。この時にMethodBodyStreamEncoder.MethodBodyからOffsetを拾ってくるのを忘れずに。これを記録しておかないと、RVA(RelationalVirtualAddressの略です、多分)が算出できなくなります。
  3. MetadataBuilderを作ります。これはMetadataBuilderのインスタンスを生成後、各テーブルの情報を押し込むところまで含めています。この部分だけで200行近く行くはずです。
  4. MetadataBuilderからMetadataRootBuilderを作ります。
  5. あとはこれらをManagedPEBuilderに放り込み、BlobBuilderにSerializeして、最後にWriteContentToでStreamに書き込めば完成のはずです。
こんな概要を見るよりも、私の自作言語のコンパイラの該当箇所を読んだ方が早いかもしれないので、該当箇所のURLを貼っておきます。

これがMSの中の人に聞きつつ、捻り出したSystem.Reflection.Metadataの使い方です。ドキュメンテーションのないライブラリを利用するのは辛いですね。今回ので身に沁みました。
中の人によると、全然ドキュメンテーションが書けてないようなので、しばらくはこれが活用されるかもしれませんね。

2018年3月13日火曜日

My original programming language, Expresso -- Tools are now ready

Hi, this is HAZAMA. And this is another blog entry for Expresso.

So far, you can only call methods in some types such as System.Math, but now programs can construct a new instance of a C#'s type, read properties, and resolve which overloads of a method or constructor to call, so we can see .NET Framework as the standard library for Expresso, which will be a big step forward for it. I also introduced the null literal in order to interact with the .NET Framework. I'm planning to prohibit the use of null literals in contexts without .NET. If you need null in other contexts, you can use the Option type in the standard library(I have no idea of how it and other types in the standard library will be provided).


module main;

import "System.IO.File" as File;
import "System.IO.FileStream" as FileStream;
import "System.Text.UTF8Encoding" as UTF8Encoding;


def main()
{
    var writer (- FileStream;
    try{
        writer = File.OpenWrite("./some_text.txt");
        let bytes = UTF8Encoding{encoderShouldEmitUTF8Identifier: true}.GetBytes("This is to test writing a file");
        writer.Write(bytes, 0, bytes.Length);
    }
    finally{
        if writer != null {
            writer.Dispose();
        }
    }
}

The code above comes from the test codes and as you can see, now you can use .NET Framework as if it would be built into Expresso. You can call a constructor by passing values in the order in which the parameters are defined. The compiler is ignoring the names of the arguments because it simply checks that the types of arguments match to the types of parameters of constructors and methods, but I suppose it would be better to check that the types and names of arguments match to those of parameters of constructors and methods(In the code above I used the names of the parameters for those of the fields of the object creation expression). Note that when you call a constructor of an Expresso's type, you have to pass values in the order that the fields are defined in the class definition. Even though you specify the names of the fields, they will be simply ignored. If you need constructors that initialize a new instance with certain values, you can define factory functions that always pass the same values for the fields that you need to have. Note also that you can't define methods in class definitions that return or take themselves as the parameters(so you will define the factory functions in modules).
As you can see, because Expresso doesn't have the using statement as in C#, the body looks ugly. Maybe I should add a similar construct. In addition, I'm planning to make it so that immutable variables won't allow mutating methods to be called as in Rust(In the code above, writer.write will be affected). And because Expresso doesn't have enums, you also can't write code that uses .NET's enums. I'm going to make Expresso's enums algebraic data types, so you won't likely to use C#'s enums directly, but maybe I'll think something out because otherwise you can't write code that uses full capability of the FileStream class. Finally, I converted the names of C#'s methods to camel case. So be careful not to call them like File.OpenFile("./some_text.txt").(Apr. 8 2018 added: This feature has been removed because it makes the language too complex)

2018年3月5日月曜日

My original programming language, Expresso -- Explaining the core

Hi, this is HAZAMA. And this is the second entry to Expresso.

First of all, I'll explain several features that stand out.
First, Expresso supports vectors and dictionaries as builtin types. They can be written in literal forms and the compiler treats them as special types(although they are compiled to System.Collections.Generic.List and System.Collections.Generic.Dictionary respectively). But patterns don't support them yet because of the problems of the implementation.
Second, Expresso supports the intseq type, which is short for "integer sequence" and is a generator for integers like the xrange type in Python or the Range type in Rust. Unfortunately, even though it only handles 32 bit integers, it will be frequently used for counting up in for loops because Expresso doesn't support the traditional for loops as in C. If you index into vectors or arrays with an intseq, it will produce an iterator(in .NET terms it will be called an enumerator) that yields elements that match to the integer sequence, which is called a slice.
Third, Expresso also supports match statements like Rust. This construct pattern-matches against values and destructures objects and matches against literal values. Even though it will only match to tuple patterns, the variable declaration statements also now support pattern matches.
Oh, though I forgot to mention this, you can omit the parameters types and the return type in closures if they are obvious like the closures will be passed to functions or methods directly. This implementation expects that writing chains of methods is easy when in the future the methods in System.Linq.Enumerable can be called but I'm being hesitated to implement extension methods. I doubt that it will result in having to iterate through every type defined whenever a new type is defined.

Now that we know the features of Expresso that stand out, let's look at how Expresso works next. Currently the compiler is entirely made in C#. The lexer, the parser, the analyzer and the code generator are all written in C# at present. The binary the compiler will output is in the IL code, and the parser generator is also written in C#. These are the reasons why I chose C# for the language that the compiler is written in(you only need to generate expression trees in order to generate some data that can be executed). I'm keen to implement the compiler in Expresso, but there are a lot of problems that should be solved like how I can split the parser and the analyzer and assume we will use the parser that is written in C#, because the parser and the analyzer are the part that can't be separated, then what will be left is the code generator and so that will make no difference in how the compiler is written. In addition, how I can implement the intseq and the slice type is also a problem when I will write the compiler in Expresso. The intseq type is compiled to the ExpressoIntegerSequence type and doing so is made easy by the feature of the C# compiler so if I will implement it in Expresso, then I should also fully implement the ExpressoIntegerSequence without the convenient C# compiler's feature(This feature transforms the source code into a state machine. So it would be no problem if I know how the compiler transforms the source code).
Even though there are some problems, because the code generation is fairy easy, there is a parser generator and it runs on multiple platforms by default, I can say C# is the language for writing a compiler. If you are interested in creating your own programming language, I recommend you to start by implementing a LISP interpreter rather than recommending you to start by implementing a new language in the first place, for example. After you do that, you can easily see what the parser, the lexer and the interpreter are doing.
Next onto the grammar, it is not currently available in printed format or something similar so if you need to know which construct creates what object, see the Coco parser specification. Of the specifications there are ones that don't work at present because they are not implemented yet(namely, the comprehension and interfaces). If you need to grasp a bit of the grammar, see the files under cloned_directory/ExpressoTest/sources/. Of those files there are ones that the parser can't recognize but you will find what the grammar is like.
For documentations, I'm writing them in Markdown in English only. They are located in cloned_directory/Expresso/Documentation/.
And this wraps up the entry. I'll write another entry if I have more informations to share. See you again ;)

2018年3月2日金曜日

My original programming language, Expresso -- Introduction

Hi, I'm HAZAMA. This is the first entry in this blog written in English. This is the introduction to my original programming language, Expresso.
First of all, what do you really want to finish as a programmer in your whole life? I guess and wish it's a programming language.

Expresso first originated about 4, 5 years ago, and it now supports try, catch statements and it would accomplish many tasks so I've decided to release it as an alpha version. But I won't yet publish any official web sites or something.
The language name, Expresso, was coined as a mixture of "expressive" and "Espresso", meaning that it's highly expressive and it would be an easy-to-write programming language. There is a slogan as well saying that "Easy for beginners, elegant for enthusiasts". This slogan deliberately contains a lot of "e"s on the heads of words, meaning "Seeking for good things" in Japanese. That is, "e wo sagasu gengo". The pronunciation of e is the same as that of a Japanese word for "good". So it's just a pun in Japanese.
Expresso would aim to be an educational programming language if it gets spread over the world. So the slogan is shouted. In other words, Expresso is going to be an easy-to-write programming language for beginners and yet it is going to be an expressive programming language for enthusiasts.
You might be wondering why it aims to be a programming language for education. Here is the answer: I learned Pascal when I was a university student and it was an old-fashoned language. And Wikipedia says it is a programming language for education(only in Japanese and as of writing this entry. The English version says "a language intended to encourage good programming practices using structured programming and data structuring", though). That's why I made my original programming language a language for education. Yes, I admit that Expresso won't be faster than Rust.

Its specification, which is vague about many things currently, is a type-strict and object-oriented programming language. Because Rust has a big influence on Expresso, it also has a lot of features from Rust.
Currently Expresso runs on .NET environment only. The reasons for this are that you can rather easily set up a .NET environment and that it runs on multiple platforms. Oh yeah, I like C# the most.

Before covering the traditional Hello world program in Expresso, we'll look at how to set up an Expresso environment. Above all, git clone the repository from Github because we have no official web sites. Then run "git submodule update --init".  The dependencies will be resolved. And then make a directory named "test_executables" on cloned_directory/ExpressoTest/. Because binaries will be created on this directory when tested, it won't get run if it is missing. I guess I want to add this directory to the git repository.
Apr. 7 2018 added: Then, build the InteroperabilityTest project and move the resulting dll file to /ExpressoTest/sources/for_unit_tests.
After that, if you are a Mac or Linux user, then you should now be able to open up the solution file in your IDE and build and run the solution(Note that you may have to change your IDE settings so that it automatically download missing projects with NuGet because it now uses NuGet to download a dependency project). If you are a Windows user, then you must get Coco. And probably you should write a batch file that automatically build the parser with Coco. After downloading Coco from Coco/R for C# section in the above web site, put it in cloned_directory/Expresso/. The Expresso project contains the core source files that powers Expresso. After that, write a batch file similar to cloned_directory/Expresso/parserCompile.sh. It would be better to modify the project setting to automatically run the batch file because then it can automatically generate and compile the parser when you running the build command.
Even though I have said a lot about setting up on Windows, I won't guarantee that it runs on Windows. So I recommend you to use Mac + Visual Studio or Linux + Xamarin Studio(I used the latter and am using the former now)(Apr. 7 2018 added: I found that the EmitterTests don't run on Windows because Mono on Mac and .NET on Windows provide different implementations.)(Apr. 8 2018 added: Now most of the EmitterTests run on Windows. But there are still some tests that issue errors I can't resolve).

OK, enough with pre-execution or something:) Now let's get on a real program, the hello world program.

module main;
def main()
{
    println("Hello, world!");
}

Let's execute an Expresso program before inspecting it. Build the ExpressoConsole project and run mono exsc.exe hello_world.exs -o ./ -e hello_world. Then it should compile the source file to main.exe and to run the executable, you need to have Expresso.dll and ExpressoRuntime.dll in the current directory. So copy them first and then run mono hello_world.exe to actually executing the Expresso program. Do you see "Hello, world!" text on the console? You made it! You've successfully compiled and run an Expresso program!

In Expresso, one file corresponds to one module like Python. Every module has to be explicitly named. The program's entry point will be the main function. At present the main function takes no parameters and returns nothing(you can write those but they will be simply ignored)(Apr. 11 2018 added: now the main function should take an args parameter and be able to return an int.).
As you can see, functions and methods are defined with the def keyword. I think it is a keyword in Python and Ruby, but it is strange to use some keyword derived from "function" like Rust in Expresso. Rust has trait objects but doesn't have objects themselves so it is fine in Rust.
Even though it doesn't appear in this example, names precede types. The (- sign separates the name and the type. This sign is unique to Expresso(it should be), derived from the mathematical ∈ sign. It is a 2 type-strokes sign because of the idea of not wanting programmers to explicitly write the types of variables as much as possible.
If you need to explicitly write the return type of a method or a function, you use -> sign as in Rust. Because the return types of methods or functions will be inferred from their bodies, you can omit them(In fact in the above code, the return type will be void).
The println function that the main function calls is a builtin one, which calls Console.WriteLine method and therefore takes a variable number of parameters and prints them out separated with commas. There are the print function which doesn't put a new line at the end, and the printFormat function which takes a format string as the first parameter that Console.WriteLine takes(Apr. 11 2018 added: now it is unsupported because I implemented string interpolation).

This finishes the introduction. In the next entry, we'll examine the features and how Expresso works.

2018年2月26日月曜日

interfaceを動的に継承する方法

どうも、こんにちは、はざまです。今回は需要があるかどうかわかりませんが久しぶりに単独の技術ネタを書こうかなと思います。
先日の記事で私が自作言語を作っていることは周知のことかと思いますが、そのコードを書いている際にある問題に出くわしました。Interfaceの実装問題です。C#にはTypeBuilderというクラスがあり、これを使えば、型(interfaceもclassもstructも)を定義できるはずなのですが、なぜかinterfaceを実装するclassを定義しようとしてもうまくいかない。MSDNのTypeBuilder.AddInterfaceImplemetationメソッドの例の通りに書いているのにうまくいかない、なんでだろうと1週間以上試行錯誤を経てたどり着いた結果が、生成したクラスがobjectを継承していないことでした。つまり、C#でinterfaceを継承した型を動的に生成する最小のコードは、以下のような感じになるでしょう。
using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace Test
{
    class Main
    {
        public static void Main(string[] args)
        {
            var name = new AssemblyName("test");
            var asm_builder = Thread.GetDomain().DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave, "./");
            var file_name = "test.exe";
            var mod_builder = asm_builder.DefineDynamicModule(file_name);
            var interface_builder = mod_builder.DefineType("IInterface", TypeAttributes.Abstract | TypeAttributes.Interface);
            interface_builder.DefineMethod("DoSomeBehavior", MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(int), null);
            var interface_type = interface_builder.CreateType();

            var type_builder = mod_builder.DefineType("TestClass", TypeAttributes.NotPublic | TypeAttributes.Class, typeof(object), new []{interface_type});
            var ctor = type_builder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
                CallingConventions.Standard, Enumerable.Empty<Type>().ToArray());
            var il_generator = ctor.GetILGenerator();
            il_generator.Emit(OpCodes.Ret);

            type_builder.AddInterfaceImplementation(interface_type);
            var method_builder = type_builder.DefineMethod("DoSomeBehavior", MethodAttributes.Public | MethodAttributes.Virtual, typeof(int), Enumerable.Empty<Type>().ToArray());
            var il_generator2 = method_builder.GetILGenerator();
            il_generator2.Emit(OpCodes.Ldc_I4_0);
            il_generator2.Emit(OpCodes.Ret);

            var class_type = type_builder.CreateType();
            var instance = Activator.CreateInstance(class_type);

            asm_builder.Save(file_name);
            var method = class_type.GetMethod("DoSomeBehavior");
            var return_value = method.Invoke(instance, null);
            Console.WriteLine(return_value);
        }
    }
}

注意点としては、classにobjectを継承させる以外にも、interfaceはabstractにすること、interfaceのメソッドは、abstractかつvirtual、publicで宣言すること、classのメソッドは、publicかつvirtualで宣言することが挙げられます。今回は、フィールドを宣言したり、アクセスしたりしないので簡潔になっていますが、フィールドにメソッド内でアクセスしようとすると途端に複雑になり、TypeBuilder単独では達成できなくなったりしますが、それはまた別の話です。そこに関しては、他の方が記事にしているので、当方では記事にするつもりはありません。

2018年2月17日土曜日

オレオレ言語、Expressoについて・・・骨格解説編

こんにちは、はざまです。今回も前回に引き続き、自作言語Expressoの解説をしていこうと思います。

以下、まずはいくつか特筆すべきと思われるExpressoの機能を紹介します。
まず、Expressoでは、組み込み型でvectorやdictionaryがサポートされています。これらを生成するリテラルが用意されていますし、多少コンパイルでも特別扱い(実体は、それぞれSystem.Collections.Generic.ListとSystem.Collections.Generic.Dictionaryですが)されます。ただ、今の所は、実装上の問題により、パターンマッチの対象にはなっていないのですが……
他に組み込み型関連では、intseqという型があります。intseqとは"integer sequence"の略で、これはPythonで言うところのxrange型やRustのRange型と同様、整数列を生成するジェネレータです。残念ながら、int(32ビットの整数)の範囲の整数しか扱えないのですが、いわゆるCのような旧式のfor文が存在しないExpressoにおいて、カウントアップなどを行う際に多用される型です。vectorやarrayなどに作用して、整数列にマッチする要素だけを取り出すiterator(.NET用語だと、enumerator)も生成できます(sliceと呼ばれる)。
Expressoには、Rustにも存在するmatch文があります。これは、最近はやりのパターンマッチを行う文法要素で、各種オブジェクトの分解や、リテラル値とのマッチングを行います。tupleパターンとのマッチ程度しか想定していませんが、一応変数宣言(let文var文)でも、パターンが使用できるようになりました。
一つ書き忘れてましたが、クロージャは、関数やメソッドに直接渡すなど、すぐにその引数の型を推論できる状態であれば、型を省略することができます。将来的にEnumerable拡張のメソッドを呼べるようになった際にメソッドチェーンを書きやすくするための実装ですが、拡張メソッドを導入するかどうか悩んでいます。型を定義する度に、全型を走査しなければならなくなりそうで、ちょっと微妙なんですよね・・・

いくつか特筆に値する機能を見たところで、皆様も気になっているかもしれないExpressoの原理について解説しましょう。まず、現状、コンパイラは純C#製です。レキサ、パーサー、アナライザ、コード生成、全部C#で完結しています。吐かれるバイナリは、C#のコンパイル後の表現であるIL形式ですし、パーサージェネレータもC#のものを使用しています。この部分もC#を選んだ理由の一つに挙げられるでしょう(式木と呼ばれるデータ構造を生成するだけで実行可能なコードが生成できる)。いずれ、セルフホスティングしてコンパイラ自体をExpressoで実装したいところなのですが、パーサーとアナライザの切り離しをどうするか、パーサーはC#のものを使用するとして、現状、パーサーとアナライザは三位一体なので、そうするとあとはコード生成部分程度しかExpressoで書ける部分がなくなり、結局今のままと大して変わらないのではないかなどの問題があり、まだ実現していません。また、Expresso化するにあたって、組み込みのオブジェクト(intseq,slice)の実装をどうするかという問題もあります。intseq型は、ExpressoIntegerSequenceという型をC#で定義しているのですが、C#の機能を利用してEnumeratorを生成しやすくしているので、Expressoに置き換えるなら、それを自分で実装しなくてはならなくなります(コンパイラが自動で行う変換なので、それを知っていれば大した問題ではないかもしれません。yield式を使用するので、状態を保持するステートマシンみたいなものを自分で書かなければならない)。
とまあ、問題はあるものの、コード生成が楽だったり、パーサージェネレータが存在したり、標準でクロスプラットフォームで動くので、C#はオレオレ言語作りに結構向いている環境と言えるかもしれません。まあ、今から言語作りをしたい方にはいきなり言語作りするのではなく、まずはLISPのインタープリタあたりを実装するところから始められることをお勧めしますが。上で出したレキサ、パーサー、インタープリタ、それぞれの機能を具体的にイメージできるようになります。
次に文法についてですが、現状明文化していないので、Cocoのパーサー定義を見ていただくのが一番早いかと思います。中には、パーサー定義を作ったものの、機能の実装をしていなくて動かないものもありますが(具体的にいうとcomprehension, interfaceなどです)。大雑把に文法を把握したいのなら、ExpressoTest/sources配下のファイルが参考になるでしょう。こちらも仮で書いただけの定義があったりして、パースもできないものが含まれていたりしますが、概要を知りたいだけなら十分と思われます。
ドキュメントについては、Rustの公式解説本のようなものを英語でmarkdown形式で書いています。日本語で書き直すのは面倒なので、しないかもしれません。こちらは、Expresso/Documentation/配下に存在します。
まだ、書きたいことはあるような気がしますが、今回の記事はこの程度で、どうしても書いておきたいことができたら、また記事にしようと思います。では( ̄^ ̄)ゞ

2018年2月15日木曜日

オレオレ言語、Expressoについて・・・導入編

こちらでは、ご無沙汰しています、はざまです。
突然ですが、プログラマをされている皆様が生涯で何としても作り上げたいプログラムはなんでしょうか?これは私の願望も多分に含まれているのですが、恐らく一定数の方が、自作言語と答えるのではないでしょうか?

というわけで今回の記事は、自作言語のExpresso(エクスプレッソ)の紹介をします。4,5年前から開発しているオレオレ言語なんですが、最近、try,catchなども実装し、それなりに使える言語になってきたので、α版として公開することにしようかと思った次第です。とは言っても、専用のサイトはまだ用意しませんが。
Expressoという名前は、ExpressiveとEspressoからの造語です。表現力豊かに、かつエスプレッソ(コーヒー)一杯飲む間にでも開発できるような簡潔な言語を目指すという意味を込めています。また、言語のスローガンとして"Easy for beginners, elegant for enthusiasts"という標語も掲げています。「初学者には簡単に、熱狂者には華麗に」という意味ですね。この標語にはあえて、eで始まる単語を多用しています。これは「e(いい)を探す言語」というダジャレです。
Expressoは、オレオレ言語でありながら、普及、実用化させるならPascalのような教育用言語の地位を目指しています。そのために、先ほどの標語のような目標を掲げているわけです。つまり、初学者には簡単に書ける言語、しかしながら、習熟者にとっても、書きやすい言語を目指すということです。
なぜ、教育用言語を開発するのか不思議に思っているの方もいるかもしれないので、解説しておくと、私は大学時代にPascalという言語でプログラミングを学んだのですが、Wikipediaには教育用と書いてあるんですよ。200x年にしては時代錯誤的な構文とまだ設計に慣れていなかったせいもあって無事単位を落とし、翌年Cに変わったカリキュラムで再履したんですが、その現状を変えたいと思ったのがきっかけです。まあ、スピードとかを売りにしてもRustに敵うわけがないという本音もあります。

言語仕様としては、まだα版と銘打ってることもあって、かなりガバガバなところが多いんですが、基本は静的型付け、オブジェクト指向を基本とするマルチパラダイム言語になっています。一番強く影響を受けている言語が、Rustなので、Rustで採用されている仕様が結構入っています。
現状、Expressoは、.NET環境上でのみ動く言語になっています。理由は、.NETだと比較的ランタイム環境を整えるのも楽ですし、クロスプラットフォームで動くのが大きいです。まあ、私が、いちばん好きな言語がC#だからというのもありますが……

伝統的なHello worldプログラムの解説をする前に、導入方法を紹介しましょう。現状、専用のサイトがないので、Githubのリポジトリからcloneして導入していただく形になります。こちらからcloneしてください。cloneしたら、git submodule update --initを実行してください。依存リポジトリの解決が行われます(といっても、一つしかありませんが)。そして、cloneしたディレクトリ/ExpressoTest/配下にtest_executablesというディレクトリを作成してください。ここにテストで使用するバイナリが吐かれる設定になっているので、これがないとテストが実行できません。gitにこのディレクトリを追加できるのなら、追加したいところではあります。
2018/4/7 追記: それからソリューションをVisual Studioなどで開いて、InteroperabilityTestプロジェクトをビルドし、出力されたDLLファイルを/ExpressoTest/sources/for_unit_testsディレクトリに移動してください。
そうしたら、Mac,Linuxユーザの方なら、あとはメインのソリューションファイルをIDEで開けば、ビルドして実行できるはずです(NuGetを使って一部の依存プロジェクトをダウンロードするようになったので、自動で手元にないプロジェクトをダウンロードするよう、IDEの設定を変える必要があるかもしれません)。Windowsユーザの方は、Cocoという依存プログラムを拾ってこなければなりません。あと、パーサ定義をシェルスクリプトで自動生成しているので、その代わりのバッチファイルも書かなきゃダメですね、多分。
Coco/R for C#からCoco.exeを選択してバイナリを拾ってきたら、cloneしたディレクトリ/Expresso/配下に配置してください。Expressoが言語のコアを担うプロジェクトです。バッチファイルは、cloneしたディレクトリ/Expresso/parserCompile.shというシェルスクリプトを参考に作成していただきたいのですが、Coco.exeに渡すオプションは自由に変更してください。バッチファイルを作成したら、Expressoプロジェクト設定でビルド前に自動実行するように設定すれば、いちいち手動でパーサを生成する必要がなくなります。
長々と書きましたが、Windowsでの動作確認は不十分な(動きはするもののテストは通らない程度までしか確認していない)ので、動くことは保証しません。手軽に使いたいなら、Mac+Visual StudioかLinux+Xamarin Studioあたりの環境をお勧めします(昔は、後者、今は前者の開発環境で作ってます)(2018/4/7 追記: 現状、Windowsでは.NET自体の実装が異なるようでEmitterTestsが動きません)(2018/4/8 追記: Windowsでも、大方のEmitterTestsを動かせるようになりました。ですが、動かないものは割とどうしようもなさそうなエラーで失敗してます・・・特にOOPのはずなのに、インターフェイスが動かないのは痛すぎる・・・)。

さて、ここまでで肝心のコンパイラは動かせるようになったはずなので、伝統的なHello worldプログラムに移ります。Hello worldプログラムは、以下のように書きます。
module main;
def main()
{
    println("Hello, world!");
}

中身の解説をする前に実行してみましょう。ExpressoConsoleプロジェクトをビルドし、MacかLinuxならmono exsc.exe hello_world.exs -o ./ -e hello_worldなどとしてまずコンパイルします。その後、カレントディレクトリにExpresso.dllとExpressoRuntime.dllをコピーしてください。正式リリースする頃には、この部分はなんとかすると思いますが、とりあえず今は、ランタイム環境が必要です。そして、できあがったmain.exeをmono hello_world.exeとして実行すると、Hello, world!と画面上に出力されるはずです。Expressoプログラムの実行に成功しました!

Expressoでは、基本的に1ファイル1モジュール構成を採用しています。ここは、Python譲りですね。各モジュールは、明示的に名前付けすることを義務付けられています。プログラムのエントリーポイントは、mainモジュールのmain関数からになります。今のところ、main関数は、引数も戻り値もなしの仕様になっています(Cのように文字列配列の引数を定義したり、intを戻り値にしても動きますが、単純に無視されます)(2018/4/11 追記: main関数がargs引数を取り、intを返せるようになりました。monoを使って実行するからです)。
ご覧の通り、関数、メソッドはdefキーワードで定義します。PythonやRubyで採用されている構文だったと思いますが、Rustのようにfunction由来のキーワードだとメソッド定義に違和感があるからです。Rustにはトレイトオブジェクトはあるものの、オブジェクトはないので、メソッドは存在しないはずです(追記: 公式ドキュメントでは、implブロックの関数をメソッドと呼んでいるようですので、これは間違いだったようです)。
この例ではどこにも明示されていません(というか変数宣言がない)が、型は後置です。その際、変数名と型の区切り記号には、(-という記号を使用します。これは、数学の∈に由来するExpresso独自の記号(のはず)です。あまり型は明示してほしくないという思想の元、入力しづらい2文字の記号を採用しています。Rustには似た記号を一元化してくれる機能があったと思いますが、Expressoには導入していないので、(-を∈と書いても、認識してくれませんので悪しからず。
関数、メソッドの戻り値を明示する場合は、Rustでも採用されている->記号を使います。関数の戻り値は、return文から推論するので、省略しても構いません(上のmain関数では省略しているが、voidに推論される)。
上記のプログラムで呼び出しているprintln関数は、組み込みの関数です。.NET環境のConsole.WriteLine関数を使用しているので、可変長の引数をとって、それをカンマ区切りで標準出力に出力します。他に、お尻に改行を追加しないprintや、第1引数にConsole.WriteLineに準じるフォーマット文字列を取るprintFormat関数(追記: string interpolation(日本語だと「文字列補完」ですかね)を実装したので、廃止しました)などが存在します。

いかがでしたでしょうか。以上で、オレオレ言語Expressoの導入は終わりです。あ、Expressoの骨格の説明などをしませんでしたが、機能詳細などは次回の記事で行いましょう。コンパイラ作りに興味のある方には、次の記事が参考になるかもしれませんね。

2017年10月30日月曜日

C#のforとforeachに関する思想録

どうも、はざまです。つい先日、とあるニコ生を見ている際に、forとforeachの違いについて言及される場面があり、私がforeachを使うことを勧めたところ、foreachの方がiteratorオブジェクトの破棄がループごとに発生するから遅くなるという指摘をいただき、気になったので速度比較してみることにしました。私はそのとき、え〜Releaseビルドなら、ループ全体でiteratorを使い回すように最適化してくれるんじゃないと答えたのですが、果たして結果は(よく考えたら、iteratorは使いまわさないとおかしいですね。ループ状態を内包しているはずなので)。
今回試したコードは、以下のようなものです。
const int Max = 10_000_000;

var stopwatch = Stopwatch.StartNew();
int result = 0;
foreach(var i in Enumerable.Range(0, Max))
    result += i;

stopwatch.Stop();
Console.WriteLine("foreach Result: {0}/{1}ms", result, stopwatch.ElapsedMilliseconds);

var stopwatch2 = Stopwatch.StartNew();
int result2 = 0;
for(int i = 0; i < Max; ++i)
    result2 += i;

stopwatch2.Stop();
Console.WriteLine("for Result: {0}/{1}ms", result2, stopwatch2.ElapsedMilliseconds);


Stopwatchインスタンスを生成して、foreach/forループ内で足しこむだけです。最後にそれぞれの結果を出力するのは、デッドコード削除で足しこみ自体省略されたら、嫌だなという意図です。このコードだとそれぞれ、確実にオーバーフローが発生しますが、何も書かなければC#はオーバーフローを無視してくれるはずなので、今回は特に考慮していません。さて、この単純なコードで出た結果がこちら。

Debugビルド時
foreach Result: -2014260032/197ms
for Result: -2014260032/41ms

Releaseビルド時
foreach Result: -2014260032/171ms
for Result: -2014260032/26ms

テスト環境はMacBook Pro (Retina, 13-inch, Early 2015)で2.7Ghz Intel Core i5、メモリ16GBです。Debugビルド時でおよそ5倍、Releaseビルド時で7倍弱程度の差ができてしまいました。これを見る限り、ループごとに一時変数を用意してGC走らせていそうなのは間違いなさそうですね。それにしても、結構インパクトでかいです。そっか〜、foreachって遅かったのか〜。とは言っても、可読性重視したいので、使い続けますけどね。for使うとめんどくさいですしね。
速度にうるさいC++のrange-based forとかはどんな実装になってるんだろう。ループ全体で一時変数を使いまわしたりしてないのかな〜。まあ、そうしたら、この一時変数のスコープが大きくなっちゃうんですがね。
では、また(๑╹ω╹๑ )

2017年10月4日水曜日

Rust本の翻訳始めました

お久しぶりです。ご無沙汰してました、はざまです。昔のハンドルネームに戻したりしましたが、ここではこのままはざまと名乗り続けると思います。

さて、本題なのですが、題名の通り、Rustプログラミング言語の公式ドキュメントであるThe book第2版の翻訳を始めました。実際には数ヶ月前から行っているので、もっと早くにご報告していればよかったですね。公式リポジトリで今後大きな変更がないと考えられるFrozen扱いされた章から順に翻訳を進めているので、牛歩ではありますが、お付き合い頂ければと思います。
ドラフトの執筆が完了するまでは、こちらのgithubページのsecond-editionディレクトリで読んでいただくしかないかと思われます。目次がなく読みにくいかとは存じますが、どうぞご容赦ください。

では、今回はここまで。どうぞ、よしなに。

2016年9月12日月曜日

プログラミングにおける演算子と言語の進化に関する思想録

いきなり紛糾から本文を始めてしまいますが、現代において最も幅広く使われている言語の直系の始祖はC言語です。しかし、このC言語は最大にして不可侵の過ちを犯してしまいました。それはそう、"代入演算子"です。
数学における"="記号は"等価"を表します。a = 1という式は、aは1と等しいという意味であり、それ以外の何者でもありません。しかし、あやつ(C言語)はこの記号に代入という新たな意味を割り当て、代わりに等価演算子は"=="という新たな記号を生み出してしまいました。これにより、数学世界とコンピュータ世界の乖離が発生してしまったのです。

この乖離により、コンピュータ言語への入門のハードルが一段階上がってしまったと言えるでしょう。なぜなら、C言語での代入記号の導入により、その血筋を受け継ぐ言語(いわゆるC系言語)においても、"="記号は、代入演算子として使用される羽目となり、その結果、これら後継言語においても数学世界との乖離を生み出してしまったためです。
代入記号の導入により生み出される混乱は以下の通りです。


  1. if文内で数学の"="記号として、この記号を使用する
  2. 実際には代入となるため、左辺の値が変わってしまう
  3. 特にC言語系においては、if文の条件式になんら制約がないので、コンパイルが通ってしまう
  4. 結果、if文内で変数の値が変わってしまい、想定と違う動作をするバグを作りこんでしまう

もちろん、この程度の落とし穴ならば、人間が細心の注意を払えば回避可能ではありますが、現代のコンピュータ業界において、性能の向上は著しいものがあります。このような、多少のコンピュータリソースの消費で回避できうるミスならば、回避できる機構を用意して、使うべきだというのが持論です。
その持論に沿うように、最近作られた言語では、if文の条件式にbool値を返す式しか書けないようになっていたりして対策が施されています。
登場当初は、その万能性から高級言語と呼ばれていたであろうC言語も、今となっては、中級言語と呼ぶべき存在になってしまいました。今後も、言語が進化を続け、ヒューマンエラーの排除を言語が行ってくれるようになるといいですね。

※思想「録」と言いつつ、1エントリーしかありませんσ^_^;

2016年9月9日金曜日

TS LISPソース

どうも、はざまです。
前回の記事で、TS LISPの紹介をしました。今回の記事は、そのTS LISPのソースを全掲載しようと思ったのですが、さすがに長くなりすぎる上に、一覧で見せられても、閲覧性が悪くなるだけなので、その役目はGithubに譲るとして、解説を軽くふわりとするだけに留めようと思います。
中身自体、TypeScriptのコンパイラのバージョンがまだ0.8.*だか、0.9.*の時代に書いたものなので、現在のコンパイラでコンパイルしたら、時代遅れ感が否めませんが、まあ参考になる箇所もあるでしょう。
まあ、解説と言っても、そのファイルが何をしているか概要を説明するだけの簡便なものです。人によっては煩わしく感じるかもしれませんが、お付き合いください。


  • Common.ts - IEnumeratorやDictionaryなど、.NET環境の実行環境で広く必要になる基本的なクラス群を独自定義しています。ただ、このソースを書いた時点のTypeScriptの制限で、ちょっと本物の.NET環境とは異なるメソッド定義になっている箇所があります。新しいTypeScript環境では、解消されているのかな
  • WebHelpers.ts - 見た目をコンソール状にする外部ライブラリ"jqconsole"のラッパと、同じ機能を持つクラスを独自定義しようとしているファイル。ただ、独自実装は、途中で力尽きてます( ;´Д`)
  • ErrorFactory.ts - 様々な種類の例外を投げる"例外ファクトリ"クラスを定義
  • Utils.ts - 全体で必要になる種々のユーティリティ関数群を定義
  • LispTypes.ts - LISPの実行環境となるクラス群を定義
  • Reader.ts - LISPのトークンを識別する文法解析器と"クォーサイクォート"と呼ばれる特殊形式の内部表現を行うクラスを定義
  • LispFunctions.ts - 基本的なLISP関数のネイティブ実装を定義
  • Interpreter.ts - 実際にLISPの処理を行うインタープリタ
  • Snippets.ts - LispFunctions.tsだけでは足りない、よく使われる関数やマクロをLISPとして定義し、文字列で保持するモジュール。load-sample関数で読み込めるS式もこの中に定義されてます
  • main.ts - インタープリタ自体のブートアップを行う処理が記載されている
以上が概要です。こんなほとんど中身のない記事ですが、参考に(?)していただけると光栄です。
今更、なんでこんな記事を書いたのかって怒る方もいらっしゃるかもしれませんね。その理由は、なんとなくとしかお答えできませんε-(´∀`; )

2016年9月7日水曜日

新技術と旧技術の融合

みなさん、ご無沙汰してます。はざまです。
今回は、以前何の目的で作ったかは忘れましたが、作成したLISP処理系の紹介です。といっても、実装内容自体は、完全に借用しているため、そのポーティング程度しか語ることはありません。
そのポーティング先となったのが、今やVisualStudioでも、正式にサポートされ、一線級で活躍していると思われる初期のTypeScriptです。まだ、コンパイラのバージョンが0.8.*だか、0.9.*時代に書いたものなので、今から見ると粗がある可能性も否めませんが、大筋は今の思想と合致しているはずなので、今回、紹介するに至った次第です。これを期に、今後、TypeScriptの記事を増やせていけたらいいな〜とか思ったり、思わなかったり。
Jsdo.itがTypeScriptに対応したとの話も聞くので、修正するなら参考実装もそれに合わせて変更する形になるでしょうかね。
そうそう、Web上で動くLISP実装としての活用もしていただけると、ありがたい限りです。

2016年5月15日日曜日

クロージャの変数捕捉タイミングが変わってる!(JS編)

みなさん、こんにちは。また間が空いてしまいました。はざまです。
今回は久しぶりにJSの話題をしようと思います。

今日、昔書いたJSアプリの修正を行っていたのですが、以下のようなコードを書いてみて愕然としました。

jsdo.itの説明文にも書いた通り、コメント内の記法でChromeやSafariで評価を行うと、これまたコメント内のように、関数の実行順とリンクした結果が表示されたんですね。一昔前の挙動は、jsdo.it内のテストコードのように、順繰りにカウントアップされるというものだったんですが、いつからこのような挙動になったんでしょうか。
そもそも、jsdo.it内だと、ループ内にクロージャを定義できなくなっていること、Chrome、Safariともに同じ挙動だったことから、おそらく標準規格が変わったんだと思いますけど、今はそこまで追う余裕もないので、推測だけ記載しておきます。きっと、パフォーマンスの観点から、ループ内に無名関数を定義できなくした上に、既存のコードを吟味して下方互換性を保たなくても大丈夫という判断がなされたんでしょうけど、私個人としては、かなり衝撃的な仕様変更でした。……って、めちゃくちゃ個人的な理由ですね。
ワークアラウンドとしては、例にも示した通り、Function.prototype.bindを使うことです。クロージャを保持するための一時変数が必要になったり、thisにbindするわけでなければ、第1引数にnullを指定しなきゃいけなかったり、クロージャをさっと書けていた時代に比べると、ちょっと不格好なのは否めませんが、ループ最適化でパフォーマンスが向上するのなら、我慢しようといったところでしょうか。

6/7 追記: TypeScriptのマニュアルを読んでいて見つけた別のワークアラウンド。即時実行関数を挟むパターン。例に出している構文も酷似してるし、MS内に私のクローンがいるんでしょうか……

ではでは、またそのうちお会いしましょう^^/

2016年3月13日日曜日

C++プログラマー向けRust 翻訳シリーズ12

クロージャと第1級関数


クロージャと第1級および、高階関数は、Rustの核心部分である。
CとC++には、関数ポインタ(とC++限定で、まったく要領のわからなかった奇妙なメンバ/メソッドポインタとかいうもの)があった。とはいうものの、比較的使われる機会は少なく、さほどプログラマーフレンドリーでもなかった。
C++11でラムダ式が導入され、こちらは、Rustのクロージャに瓜二つのものである。特に、実装方法が似通っているという点でね。

手始めに、これらの概念のさわりに触れておきたい。それから、詳細に移っていこう。

ここにfoo関数があるとしよう。定義はpub fn foo() -> u32 { 42 }だ。
さらに、別の関数barを思い浮かべよう。こちらは、引数に関数を取る(bar関数の見た目は、後述する)。宣言はfn bar(f: ...) { ... }
foo関数をbar関数に、Cで関数ポインタを渡すような感じで与えることができる - bar(foo)
bar関数の内部で引数fを関数かのように呼び出すことができる - let x = f();

Rustには第1級関数が存在すると言う。理由は、関数を持ち回り、他の値同様に使うことができるからだ。
また、関数barは高階関数であると言う。理由は、関数を引数に取る、つまり、関数を操作する関数だからだ。

Rustのクロージャは、書きやすい記法の無名関数だ。|x| x + 2というクロージャは、引数を一つ取り、それに2を足して返す。なお、クロージャの引数に対して型を明示する必要はない(大抵型推論される)。また、戻り値も然りだ。
クロージャ本体が式1つ以上になる場合は、大かっこを使う - |x: i32| { let y = x + 2; y }
クロージャも関数と同じように引数に渡せる - bar(|| 42)

クロージャとその他の関数の大きな違いは、クロージャが周りの環境を保持することにある。これはつまり、クロージャ内からクロージャ外の変数を参照できるということである。

let x = 42;
var(|| x);
変数xがクロージャのスコープ内に存在するあり方に注目してほしい。

以前にもクロージャは見かけてきており、その時はイテレータとともに使用していた。これは、よくある使用方法である。具体例: ベクターの各要素に値を加える。

fn baz(v: Vec<i32>) -> Vec<i32> {
    let z = 3;
    v.iter().map(|x| x + z).collect()
}
ここで、引数xはクロージャに対するものであり、変数vの各要素が引数xとして渡されてくる。変数zは、クロージャの外部で定義されているが、クロージャであるがゆえに参照することができる。また、mapメソッドに関数を渡すこともできる。

fn add_two(x: i32) -> i32 {
    x + 2
}

fn baz(v: Vec<i32>) -> Vec<i32> {
    v.iter().map(add_two).collect()
}
ちなみに、Rustでは、関数内関数も定義することができる。こちらは、クロージャではなく、つまり、環境にはアクセスできない。スコープを限るためにあるようなものである。

fn qux(x: i32) {
    fn quxx() -> i32 {
        x // エラー: 変数xはスコープにない
    }

    let a = quxx();
}

関数タイプ


新しい例題関数を導入しよう。

fn add_42(x: i32) -> i64 {
    x as i64 + 42
}
以前にも見かけたように、関数を変数に代入することができる。例: let a = add_42;
この時、変数aの厳密な型は、Rustでは記述できない。時折、コンパイラがこれをエラーメッセージ内でfn(i32) -> i64 {add_42}と表現しているのを目撃するだろう。
各関数は、各々固有かつ匿名の型を持っている。宣言が同じ見た目でも、fn add_41(x: i32) -> i64は、異なる型になる。

いささか正確でないlet a: fn(i32) -> i64 = add_42などの型名なら、記述することができる。宣言が同じ関数はすべて、fn型に簡約化される(これなら、プログラマが記述できる)。

変数aはコンパイラに関数ポインタとして表現されるが、厳密な型を把握している場合、コンパイラはその関数ポインタを実際には使用しない。a()のような呼び出しは、aの型に基づいて静的に行われる。もし、コンパイラが(fn型であることしか把握していないなど)厳密な型を把握していない場合、呼び出しは値に含まれる関数ポインタを使用して行われる。

Fn型(先頭のFに注目)というのもあり、trait同様、制約である(実際のところ、traitである。いずれわかるだろう)。Fn(i32) -> i64はこのような宣言を持つ関数様オブジェクトの型に対する制約である。関数ポインタへの参照を取得すると、実際には、非正規化ポインタ(DSTの箇所を参照)で表されるtraitオブジェクトを生成していることになるのだ。

関数を別の関数に引き渡したり、フィールドに代入するには、型を記述しなければならない。書き方はいくつかあり、fn型ともFn型とも書くことができる。
このうち、後者の方が望ましい。なぜなら、これにはクロージャ(や可能性として他の関数様のオブジェクト)も含まれるからだ。一方、fn型には含まれない。
Fn型は動的サイズ付けである。つまり、値として使用することはできない。
関数オブジェクトを渡すか、ジェネリクスを使うかのどちらかにしなければならない。まず、ジェネリクスを使う方法を見てみよう。

fn bar<f>(f: F) -> i64
    where F: Fn(i32) -> i64
{
    f(0)
}
関数barは、Fn(i32) -> i64という宣言を持つ関数ならば、どんなものでも受け取ることができる(つまり、Fという型引数に対して、あらゆる関数様の型で実体化することができる)。
bar(add_42)と呼び出して、関数add_42を関数barに渡せば、型引数Fadd_42の匿名型で実体化する。また、bar(add_41)と呼び出しても動作する。

さらに、クロージャを関数barに渡すこともできる。例: bar(|x| x as i64)
これが動作するのは、クロージャの型も宣言に合致するFn型制約に紐づけられているからだ(関数のように、クロージャも各々、独自の匿名型を持っている)。

最後に、関数やクロージャへの参照を引き渡すこともできる。例: bar(&add_42)bar(&|x| x as i64)

関数barは、fn bar(f: &Fn(i32) -> i64) ...とも書くことができる。これら2種のアプローチ法(ジェネリクスと関数/traitオブジェクト)は、全く異なる意味を持っている。
ジェネリクスの場合、関数barは単態化(造語: polymorphize - 多態化の逆から。単射化でもいいか?)され、コード生成時には、コンパイラがfの型を把握できるので、静的ディスパッチされる。
関数オブジェクトを使用しているなら、関数は単態化されない。fの厳密な型がわからないので、コンパイラは仮想ディスパッチコードを生成せねばならない。
後者の方がスピードが遅いが、前者はコード生成量が多くなる(型引数インスタンス一つにつき単態化された関数が一つ)。

実は、Fn型以外にも関数traitは存在する。FnMutFnOnceである。使い方は、Fn型と同じである。例: FnOnce(i32) -> i64
FnMut型は、呼び出し中に可変化できるオブジェクトを表す。これは、普通の関数には適用されず、クロージャに作用してクロージャが環境を可変化できるようにする。
FnOnceは、(最大でも)1回しか呼び出せない関数を表し、こちらもまたクロージャにしか関連しない。

Fn型とFnMut型、FnOnce型は、継承関係にある。Fn型はFnMut型でもあり(Fn型の関数を可変化許可を得た状態で呼び出しても何ら害はないが、逆は言えない)、Fn型とFnMut型は、FnOnce型でもある(通常の関数を1回しか呼び出さなくても害はないが、逆は言えない)。

以上より、高階関数をなるべく柔軟にするには、Fn型ではなく、FnOnce型を使うべきだ(あるいは、この関数を2回以上呼び出す必然性があるなら、FnMut型を使う)。

メソッド


メソッドは、関数と全く同じように使用できる。つまり、ポインタ化したり、変数に代入するなど。ドット演算子は使用できず、メソッド名をフルネーム(UFCS - universal function call syntax ~普遍的関数呼び出し記法~と呼ばれることもある)で記述しなければならない。
self引数がメソッドの第一引数になる。

struct Foo;

impl Foo {
    fn bar(&self) {}
}

trait T {
    fn baz(&self);
}

impl T for Foo {
    fn baz(&self) {}
}

fn main() {
    // 固有メソッド
    let x = Foo::bar;
    x(&Foo);

    // traitメソッド。フルネームで記述していることに注目
    let y = <foo as T>::baz;
    y(&Foo);
}

汎用メソッド


汎用メソッドへのポインタを取得することはできず、汎用関数型を表現する手段も存在しない。しかし、全型引数がインスタンス化されていれば、関数への参照を取ることができる。

fn foo<T>(x: &T) {}

fn main() {
    let x = &foo::<i32>;
    x(&42);
}
汎用クロージャを定義する方法もない。複数の型に作用するクロージャが必要ならば、traitオブジェクトやマクロ(でクロージャを生成すること)を使ったり、クロージャを返すクロージャ(返ってくるクロージャごとに違う型に作用する)を渡せばいい。

汎用ライフタイム関数と超高位型(higher-ranked type)


ライフタイムについて汎用的な関数型やクロージャを存在させることができる。

無所有権参照を取るクロージャを想像してほしい。参照のライフタイムが何であれ、このクロージャは同じ挙動をするが(また、実際のところ、コンパイル済みのコードからは、ライフタイムは消去される)、その型定義はどんな感じだろうか?

fn foo<f>(x: &Bar, f: F) -> &Baz
    where F: Fn(&Bar) -> &Baz
{
    f(x)
}
この時、参照のライフタイムは何になるだろうか?この単純な例では、単一ライフタイムを使用しても構わない(汎用クロージャを使う必要性はない)。

fn foo<'b, F>(x: &'b Bar, f: F) -> &'b Baz
    where F: Fn(&'b Bar) -> &'b Baz
{
    f(x)
}
しかし、変数fに異なるライフタイムを入力できるようにする必要があったらどうだろうか?その場合は、汎用的な関数型が必要になる。

fn foo<'b, 'c, F>(x: &'b Bar, y: &'c Bar, f: F) -> (&'b Baz, &'c Baz)
    where F: for<'a> Fn(&'a Bar) -> &'a Baz
{
    (f(x), f(y))
}
ここでの新規要素は、for<'a>という箇所であり、これは、ライフタイムについて汎用的な関数型を記述し、「すべての'a, ...に対して」と読む。専門用語で言えば、この関数型は普遍定量化されているという。

なお、上述の例で'afooに捕らえさせることはできない。反例:

fn foo<'a, 'b, 'c, F>(x: &'b Bar, y: &'c Bar, f: F) -> (&'b Baz, &'c Baz)
    where F: Fn(&'a Bar) -> &'a Baz
{
    (f(x), f(y))
}
これはコンパイルが通らない。なぜなら、コンパイラがfooの呼び出しに対してライフタイムを推論する際、'aに対して単一のライフタイムを選択しなければならないが、'b'cが異なる場合にはそれができないからである。

このような感じで汎用的な関数型は、超高位型(higher-ranked type)と呼ばれる。上層のスコープのライフタイム変数は、ランク1になる。上記の例の'aは、上位スコープに移動できないため、このランクは2以上ということになる。

超高位関数型の引数を持つ関数を呼び出すのは簡単だ。コンパイラがライフタイム引数を推論してくれる。例: foo(&Bar { ... }, &Bar {...}, |b| &b.field)

実際問題、たいていの場合、そのようなことを気にかける必要さえない。関数引数のライフタイムを省略できるのと同じようにして、コンパイラが定量化されたライフタイムを省略させてくれる。具体的には、上記の例を以下のように書き換えることができる。

fn foo<'b, 'c, F>(x: &'b Bar, y: &'c Bar, f: F) -> (&'b Baz, &'c Baz)
    where F: Fn(&Bar) -> &Baz
{
    (f(x), f(y))
}
(これは不適切な例なので、'b'cしかライフタイムパラメータは必要ない)

Rustにおいて、無所有権参照を含む関数型が認識される箇所には、通常の省略ルールが適用され、この関数型(すなわち、超高位型)のスコープにおいて省略された変数の定量化が行われる。

このような非常に稀な使用例に対して、なぜこんなに悩まされなければならないのかと疑問に思っているかもしれないね。本当のきっかけは、外部の関数から渡されるデータに処理を施す関数を引数にする関数なのだ。

fn foo<f>(f: F)
    where F: Fn(&i32) // 完全明示記法: for<'a> Fn(&'a i32)
{
    let data = 42;
    f(&data)
}
このようなケースの場合は、超高位型が必要不可欠になる。代替手段として関数fooにライフタイム引数を追加したとしても、正常なライフタイムを推論することはできない。その理由を探るために、どのような挙動をするのか見てみよう。fn foo<'a, F: Fn(&'a i32')> ...というものを考えてほしい。
Rustでは、いかなるライフタイム引数も、自分が定義されている文法項目より長生きすることが必須条件になる(このような条件がない場合、このライフタイムを持つ実引数が、その関数内で使用できるが、ここでの存在は保証されないことになってしまう)。
foo関数内で、f(&data)という記述をしており、この参照のライフタイムはコンパイラにより推論され、(最大でも)変数dataが定義された箇所から、この変数がスコープを抜けるまでの間存在する。ライフタイム変数'aは、関数fooよりも長生きせねばならないが、ここで推論されたライフタイムはそうはならないため、このような方法で関数様オブジェクトfを呼び出すことはできない。

ただ、超高位型ライフタイムがあれば、オブジェクトfはどんなライフタイムでも受け取れることになり、&dataの無名ライフタイムも道理が通るので、この関数型もチェックが付くのだ。

Enumコンストラクタ


少し関係のない話をするが、役に立つこともある豆知識を紹介しょう。enumの状態はすべて、その状態のフィールドからenum自体にマッピングする関数を定義している。

enum Foo {
    Bar,
    Baz(i32),
}
これは二つ関数を定義する。Foo::Bar: Fn() -> FooFoo::Baz: Fn(i32) -> Fooというものだ。通常、各状態をこのように使うことはない。状態は関数というよりも、データ型として扱われるのだ。しかし、時として役に立つこともある。具体的には、i32型のリストがあるとして、以下のようにしてenum Fooのリストを作れたりすることだ。

list_of_i32.iter().map(Foo::Baz).collect()

クロージャの風味付け


クロージャは2種類の入力を持つ。明示的に引き渡される実引数と、環境から横取りする変数だ。普段なら、いずれの入力も推論されるので、(訳注: 特に気にかける必要はないが)、必要に応じて細かい制御を行うこともできる。

実引数に関しては、コンパイラに型推論させるのではなく、型を宣言することができる。これは戻り値の型にも適用できる。
|x| { ... }と書く代わりに|x: i32| -> String { ... }と書けばいい。実引数が所有権ありになるか、所有権なしになるかは、(宣言されていようが、型推論だろうが)型によって決まる。

捕捉される変数については、ほとんどの場合、型はその環境からわかっているが、もう少し魔法の呪文があるのだ。捕捉変数が参照渡しになるか、値渡しになるか?
コンパイラは、クロージャ本体からこの情報を推論し、可能な限り、参照渡しをしてくれる。

fn foo(x: Bar) {
    let f = || { ... x ... };
}
すべてがつつがなくいっていれば、クロージャf内で、引数xは関数fooの生存期間を持つ&Bar型になる。
ところが、引数xが可変な場合、捕捉変数は可変参照渡しと推論され、すなわち引数xの型は&mut Barになる。引数xがクロージャf内でムーブ(変数や値型のフィールドに代入されるなど)されていると、捕捉変数は値渡しと推論され、すなわち型はBar型となる。

この挙動は、プログラマーが変更できる(クロージャがフィールドに代入されたり、戻り値になる場合には必要になることもある)。クロージャの前にmoveキーワードを付ければ、捕捉変数はすべて値渡しされるようになる。具体例: let f = move || { ... x ... };と書くと、引数xの型は常にBar型になる。

先刻、関数の種類について話をした。つまり、FnFnMutFnOnceの話だ。今なら、なぜこれらが必要なのか説明がつく。
クロージャにおいて、可変性と呼び出しの唯一性は、捕捉変数にかかるものである。捕捉によって、捕捉された変数が一つでも可変になったら、FnMut型になる(なお、コンパイラにより推論されるものなので、宣言は必要ない)。
変数がクロージャにムーブされていたら、つまり、値渡しになっていたら(moveの明示と型推論によるもの、両方の可能性がある)、クロージャはFnOnce型になる。このようなクロージャを二度以上呼び出してしまうと、複数回捕捉変数がムーブされることになるので、危険である。

コンパイラは可能な限り、クロージャが柔軟な型になるよう、推論を行う。

実装


クロージャは、無名構造体として実装されている。この構造体が、クロージャの捕捉変数を各々フィールドとして格納しているのだ。この構造体は、単独のライフタイム引数を持ち、これが捕捉変数のライフタイムに制約としてかかる。また、この無名構造体はcallという名のメソッドを持ち、これを使ってクロージャを実行する。


fn main() {
    let x = Foo { ... };
    let f = |y| x.get_number() + y;
    let z = f(42);
}
例として、上記のクロージャを考えよう。これをコンパイラは、以下のように解釈する。

struct Closure14<'env> {
    x: &'env Foo,
}

// 実際の実装とは異なる。以下を参照
impl<'env> Closure14<'env> {
    fn call(&self, y: i32) -> i32 {
        self.x.get_number() + y
    }
}

fn main() {
    let x = Foo { ... };
    let f = Closure14 { x: x }
    let z = f.call(42);
}

前述したように、3つの異なる関数traitが存在する。FnFnMutFnOnceだ。現実的には、callメソッドはこれらのtraitが必要とするものであって、実装に固有であるものではない。
Fn型のcallメソッドは、特殊変数selfを参照で取り、FnMut型のcall_mutメソッドは可変参照で、FnOnce型のcall_onceメソッドは特殊変数selfを値で取る。

ここまで見てきた関数型は、Fn(i32) -> i32のような形をしており、あまりtrait型らしくない。ここには、少し秘密の呪文がかかっているのだ。コンパイラは、この丸括弧形砂糖を関数型にしか、かけさせてくれない。通常の型(山括弧型)に精製すると、引数の型がタプルとして扱われ、型パラメータで渡され、戻り値型もOutputと呼ばれる結合型になる。
以上より、Fn(i32) -> i32は、Fn<(i32,), Output=i32>という型に精製されるので、Fn traitの定義は以下のようになる。

pub trait Fn<args> : FnMut<args> {
    fn call(&self, args: Args) -> Self.Output;
}
したがって、上記のClosure14型の実装はむしろこうなる。

impl<'env> FnOnce<(i32,)> for Closure14<'env> {
    type Output = i32;
    fn call_once(self, args: (i32,)) -> i32 {
        ...
    }
}
impl<'env> FnMut<(i32,)> for Closure14<'env> {
    fn call_mut(&mut self, args: (i32,)) -> i32 {
        ...
    }
}
impl<'env> Fn<(i32,)> for Closure14<'env> {
    fn call(&self, args: (i32,)) -> i32 {
        ...
    }
}
この関数traitは、core::opsモジュール内に存在する。

先ほど、ジェネリクスを使用すると、静的ディスパッチになり、traitオブジェクトを使用すると、仮想ディスパッチになる話をした。今なら、もう少しその理由について突っ込んで話すことができる。

callメソッドの呼び出しは、静的メソッドディスパッチになり、仮想ディスパッチにはならない。単態化された関数を渡しても、やはり型は静的に把握できるため、静的ディスパッチになる。

クロージャをtraitオブジェクトに押し込むことができる。具体例: &fBox::new(f)で型は&Fn(i32)->i32Box<Fn(i32)->i32>になる。
これらは、ポインタ型になり、traitを指しているので、非正規化ポインタである。要するに、データ自体を指すポインタとvtable(翻訳者注: C++などで使われる仮想メソッドルックアップテーブルのこと。これとメタデータを用いて、実際に呼び出すメソッドの実装を決定する)を指すポインタで構成されるということだ。vtableを使用して、callメソッド(やcall_mutメソッドなど何でも)のアドレスを参照する。

時として、これら2種のクロージャの表現方法を箱詰め型クロージャや非箱詰め型クロージャなどと呼んでいるのを見かけるだろう。非箱詰め型クロージャは、静的ディスパッチによる値渡し型、箱詰め型クロージャは、動的ディスパッチによるtraitオブジェクト型のものをいう。
かつてRustには、箱詰め型のクロージャしか存在しなかった(また、システムも全く異なっていた)。

参考資料



注: 以下の資料はすべて、英語表記

翻訳者後記: お疲れ様でした。これにて、Rust for C++ programmersブログポストシリーズの翻訳は終わりです。原文にはTODOが記載されていて、今後加筆修正がありそうなため、適宜修正は行うつもりですが、原文とリンクしていなくても怒らないでください。
この記事を通して、一人でも多くのC++プログラマーがRustの利便性や動作原理などを理解していただけたら、翻訳者冥利に尽きます。
なお末筆ながら、この記事により発生した損害、賠償などの責務は当方では負いかねます。予めご了承ください。(訳: この記事の訳語を使用したけど、周りのプログラマーに通じなかったから責任取れなど)


原文: https://github.com/nrc/r4cppp/blob/master/closures.md

2016年3月11日金曜日

C++プログラマー向けRust 翻訳シリーズ11

グラフとアリーナ型メモリアロケータ


(注: この章の例は、このディレクトリをダウンロードしてcargo runコマンドを入力すると、実際に動かすことができる)

グラフ構造は、Rustにおいて非常に厳密なライフタイム管理と可変性管理があるため、いささか構築がめんどくさい。ただオブジェクト指向プログラミングにおいて、オブジェクトのグラフ構造は、非常によく使われるものである。
このチュートリアルでは、グラフ構造の実装方法を数種類提示していこうと思う。個人的には、アリーナ型メモリアロケータを使用し、多少ライフタイムの明示を駆使する方法が好みだ。また最後に、今後Rustに導入されそうな機能のうち、ここで紹介する実装方法の簡略化を行ってくれそうなものについて議論して締めたいと思う。

グラフ構造は、一連のノードとそのノード間を結ぶエッジで構成され、リストや木構造を一般化したものと言える。各ノードは、複数の子や親を持つことがある(尤も、グラフ理論では親子とは呼ばずに、内向・外向という)。
グラフ構造は、隣接するリストや行列で表すことができる。前者は、根本的にはグラフのノードを表すオブジェクトがあり、そのオブジェクトが隣接するノードのリストを持つものである。一方、行列で表す場合は、行ノードから列ノードへの辺があるかどうかを表す論理値の行列を使う。
ここでは、隣接リスト方式での実装方法のみ解説する。隣接行列方式は、Rust固有とは言い難い全く別の問題を抱えているのだ。

本質的には、二種の直交する問題が存在する。どうやってグラフ全体のライフタイムを管理するかと、どうやってその可変性を管理するかだ。

一つ目の問題は、究極的には、グラフ内の他のノードを指し示すのにどんなポインタを使うかという問題に帰着する。グラフ様のデータ構造は、再帰的であるため(たとえ、データはそうでなくとも、型自体が再帰的になる)、完全に値だけでグラフ構造を構築することはできず、何らかのポインタを使わざるをえなくなる。
グラフ構造は再帰的になりうるが、Rustの所有権は再帰的にはできないため、Box<Node>型を使用することはできない(ただ、擬似木構造のデータや連結リストには使用できるけど)。

いかなるグラフも真の意味で、不変たりえない。どこかで円環状になる可能性があるため、一文でグラフを構築することはできないからだ。ゆえに、最低限でも、初期化処理中はグラフを可変にする必要がある。
Rustにおいて、通常、ポインタはすべて固有か不変でなければならないというのは、普遍の真理である。グラフの辺は、(少なくとも初期化中は)可変でなければならず、いかなるノードにも内向する辺が2つ以上存在する可能性がある以上、辺の固有性を保証することはできない。したがって、何かしら幾分高度なことをして、可変性を扱わねばならないわけだ。

解決策の一つとして、可変生ポインタ(*mut Node)を使うことが挙げられる。これは、最も柔軟性の高い手段だが、同時に最も危険でもある。
ライフタイム管理は、型システムのサポートなしにすべて自分で行わなければならない。こうすれば、非常に柔軟で効率的なデータ構造を構築できるが、同時に慎重にならねばならない。
この方法ならば、先ほどのライフタイムと可変性問題は一掃できる。しかし、それは結局、Rustの利点をすべて無視することで得られるものである。つまり、コンパイラの支援を受けることはできない(また、生ポインタは、自動(被)参照しないため、特別人間に優しくない)。生ポインタを使ったグラフ構造は、C++のものと大して相違ないので、ここでは解説しない。

ライフタイム管理には参照カウント(共有所有権、Rc<...>を使用)かアリーナ型メモリアロケータ(全ノードがアリーナに管理された同じライフタイムを持つ、無所有権参照&...を使用)を使用するという選択肢がある。前者の方がより柔軟(あらゆるライフタイムを持つ個々のノードを外部から参照することができる)であるが、後者の方はそれ以外のあらゆる点で勝っている。

可変性管理について、RefCellクラスを使用してRustの動的な内部可変性機構を援用するか、可変性を自ら管理することができる(この場合、UnsafeCellクラスを使用して内部可変性をコンパイラとやりとりせねばならない)。前者の方が安全、後者はより効率的、両者ともプログラマーフレンドリーではない。

なお、円環状になっている可能性のあるグラフがあるのに、Rcクラスを使用している場合、さらなるアクションを行って再帰構造を破り、メモリリークを避ける必要がある。Rustには、Rcポインタを再帰的に保持できるコレクションがないため、グラフ内に再帰構造がある場合、参照カウントが0にならず、グラフは解放されなくなる。
グラフ内にWeakポインタを使用するか、グラフ破棄の時期に再帰構造を手動で破ることで解決できる。前者の方が信頼性が高い。ここでは、どちらも解説しない。例では、単にメモリリークさせる。
無所有権参照とアリーナ型メモリアロケータを使用したアプローチならば、このような問題はないため、その点で優れている。

アプローチの比較のため、例は非常にシンプルに保つ。グラフ内の一ノードを表すNodeオブジェクトに、文字列データ(より複雑なデータの代わり)と隣接ノード(edges)のVecを持たせる。複数ノードを持つシンプルなグラフを作成するinit関数と事前順序付け深さ優先探索を行うtraverse関数を定義する。traverse関数で各ノードのデータを表示する。
最後に、selfが表すノードの最初に隣接したノードへの参照を返すNode::firstメソッドとノードの持つデータを表示するfoo関数を定義する。これらの関数を介してグラフ内部のより複雑なノード操作を行う。

平易になりすぎずになるべく情報量を詰め込めるよう、可能な組み合わせのうち2通りを解説する。参照カウントとRefCellクラスを使用するものと、アリーナ型メモリアロケータとUnsafeCellクラスを使用するものだ。他の2通りの組み合わせは、学習用に残しておく。

Rc<RefCell<Node>>


完全な例はこちら

unsafeコードがないので、こちらの方が安全な選択肢である。また、最も非効率的で人間工学的でもない。ただ、非常に柔軟ではある。ノードが参照カウントされているため、グラフ外での再利用も利くからね。完全に可変なグラフが必要だったり、グラフ自体の存在にかかわらず、ノードが必要な場合は、こちらの手段を取るといいだろう。

ノードは以下のような構造をしている。

struct Node {
    datum: &'static str,
    edges: Vec<Rc<RefCell<Node>>>,
}
新規ノードを作るのは、さほどめんどくさくない。Rc::new(RefCell::new(Node { ... }))と書けばいい。初期化時に辺を追加するには、先頭ノードを可変無所有権参照し、終端ノードをクローン(こうすると、ポインタをコピーし、参照カウントを1増やす。ノード自体はコピーしない)して辺のベクターコンテナに追加する。

let mut mut_root = root.borrow_mut();
mut_root.edges.push(b.clone());
RefCellクラスを使うことで、書き込み時にノードが読み書き中ではないことが動的に保証される。

ノードへのアクセスは、必ず.borrow()メソッドを使用してRefCellクラスを無所有権参照しなければならない。firstメソッドは、無所有権参照ではなく、参照カウント式ポインタを返す必要がある。従って、firstメソッドの呼び出し元でも、無所有権参照せねばならない。

fn first(&self) -> Rc<RefCell<Node>> {
    self.edges[0].clone()
}

pub fn main() {
    let g = ...;
    let f = g.first();
    foo(&*f.borrow());
}

&NodeUnsafeCell


完全な例はこちら

このアプローチでは、辺に無所有権参照を用いる。これは素晴らしく人間工学的だ。こうすれば、主に無所有権参照(なお、Rustの参照カウント式オブジェクトの素晴らしい点は、ライフタイムシステムによく馴染むことにある。Rcクラスへの無所有権参照を作成して、直接かつ安全にデータを参照することができる。先ほどの例において、RefCellクラスのせいでこのようなことはできなかったが、RcUnsafeCellクラスならば可能なはずだ)を対象とするRustの標準的なライブラリを、ノードに対して援用することができるようになるのだ。

また、破棄も正常に行われる。唯一の制約は、全ノードが同時に破棄されなければいけないことだけである。ノードの破棄および、確保は、アリーナで行われる。

他方、わずかではあるが、ライフタイムを明示しなければいけない。残念ながら、ここでライフタイム省略記法の恩恵にあずかることはできない。この節の末尾で、このような事態を改善する言語の進化の方向性を探っていく。

構築フェーズでは、複数参照される可能性のあるノードを可変化するだろう。このようなことは、通常のRustコード内では不可能なので、unsafeブロック内で初期化しなければならない。ノードが可変かつ複数参照されているので、UnsafeCellクラスを使用して通常の不変性原則に依存しないことを、Rustコンパイラに通知するのだ。

では、いつこのアプローチは現実的になるのだろうか?
グラフは、初期化時のみ可変である必要がある。加えて、グラフ内の全ノードは同じライフタイムである必要がある(同時に破棄することが可能であれば、この制約を緩めて後々ノードを追加するようにもできる)。
同様に、ノードを可変化できる時期について、より複雑な不変性原則に依存することもできるが、実装を単純には保てなくなる。プログラマーがそれらの安全について面倒を見なければいけなくなるからね。

アリーナ型メモリアロケーションは、メモリ管理法の一種であり、一まとまりのオブジェクトが同じライフタイムを持ち、同時に解放される。
アリーナとは、メモリ確保と解放を司るオブジェクトである。(個々のオブジェクトごとにメモリ確保するのではなく)ひとくくりに巨大なメモリ領域が確保されたり、解放されたりするため、アリーナのメモリ確保は非常に効率的だ。
通常、オブジェクトはすべて連続したメモリ領域に確保される。そうすれば、グラフを辿る際のキャッシュ利用効率が上がる。

Rustにおいて、アリーナ型メモリアロケーションは、libarenaでサポートされ、コンパイラ全体で活用されている。アリーナには二種類ある。型付けアリーナと型なしアリーナだ。
前者の方が、効率的で使いやすいが、ある型のオブジェクトしか確保できない一方で、後者ならば、より柔軟でどんなオブジェクトでも確保することができる。
アリーナで確保されたオブジェクトは、すべて同じライフタイムであり、このライフタイムはアリーナオブジェクトのパラメータになる。型システムにより、アリーナにより確保されたオブジェクトへの参照が、アリーナ自体よりも長く生き残り続けないことを保証してくれる。

さて、ノード構造体にグラフのライフタイム('a)を含める必要が出た。隣接ノードのVecコンテナをUnsafeCellクラスで包んで、不変であるはずのVecコンテナを可変化できるようにする。

struct Node<'a> {
    datum: &'static str,
    edges: UnsafeCell<Vec<&'a Node<'a>>>,
}
new関数もこのライフタイムを使用し、メモリ確保を行うアリーナオブジェクトを引数に取らなければならない。

fn new<'a>(datum: &'static str, arena: &'a TypedArena<Node<'a>>) -> &'a Node<'a> {
    arena.alloc(Node {
        datum: datum,
        edges: UnsafeCell::new(Vec::new()),
    })
}
アリーナオブジェクトを使ってノードオブジェクトのメモリ確保を行っている。グラフのライフタイムは、アリーナオブジェクトへの参照から派生している。そのため、アリーナオブジェクトは、グラフのライフタイムを包括するスコープから渡す必要がある。今回の例で言えば、initメソッドに渡すことを意味する。(表記上のスコープ外で値を生成できるように型システムを拡張することが考えられるが、今すぐ実装の予定はない)。
アリーナオブジェクトがスコープ外に抜ければ、グラフ全体も破棄される(Rustの型システムにより、この時点以降までグラフへの参照が残らないことが保証される)。

辺の追加方法は少し趣が異なる。

(*root.edges.get()).push(b);
本質的には、root.edges.push(b)を呼び出してノード(b)を辺のリストに追加しているだけである。とはいえ、edgesフィールドがUnsafeCellクラスに包まれているため、get()を呼んであげる必要がある。こうすると、辺への可変生ポインタ(*mut Vec<&Node>)が得られ、edgesフィールドを可変化することができる。ところが、これはこのポインタを手動で被参照しなければいけないこと(生ポインタは自動被参照できない)でもあり、(*...)と書いているのだ。
最後に、生ポインタの被参照は非安全なので、全体をunsafeブロックに入れ込む必要がある。

traverse関数の気になる箇所は以下の通りだ。

for n in &(*self.edges.get()) {
    n.traverse(f, seen);
}
辺のリストを取得するのに先ほどと同じ手順を踏んでいるため、unsafeブロックが必要になる。この場合、実際には安全である。なぜなら、すでに初期化は完了しており、可変化処理がないからだ。

さて、firstメソッドもリストを取得する手順は同じであり、unsafeブロックが必要になる。ただ、Rc<RefCell<_>>を使用したグラフとは対照的に、ノードへの単純な無所有権参照を返せばいい。とても便利だ。どこにも可変化処理がなく、初期化済みのため、このunsafeブロックは安全だと推論できる。

fn first(&'a self) -> &'a Node<'a> {
    unsafe {
        (*self.edges.get())[0]
    }
}

このアプローチに対する将来的な言語の改善点


Rustにおいて、アリーナ型メモリアロケーションと無所有権参照の使用は、重要なパターンと考えられる。言語に改良を施して、これらのパターンをより安全に使いやすくするべきだ。アリーナの使用がメモリアロケータに対する現在進行中の改良によって、よりプログラマーフレンドリーになることを願っている。
その他、著者が把握している改良点は3つある。

安全な初期化処理


オブジェクト指向の世界では、初期化時のみ可変性を保つ機構に関して多数の調査が行われてきた。一体、この機構がRustでいかように作動するかは、未解決な疑問だが、可変ではあるが固有ではなく、スコープに閉じられたポインタを示す必要があるということのようだ。そのスコープ外では、既存のポインタはいかなるものであっても、通常の無所有権参照、つまり、不変か、固有になるのだ。

そのような機構の利点は、初期化時は可変で、それから不変になる一般的なパターンを示す手段ができることにある。また、これは、個々のオブジェクト自体は、複数所有されているものの、その集合全体(今は、グラフ)は単一所有されているという普遍的原則にも依存している。
こうすれば、UnsafeCellクラスとunsafeブロックの必要なく、参照とUnsafeCellクラスを使用するアプローチを適用することができるようになり、この手段がよりプログラマーフレンドリーかつ安全なものになる。

ETH Zurich(訳注: 大学名か何か?)のAlex SummersとJulian Viereckが、これを追求している。

汎用的なモジュール


グラフのライフタイムは、いかなるグラフでも定数になる。ライフタイムを繰り返し記述するのは、冗長でしかない。
これをプログラマーフレンドリーにする一手段は、グラフのモジュールにライフタイムのパラメータを持たせられるようにすることだ。そうすれば、struct、impl、関数ごとに記述する必要がなくなる。
それでも、グラフのライフタイムは、モジュール外から指定する必要があるが、たいていの場合、推論でなんとかなる(今日、関数呼び出しではできている)と好ましい。

モジュールがどんな見た目になるかは、ref_graph_generic_mod.rsを参照されたし。((上記で確約されている)安全初期化処理を援用してunsafeコードを排除することもできるはずだ)

また、このRFC issueも参照されたし。

この機能によって、参照とUnsafeCellクラスアプローチのコードの重複箇所が大幅に削減されるだろう。

ライフタイム省略


現時点で、プログラマーは関数定義のライフタイムを一部省略してプログラマーフレンドリーにすることができる。グラフに対して&Node型を使用するアプローチが、いささか見目麗しくない理由の一つは、ライフタイム省略記法を使用していないからだ。

Rustにおいてよく使われるイディオムに共通ライフタイムを持つデータ構造がある。そのようなデータ構造への参照は、&'a Foo<'a>のような型を立てる。具体例: グラフの例では&'a Node<'a>
このようなケースに役に立つ省略記法があると便利だが、どんな挙動をすべきかは確信が持てない。

汎用モジュールの例を見ると、さほどライフタイム省略記法を拡張する必要はないようだ(Node::newメソッドがライフタイムを与えられなくても動作するか皆目見当がつかないが、仮にそうであっても、ほんの些細な拡張を施すだけで、動作するようになるだろう)。
スコープ内で唯一のときは、モジュール全体で('static以外の)ライフタイムを省略できる何らかの新しいルールを追加したくなるかもしれないが、スコープ内に複数のライフタイムがある場合(fooinit関数を参照)の挙動が読めない。

汎用モジュールを追加しなくても、&'a Node<'a>という書き方に特化した省略記法を導入する可能性はある。まあ、どう実装するのかは知ったこっちゃないけど。


原文: https://github.com/nrc/r4cppp/blob/master/graphs/README.md

2016年3月10日木曜日

C++プログラマー向けRust 翻訳シリーズ10

配列およびVectorコンテナ


Rustの配列は、Cの配列とは全く別物だ。簡単に言うと、固定長配列と可変長配列の両側面を持っているのだ。これらは一般的には、固定長配列と配列スライスとして知られている。
いずれわかることだが、前者の名前はどこか的確でない。だってどちらの配列も固定長(可変長の対義語という意味)だからね。可変長配列カテゴリとしてRustでは、Vecというコレクションクラスが用意されている。

固定長配列


固定長配列の配列長は、コンパイル時に決定され、一つの型と考えられる。例: [i32; 4]は配列長4のi32型の配列。

配列のリテラル表記やアクセス方法は、Cと変わらない。

let a: [i32; 4] = [1, 2, 3, 4];    // もちろん、型注記は必須ではない
println!("二番目の要素は{}", a[1]);
配列のインデックスがC同様、0起点のことに気づいたかな。

ところが、CやC++と異なり、インデックスは境界値チェックが行われる。事実、配列へのアクセスはすべて境界値チェックが行われる。これもRustがより安全な言語と言える根拠の一つだ。

ここでa[4]と書こうとすると、ランタイムエラーが発生する。残念ながら、Rustのコンパイラはコンパイルエラーを出してくれるほど賢明ではないのだ。上記の例みたいにエラーになるのが一目瞭然であってもね。

あなたが危ない橋を渡りたがりだったり、プログラムのパフォーマンスを極限まで追求しなきゃいけない場合、境界値チェックを行わない方法もある。配列に対して、get_uncheckedメソッドを呼び出せばいい。境界値非チェックは、unsafeブロックで行う必要がある。こんなことをする機会は極々少ないはずだ。

他のデータ構造同様、Rustにおいて、配列は規定で不変になり、可変性は伝播する。また、インデックスアクセスでも可変化できる。

let mut a = [1, 2, 3, 4];
a[3] = 5;
println!("{:?}", a);
さらに、参照を取ることで配列を無所有権参照することもできる。

fn foo(a: &[i32; 4]) {
    println!("先頭: {}; 最後: {}", a[0], a[3]);
}

fn main() {
    foo(&[1, 2, 3, 4]);
}
無所有権参照された配列でも、インデックスアクセスできることに注目してほしい。

ここら辺で、C++プログラマーにとってRustの配列の気になりやすい側面について語っておこう。メモリ構造だ。
Rustの配列は、値型である。つまり、配列は他の値同様、スタック上に確保され、配列オブジェクトは(Cで見られるような)値へのポインタではなく、データ列で表現される。
ゆえに前述の例で、let a = [1_i32, 2, 3, 4];は、スタック上に16バイトのメモリ領域を確保し、let b = a;と書けば、さらにこの16バイトの領域をコピーする。Cのような配列が必要なら、明示的に配列へのポインタを作成する必要がある。そうすれば、先頭要素へのポインタが得られる。

最後に、Rustの配列はtraitを実装できる。ゆえに、メソッドを持っている。
なので、配列長を得るには、a.len()を呼び出せばいい。

配列スライス


Rustにおける配列スライスは、ただ単に配列長がコンパイル時に決定していないだけの配列である。型の表記法も、固定長配列と同じであるが、長さを明示する必要はない。具体例: [i32]は32ビット整数の配列スライス(配列長は動的に決まる)だ。

ただ、配列スライスには落とし穴がある。Rustにおいて、コンパイラは全オブジェクトのサイズを把握しておく必要があり、配列スライスのサイズは把握できないから、配列スライスを値で保持することはできないのだ。fn foo(x: [i32])などと書こうものなら、たちまちコンパイラがエラーを吐くだろう。

そこで、配列スライスは常にポインタで保持しなければならない(このルールには、オレオレスマポを実装するという非常に高度な技術を要する例外があるが、今は無視しても構わない)。要約すると、(配列スライスを無所有権参照するなら)fn foo(x: &[i32])、(配列スライスに対して可変生ポインタを得るなら)fn foo(x: *mut [i32])などと書かねばならない。

配列スライスを生成する最も単純な方法は、簡略化を使用することだ。
C++と比較して、Rustの暗黙的簡略化は、はるかに少ない。そのうちの一つが、固定長配列から配列スライスへの簡略化である。配列スライスはポインタでなければならないから、これは実質的にポインタ間の簡略化になる。
例えば、&[i32; 4]から&[i32]への簡略化は以下のようにして行える。

let a: &[i32] = &[1, 2, 3, 4];
ここで、右辺はスタック上に確保された配列長4の固定長配列である。そこから参照(型は&[i32; 4])を取り、この参照が&[i32]型に簡略化され、let文によりaという名前を与えられている。

配列スライスもまた、C同様に([...]で)アクセスし、境界値チェックが行われる。len()メソッドを呼び出せば、手動で配列長を確認することもできる。したがって、いずれかの時点で配列長が決定するのは明白だ。
実際、Rustの配列はどんな種類でも、配列長が決まっており、これが境界値チェックの肝であるため、メモリ安全性に貢献する要素たるのだ。
サイズは(固定長配列の静的決定と対称的に)動的に定まるため、配列スライスは動的サイズ付けタイプ(英語略称: DST - Dynamically Sized Types. 他にも動的にサイズが決定する型があるので、別の機会に解説する)と呼ばれる。

配列スライスオブジェクトは、ただのデータ列でしかないため、オブジェクト内にサイズは格納できない。代わりに、サイズはポインタの一部になる(配列スライスは、絶対にポインタでなければならないことを覚えているだろうか)。
(他の動的サイズ付けタイプへのポインタ同様)配列スライスへのポインタは、非正規化ポインタ(訳注: 造語; fat pointer - regular pointerが1ワード長に対して、それよりもサイズが大きいので)になる。非正規化ポインタとは、1ワード長ではなく、2ワード長で、データを指すポインタと追加のデータを持つものである。配列スライスの場合、追加データは、スライスの長さになる。

以上より、上記の例でポインタaは(64ビットシステム上では)128ビット長になり、前半部分にデータ列の先頭となる1へのメモリアドレスが、後半には長さの4が格納される。
Rustのプログラマー的に普段は、これら非正規化ポインタも通常のポインタと同等に扱って構わないが、少しでもかじっておくのはいいことだ(具体的には、キャストでできることなどに影響してくる)。

スライス記法と範囲オブジェクト


配列スライスは、配列に対する(無所有権参照の)窓と捉えることができる。ここまで、配列全体のスライスしか見てこなかったが、それ以外にも配列の一部をスライス化することもできる。これには、インデックス表記に似た特殊な書き方があり、整数を一つ取る代わりに範囲オブジェクトを与えることで作用させる。具体例: a[0..4]は配列aの先頭4要素をスライス化する。
範囲オブジェクトは、上限は含まず、下限は含むことに注意してほしい。

let a: [i32; 4] = [1, 2, 3, 4];
let b: &[i32] = &a;     // 配列全体をスライス化
let c = &a[0..4];       // bの別表記。型も&[i32]
let c = &a[1..3];       // 中間2要素。型は&[i32]
let c = &a[1..];        // 末尾3要素
let c = &a[..3];        // 先頭3要素
let c = &a[..];         // またまたbの別表記
let c = &b[1..3];       // 配列スライスもスライス化できる
最後の例において、スライスに対しても、無所有権参照をする必要があることに注目してほしい。スライス記法は、スライスオブジェクト(型は[i32])を生成する。なので、無所有権参照されたスライスオブジェクトをスライス化していても、これを無所有権参照(そうすれば&[i32]型のオブジェクトが得られる)しなければならない。

範囲記法は、スライス記法以外でも使用できる。a..bという書き方は、aからb-1までを列挙するイテレータを生成するのだ。なので、これを通常通り他のイテレータと混ぜたり、forループで使用したりできる。

// 1から10までの整数を出力
for i in 1..11 {
    println!("{}", i);
}

ベクターコンテナ


ベクターオブジェクトは、ヒープ領域に確保される有所有権参照である。従って、(Box<_>のように)ムーブ機構に順応している。固定長配列を値、配列スライスを無所有権参照とみなすと、RustのベクターコンテナはBox<_>ポインタに相当すると考えられる。

こうすれば、Vec<_>型をBox<_>型同様に、値というよりも一種のスマポのように考えやすくなる。配列スライスと同じく、長さはポインタに格納され、この場合、そのポインタとはベクターオブジェクトになる。

i32型のベクターはVec<i32>型になる。ベクターにリテラル表記はないが、vec!マクロを使えば同じ効果が得られる。また、Vec::new()メソッドで空のベクターも生成できる。

let v = vet![1, 2, 3, 4];       // 長さ4のVecオブジェクト
let v: Vec<i32> = Vec::new();   // i32型の空ベクター
上の例の2行目では、コンパイラがベクターの中身の型を把握できるように型注記が必須である。このベクターを使うつもりがあるなら、(翻訳者注: 使用箇所から型が推論できるので)この型注記は必要なくなるだろう。

配列や配列スライス同様、インデックス記法でベクターから値を取り出す(例: v[2])ことができ、これも境界値チェックが行われる。また、スライス記法でベクターをスライス化する(例: &v[1..3])こともできる。

ベクターにしかない機能として、サイズが動的に変化することが挙げられ、必要に応じて、伸びもするし、縮みもする。例えば、v.push(5)と書けば、ベクターの末尾に要素5を追加する(この時、変数vは可変でなければならない)。
なお、ベクターを肥大化させるとメモリの再確保が必要になることがあり、巨大なベクター相手だと、大量のコピーが発生することになる。これを防ぐには、with_capacityメソッドで予め大きな領域を割り当てておけばいい。詳しくはVec docs(英語版)を参照されたし。

Index trait


注意喚起: この節は、まだまともに解説できてない要素がたくさんある。チュートリアルを辿っているのなら、飛ばしても構わない。どう考えても、話題が高度すぎる。

配列やベクターと全く同じインデックス記法をHashMapなどのその他のコレクションクラスにも使用することができる。その上、自作のコレクションクラスにも応用できる。
インデックス記法(とスライス記法)の使用を宣言するには、Index traitを実装する。これは、Rustにおいて、組み込み型のみならず、ユーザ定義タイプでも略記法を使用できるようにする良い例になる(Derefでスマポの被参照を行え、その他Add traitなども含め、似たような挙動をする)。

Index traitは以下のような定義をしている。

pub trait Index<idx: ?Sized> {
    type Output: ?Sized;

    fn index(&self, index: Idx) -> &Self::Output;
}
Idxは添え字の型を表し、インデックス記法なら、だいたいusizeになり、スライス記法ならstd::ops::Range系のいずれかになる。
Outputは、インデックス記法で返される型を表し、コレクションごとに異なる。スライス記法の時は、単一要素の型というよりも、スライスオブジェクトになる。
indexメソッドは、実際にコレクションから要素を取り出す役割を担っている。ちなみに、コレクションは参照で渡され、このメソッドは(翻訳者注: 要素と)同じライフタイムを持つ要素への参照を返す。

Vecクラスの実装を覗いて、どんな実装になるかを見てみよう。

impl<t> Index<usize> for Vec<t> {
    type Output = T;

    fn index(&self, index: usize) -> &T {
        &(**self)[index]
    }
}
前述した通り、インデックス記法はusize型を使っている。Vec<T>型オブジェクトに対して、インデックス記法だとT型の単一要素、つまりOutput型の値が得られる。
indexメソッドの実装は少し奇妙だ。(**self)でベクター全体をスライス化し、インデックス記法で要素を取り出し、最後にこの要素への参照を取っている。

自作のコレクションクラスに対しても、同様のIndex実装を行って、インデックス記法やスライス記法を使うことができる。

初期化子記法


他の種々のデータ同様、Rustにおいて、配列とベクターは適切に初期化されなければならない。しばしば、0埋めされた配列が必要になるが、これをリテラル表記で書くのは苦痛でしかない。そのため、Rustには配列を特定値で初期化する糖衣構文が用意されており、[value; len]と書く。なので、長さ100の0埋め配列を作成するには、[0; 100]と書けば良い。

ベクターでも、vec![42; 100]と書けば、長さ100の42という値で埋められたベクターオブジェクトが得られる。

また、初期値は整数に限定されず、どんな式でもいい。
配列初期化式なら、長さは整数定数式でなければならず、vec!マクロなら、usize型の式にする。


原文: https://github.com/nrc/r4cppp/blob/master/arrays.md

2016年3月9日水曜日

C++プログラマー向けRust 翻訳シリーズ9

Destructuring(造語:非構造化)パート2 matchと無所有権参照


分解を行う際、無所有権参照が関わってくると、驚くことがあるだろう。無所有権参照について深く理解していれば驚くことはないと思うが、議論する価値はある(理解するには結構時間がかかる。間違いないよ。しかも、思ったよりも長いよ。だって、この記事の初稿はめちゃくちゃにしてしまったからね)。

&Enum型の変数xがあるところを想像してみよう(ここで、Enumは何らかのenum型だ)。
選択肢は二つある。*xにマッチをかけて全状態(Variant1 => ...など)を列挙するか、xにマッチさせて全状態への参照(&Variant1 => ...など)を列挙するかのどちらかだ。(流儀次第だが、一番目のやり方のほうが、見た目がすっきりして好ましいだろう)。
変数xは無所有権参照となり、これを被参照する方法には厳密なルールがある。そして、match式と絡むと(少なくとも筆者にとっては)驚異に変わるのだ。特に、既存のenumを一見問題なさそうな方法で変更していると、コンパイラがmatch式のどこかで爆発するのだ。

match式の真髄に触れる前に、Rustの値渡しに関するルールをおさらいしておこう。
C++では、値を変数に代入したり、関数に値を渡す方法は二通りある。値渡しと参照渡しである。
前者が、規定の方法であり、値はコピーコンストラクタ経由かビット単位でコピーされる。
引数や代入先を&記号で修飾すると、値は参照渡しされる。値のポインタだけがコピーされ、これに処理を施すと、コピー前の値にも影響を及ぼす。

Rustでも参照渡しができるが、渡し先と渡し元両方を&記号で修飾しなければならない。
Rustで値渡しをすると、さらにもう二つ選択肢ができる。コピー機構かムーブ機構かの選択だ。コピー機構ならば、C++の値渡しと同じになる(尤も、Rustにコピーコンストラクタはないが)。ムーブを行うと、値をコピーした上で、古い値を破棄する。Rustの型システムにより、古い値にはそれ以上アクセスできないことが保証される。具体的に、int型はコピー機構、Box<int>はムーブ機構である。

fn foo() {
    let x = 7i;
    let y = x;               // xはコピーされる
    println!("x: {}", x);    // OK

    let x = box 7i;
    let y = x;               // xはムーブされる
    //println!("x: {}", x);  // エラー: ムーブ済み(x)の値を使用しようとしている
}
Rustでは、オブジェクトがコピー機構かムーブ機構かは、デストラクタの有無で決定される。デストラクタはおそらく1投稿必要な話題だが、ひとまずDrop traitを実装していればデストラクタを持っていると言えるとだけ説明しておこう。
C++と全く同じように、デストラクタはオブジェクトが破棄される直前に実行される。そして、デストラクタがあるオブジェクトはムーブ機構になる。もしデストラクタがないなら、全フィールドのどれか一つでもデストラクタが定義されていれば、オブジェクト全体がムーブ機構に適応する。そうして、オブジェクトツリーをたどっていき、どこにもデストラクタが見当たらなければ、そのオブジェクトはコピー機構に適応していると判断される。

さて、無所有権参照オブジェクトがムーブされていないことは重要である。その前提がないと、もはや妥当ではない古いオブジェクトへの参照を保有することになり、これは、スコープ外に抜けて破棄されたオブジェクトへの参照を保持しているのと等しい。一種のdanglingポインタである。
ポインタを保持していると、他にも同じオブジェクトを指す参照があるかもしれない。ゆえにオブジェクトがムーブ機構に適応している場合、ポインタを被参照するのは非安全である(一方、コピー機構に適応しているなら、被参照してもコピーを作るだけで古いオブジェクトはそのまま残り、他の参照も生き残り続ける)。

よし、じゃあmatch式に戻ろう。前述したように、&T型の変数xにマッチさせるには、match式内で1回だけ被参照するか、全項で参照にマッチさせるかの2通りの方法がある。

enum Enum1 {
    Var1,
    Var2,
    Var3
}

fn foo(x: &Enum1) {
    match *x {   //選択肢1: ここで被参照
        Var1 => {}
        Var2 => {}
        Var3 => {}
    }

    match x {
        //選択肢2: 各項で被参照
        &Var1 => {}
        &Var2 => {}
        &Var3 => {}
    }
}
今回の場合、Enum1はコピー機構に適応しているので、どちらの手段も選択できる。順につぶさに見ていこう。
一つ目の選択肢では、変数xEnum1型の一時変数(変数xの値をコピーしている)に被参照し、Enum1型の各状態に対してマッチを行う。この方法は、値の型までは見ないから1段階マッチングになる。
一方、二番目の方法では、被参照は行わない。&Enum1型の値を各状態の参照とマッチさせることになる。こちらのマッチングは2段階である。まず、型(必ず参照)に対してマッチさせ、さらに参照化された型(Enum1)にマッチさせるのだ。

どちらにせよ、プログラマー(コンパイラ)がムーブや参照に関する不変性を保っていると確認せねばならない。参照されているオブジェクトは、一部であってもムーブしてはならないのだ。
もし対象の値がコピー機構に適応しているなら、これは大したことない。
しかし、ムーブ機構に適応している場合、全項でムーブが発生していないことを確認せねばならない。これを実現するには、ムーブが発生するデータを無視するか、参照を作ればいい(こうすれば、ムーブ渡しではなく参照渡しになる)。

enum Enum2 {
    // Boxはデストラクタ持ちなので、Enum2はムーブ機構に適応している
    Var1(Box),
    Var2,
    Var3
}

fn foo(x: &Enum2) {
    match *x {
        // 内包されたデータを無視するので、大丈夫
        Var1(..) => {}
        // 他項には変更なし
        Var2 => {}
        Var3 => {}
    }

    match x {
        // 内包されたデータを無視するので、大丈夫
        &Var1(..) => {}
        // 他項には変更なし
        &Var2 => {}
        &Var3 => {}
    }
}
いずれのアプローチでも、内包データを参照していないので、ムーブされることはない。
一番目の手段で、変数xは参照されているものの、被参照のスコープ(つまり、match式全体)内で中身には触れていないから、何も逃すことはない。また、値自体を束縛(*xを変数に束縛するということ)してもいないので、このオブジェクトをムーブしてもいない。

二番目のアプローチで各項において参照を取ることができるが、被参照を行う一番目では無理だ。ゆえに、二例目の2番目の項をa @ &Var2 => {}と書き換える(ここで変数aは参照)ことはできるが、一例目でa @ Var2 => {}と書いてしまうと、*xを変数aにムーブすることになってしまうため不可能だ。
ref a @ Var2 => {}となら書き換えられる(ここで変数aもまた参照)が、あまり見かける記法ではない。

では、Var1に内包されたデータが必要ならばどうすればいいだろうか。以下のような書き方はできない。

match *x {
    Var1(y) => {}
    _ => {}
}
または
match x {
    &Var1(y) => {}
    _ => {}
}
理由は、どちらの場合も、変数xの一部を変数yにムーブすることになってしまうからだ。
refキーワードを使用して、Var1内のデータへの参照を得ればいい。つまり、&Var1(ref y) => {}と書く。これならば、どこにも被参照はなく、変数xの一部をムーブすることにはならないからだ。その代わり、変数xの中身を指すポインタを作成することになる。

別の解決策として、Boxオブジェクトに分解することもできる(こうすると3段階マッチングになる)。つまり、&Var1(box y) => {}と書く。こうすると、int型はコピー機構に順応しており、変数yは、Var1内のBoxオブジェクトに包まれたint値のコピーになるからだ(この時、Var1はさらに無所有権参照になっている)。int型はコピー機構のため、変数xの一部をムーブする必要はない。
また、int値をコピーするのではなく、参照とすることもできる。つまり、&Var1(box ref y) => {}と書くのだ。この場合、どこにも被参照はなくなり、変数xの一部をムーブする必要がなくなるのだ。
仮にBoxオブジェクトの中身がムーブ機構に適応している場合、&Var1(box y) => {} と書くことはできず、参照バージョンを使わざるをえない。
さらにさらに、以前挙げた例の一番目においても、同様のテクニックを駆使することができ、いずれも先頭の&記号がなくなるだけの違いにしかならない。例えば、Var1(box ref y) => {}などね。

さあ、さらに複雑度を上げていこう。
1組のenum値に対してマッチングさせたいとする。こうなると、もう一番目のアプローチを取ることはできない。

fn bar(x: &Enum2, y: &Enum2) {
    // エラー: 変数xとyはムーブされてしまう
    // match (*x, *y) {
    //     (Var2, _) => {}
    //     _ => {}
    // }

    // OK
    match (x, y) {
        (&Var2, _) => {}
        _ => {}
    }
}
最初のアプローチは不正になる。理由は、マッチ対象の値が変数xyを被参照することで作成され、新しいタプルオブジェクトにムーブされているからだ。そのため、今回は二番目のアプローチしかうまく動作しない。その上、言うまでもないことだが、上述したルールに従って変数xyの一部をムーブしないようにしなければならない。

もし、データに対して参照しか得られないにもかかわらず、値が必要な場合は、このデータをコピーする以外に手段はない。大体の場合、それはcloneメソッドを使うことを意味する。データがcloneメソッドを実装していない場合、さらに分解を行って手動でコピーを行うか、自分でcloneメソッドを実装することになる。

では、ムーブ機構を持った値への参照ではなく、値自体が存在する場合はどうだろうか。
今度は、ムーブしても大丈夫になる。なぜなら、他にこの値への参照がないことが明白になるからだ(もし、参照が存在していたら、コンパイラによって値の使用を制限されてしまう)。

fn bad(x: Enum2) {
    match x {
        Var1(y) => {}
        _ => {}
    }
}
ここでも、注意事項がある。
まず、ムーブは1カ所でしか行えない。上記の例で、変数xyの一部をムーブし、他は忘却の彼方へ追いやっている。しかし、仮にa @ Var1(y) => {}と書いて、変数xの全体を変数aに、変数xの一部を変数yにムーブしようとすると失敗する。このような項は不正なのだ。
しかも、変数ayを参照に変えても無駄だ。こうすると今度は、以前解説した参照中ムーブ問題に直面してしまう。
変数ay両方を参照に変えるのはありだ。これなら、いずれもムーブされず、変数xはそのまま残り、その全体と一部へのポインタを得られる。

同様に(かつ、より一般的に)、複数のデータを内包する状態がある場合、あるデータには参照をとって、他はムーブするという芸当は行えない。
具体的に言うと、Var4(Box<int>, Box<int>)と定義されたVar4があるとして、match項内でVar4(ref y, ref z) => {} という風に両方を参照したり、Var4(y, z) => {}という風に両者をムーブしたりすることはできるが、Var4(ref y, z) => {}という風に一方はムーブして、もう片方は参照するといったことはできないということである。
なぜなら、オブジェクトは一部でもムーブしたら全体が破棄されてしまい、参照が無効になってしまうからだ。


原文: https://github.com/nrc/r4cppp/blob/master/destructuring%202.md

2016年3月8日火曜日

C++プログラマー向けRust 翻訳シリーズ8

分解(造語:非構造化)


前回は、Rustのデータ型について見た。
一旦、何かしらデータ構造を手に入れたら、そこからデータを取り出したくなるだろう。
構造体について、RustではC++と全く同じようにフィールドアクセスができる。しかし、タプルやタプル構造体、enumについては、分解を行わねばならない(ライブラリには様々な便利関数があるが、それらも内部的には非構造化機能を使用している)。
データ構造の分解は、C++にはない機能だ。しかし、Pythonや種々の関数型言語で馴染み深いかもしれない。その根底にあるのは、ローカル変数でフィールド値を初期化し、データ構造を作成できるように、ローカル変数をデータ構造の値で初期化できるはずだという理論である。この単純な理論に端を発して、非構造化はRustで最も強力な機能の一つになった。
別の言い方をすると、非構造化は、パターンマッチングとローカル変数への代入を組み合わせたものである。

非構造化は、主にlet文かmatch文で行われる。対象のデータ構造が複数の状態を持つ(enumなど)場合は、match文を使用する。let式は現在のスコープに変数を展開するが、match式はそれ自身スコープを持っている。比較してみよう。

fn foo(pair: (int, int)) {
    let (x, y) = pair;
    // これでfoo内のどこからでもxとyを使用できる

    match pair {
        (x, y) => {
            // こちらのxとyは、このスコープ内でしか使用できない
        }
    }
}
パターン(上記の例では、letキーワードの後と、=>記号の前に見られる)の書き方は、どちらのケースでも全く同じだ。このパターンは、関数定義の引数の位置でも使用することができる。

fn foo((x, y): (int, int)) {
}
(この書き方は、タプルよりも構造体やタプル構造体に対して効果を発揮する)

非構造化パターンは、ほとんどの初期化式内に書くことができ、無限に複雑化できる。この中には、データ構造のみならず、参照やリテラルも含まれる。

struct St {
    f1: int,
    f2: f32
}

enum En {
    Var1,
    Var2,
    Var3(int),
    Var4(int, St, int)
}

fn foo(x: &En) {
    match x {
        &Var1 => println!("一つ目"),
        &Var3(5) => println!("三つ目 数字の5"),
        &Var3(x) => println!("三つ目 数字の{}", x),
        &Var4(3, St { f1: 3, f2: x }, 45) => {
            println!("構造体入り。f2の中身は{}", x)
        }
        &Var4(_, x, _) => {
            println!("別のVar4 f1の中身は{} f2の中身は{}", x.f1, x.f2)
        }
        _ => println!("その他(Var2)")
    }
}
パターン内に&記号を使用して参照で分解する方法と、リテラル(5, 3, St{ ... })やワイルドカード(_)、変数(x)を組み合わせる方法に注目してほしい。

パターン内でアイテムを一つ無視したい場合は、変数の代わりに_を記述すればいい。なので、中身の整数がどうでもよければ、&Var3(_)と書いてもいい。
最初のVar4項で中身の構造体を分解(ネストしたパターン)している。また、二番目のVar4項では、構造体全体を変数に束縛している。
その他、..と書くとタプルや構造体の全フィールドを表すことができる。したがって、enumの状態に応じて処理を行う必要はあるが、中身はどうでもいい場合は以下のようにも書くことができる。

fn foo(x: En) {
    match x {
        Var1 => println!("一つ目"),
        Var2 => println!("二つ目"),
        Var3(..) => println!("三つ目"),
        Var4(..) => println!("四つ目")
    }
}
構造体を分解する際、フィールドは定義時の順番通りに記述する必要はなく、..で残りのフィールドを省略することもできる。

struct Big {
    field1: int,
    field2: int,
    field3: int,
    field4: int,
    field5: int,
    field6: int,
    field7: int,
    field8: int,
    field9: int,
}

fn foo(b: Big) {
    let Big { field6: x, field3: y, ..} = b;
    println!("{}と{}を取り出したよ", x, y);
}
構造体の省略記法として、フィールド名を記述すれば、同名のローカル変数を定義してくれる。上記の例では、xyという新しい変数を定義していたが、次のようにも書ける。

fn foo(b: Big) {
    let Big { field6, field3, ..} = b;
    println!("{}と{}を取り出したよ", field3, field6);
}
今度は、フィールドと同じ名前のローカル変数を定義している。今回の場合は、field3field6ね。

これ以外にもRustの分解機能にはテクニックが必要なものがある。
例えば、パターン内で変数を参照する必要が出たとしよう。この場合、&演算子は使用できない。これだと、参照を作成するんじゃなくて、参照にマッチしちゃうからね(ゆえに、オブジェクトを被参照することになる)。コードで表すとこう。

struct Foo {
    field: &'static int
}

fn foo(x: Foo) {
    let Foo { field: &y } = x;
}
ここで、変数yのタイプはintとなり、変数xのフィールドをコピーしている。

パターン内で参照を作成するには、refキーワードを使う。

fn foo(b: Big) {
    let Big { field3: ref x, ref field6, ..} = b;
    println!("{}と{}を取り出したよ", *x, *field6);
}
ここで、変数xfield6の型は&intになり、変数bのフィールドを参照している。

最後のテクニックは、複雑なオブジェクトを分解する際に、個々のフィールドのみならず、中間のオブジェクトにも名前をつける必要が出た際に使うものだ。
前述の例で言うと、&Var4(3, St{f1: 3, f2: x }, 45)というパターンがあった。このパターン内では、あるフィールドだけに名前付けをしていたが、構造体オブジェクト全体に名前付けをする必要が出てくる可能性もある。
もちろん、&Var4(3, s, 45)と書けば、変数sに構造体オブジェクトを束縛できるが、こうしたら、フィールドアクセスにはもう1段階処理が必要になってしまうし、またフィールドが特定の値だった場合だけにマッチさせたいときにはmatch式をネストする必要が出てくる。これでは面白くない。
そこで、Rustでは、パターンの一部を@記号で名前付けすることができるようになっている。例として、&Var4(3, s @ St{f1: 3, f2: x }, 45)と書けば、フィールド(f2フィールドに対して変数x)と構造体オブジェクト全体(変数s)を変数に束縛できるのだ。

これにて、Rustのパターンマッチングの機能はほぼ網羅し終えた。まだベクターコンテナマッチングなどカバーしていない機能もあるけど、match式とlet文の使い方や強力な機能の一部でも理解してもらえてればありがたい。
次回は、match式と無所有権参照の些事たる関連性について解説する。これがまた、Rustを学習する上で間違いやすいんだよな〜。


原文: https://github.com/nrc/r4cppp/blob/master/destructuring.md

2016年3月7日月曜日

C++プログラマー向けRust 翻訳シリーズ7

データ型


この投稿では、Rustのデータ型について論じていこう。データ型は、ほぼC++のクラス、構造体、enumに等しい。Rustの方の違いは、データと振る舞いがC++(やJava、さらには他のオブジェクト指向言語)のものよりはるかに厳密に分けられていることにある。
振る舞いは関数によって定義され、関数はtraitとimpl(implementationのこと)両方で定義できるが、traitはデータを含むことができない。その点においてtraitは、Javaのインターフェースに近い。
traitとimplに関しては、また別記事を立てて解説する。今回は、データについてのみ述べる。

構造体


Rustの構造体はメソッドのないCやC++の構造体に似て、単純な名前付きフィールドのリストである。この見た目は、例をあげればよくわかるだろう。

struct S {
    field1: int,
    field2: SomeOtherStruct
}
ここで、2つフィールドのあるSという構造体を定義している。フィールドはカンマ区切りで記述する。好みによっては、最後のフィールドもカンマで区切らせることができる。

構造体は型を導入する。上記の例では、識別子Sを型として使用することができる。SomeOtherStructは別の構造体という想定(上述の例では、型になっている)であり、(C++同様に)値としてフィールド化している。つまり、メモリ上にある構造体オブジェクトを指すポインタではないということ。

構造体のフィールドは、.演算子とフィールド名でアクセスする。以下、構造体の使用例。

fn foo(s1: S, s2: &S) {
    let f = s1.field1;
    if f == s2.field1 {
        println!("field1 matches!")
    }
}
引数s1は値渡しの構造体オブジェクト、引数s2は参照渡しの構造体オブジェクトである。メソッド呼び出し同様、.演算子だけで値渡しのオブジェクトだろうが、参照渡しのオブジェクトだろうが、フィールドにアクセスできる。->演算子を使用する必要はない。

構造体は、構造体初期化式で作成する。構造体初期化式は、構造体名とフィールド値の組み合わせである。

fn foo(sos: SomeOtherStruct) {
    let x = S { field1: 45, field2: sos };  // xを構造体初期化式で作成
    println!("x.field1 = {}", x.field1);
}
構造体は、循環参照できない。つまり、宣言やフィールドの型名に繰り返して構造体名を使うことはできないということだ。これは、構造体の値の取り扱い方のせいである。
故に例えば、struct R { r: Option<R> }は不正となり、コンパイルエラーが発生する(Option型について詳しくは後述)。そのような構造が必要ならば、何かしらポインタを使用しなければならない。ポインタならば、循環参照が許されている。

struct R {
    r: Option<Box<R>>
}
上記の構造体にOption型を含めていないと、これをインスタンス化する手段がなくなって、コンパイラがエラーを吐くことになる。

フィールドを持たない構造体は、定義においても、初期化においても、かっこは使用しない。ただ、宣言ではセミコロンで区切る必要がある。まあ、構文解析上の問題だけどね。

struct Empty;

fn foo() {
    let e = Empty;
}

タプル


タプルは、名前のない、混種の連続データである。型としては、かっこに型名を並べて定義する。特に名前がないため、タプルは構造で識別される。例として、(int, int)は一組みのint、(int, f32, S)は3要素タプルである。タプルオブジェクトは、宣言と同じような書き方をするが、型の代わりに代入する値を入れ込むことで(4, 5)のように生成される。

// foo関数は、構造体を引数にとってタプルを返す
fn foo(x: SomeOtherStruct) -> (i32, f32, S) {
    (23, 45.82, S { field1: 54, field2: x })
}
タプルは、let式で分解することで使うことができる。

fn bar(x: (int, int)) {
    let (a, b) = x;
    println!("x was ({}, {})", a, b);
}
分解(非構造化)については、次回詳しく話す。

タプル構造体


タプル構造体は、名前付きタプルである。また、逆の言い方をすれば、名前なしフィールドを持つ構造体とも表現できる。宣言は、structキーワード、かっこ内に型を記述し、セミコロンで閉じて行い、この名前が型として定義される。タプル構造体のフィールドは、名前よりも(タプル同様に)分解でアクセスする必要がある。
タプル構造体は、あまり使われない。

struct IntPoint (int, int);

fn foo(x: IntPoint) {
    let IntPoint(a, b) = x;  // 分解するのにタプル構造体の名前が必要なことに注目
    println!("x was {(}, {})", a, b);
}

Enums


enum(イーナム)は複数の値を取りうるという点において、C++のenumや複合体(union)のような型である。最も単純なenumは、全くC++のenumと変わらない。

enum E1 {
    Var1,
    Var2,
    Var3
}

fn foo() {
    let x: E1 = Var2;
    match x {
        Var2 => println!("var2"),
        _ => {}
    }
}
しかしながら、Rustのenumはこれよりもはるかに強力である。各状態はデータを含むことができる。タプルのように、こちらは型を並べて定義する。こうなると、enumはC++のenumよりも複合体に近くなる。
Rustのenumは(C++のような)タグなしのenumというよりは、タグ付きのenumだ。なので、enumの各状態を実行時に取り違えることがない。

enum Expr {
    Add(int, int),
    Or(bool, bool),
    Lit(int)
}

fn foo() {
    let x = Or(true, false);   // xの型はExpr
}
Rustにおいて、多くのオブジェクト指向的多様性は、enumを使うことで取り回しが良くなる。

enumを使う際、大抵はmatch式を使用する。
match式はC++のswitch文に似ていることを覚えているだろうか。match式や他の非構造化手段については、次回詳しく話すことにしよう。

fn bar(e: Expr) {
    match e {
        Add(x, y) => println!("Addノード: {} + {}", x, y),
        Or(..) => println!("Orノード"),
        _ => println!("それ以外(今回は、Litノード)"),
    }
}
match式の各項が、Expr型の状態に合致しており、全状態が網羅されていなければならない。最終項(_)が、残りの全状態をカバーしている。まあ、今回はLitしかないけどね。
各状態のデータは、変数に紐付けることができる。Add項で、変数xyにデータを紐付けているのがその例だ。データに興味がなければ、..と書いておけばいい。例内のOr項でしているようにね。

Option型


Rustにおいて、特別よく使われるenumがOption型である。Option型には二つの状態がある。SomeNoneだ。
Noneはデータを含まず、Someは型Tのフィールドを持っている(Option型はジェネリックなenumだ。これがどういうものか後ほど詳述するが、基本的なコンセプトはC++から明らかであると嬉しい)。
Option型は、データがあるかもしれないし、ないかもしれないという状態を表すのに使われる。C++で、何かしら未定義だったり、未初期化だったり、falseだったりする値を表すのにnullポインタを使ったが、Rustでは、Option型を使って表すのがベストであろう。
使用前に必ず値の存在確認を行わなければならないので、Option型は、より安全だ。nullポインタを被参照するようなことはない。その上、Option型は、より一般的であり、ポインタだけでなく、値に対しても使用することができる。

use std::rc::Rc;

struct Node {
    parent: Option<Rc<Node>>,
    value: int
}

fn is_root(node: Node) -> bool {
    match node.parent {
        Some(_) => false,
        None => true
    }
}
ここで、parentフィールドはNoneRc<Node>型の値を持つSomeたりうる。例では、この内包データを使用していないが、実際は違うだろう。

Option型には、便利なメソッドがある。なので、is_root関数の本体は、node.is_none()!node.is_some()とも書ける。

可変性の伝播とCell/RefCell


Rustのローカル変数は、標準で不変となり、mut属性を付けることで可変にできる。構造体やenumのフィールドに属性付けは行わない。こちらの可変性は伝播するのだ。要するに、構造体のフィールドが可変か不変になるかは、構造体のオブジェクト自体が可変か不変かに依存するということだ。

struct S1 {
    field1: int,
    field2: S2
}
struct S2 {
    field: int
}

fn main() {
    let s = S1 { field1: 45, field2: S2 { field: 23 } };
    // sは内部も不変である。以下のような値の変更は行えない
    // s.field1 = 46;
    // s.field2.field = 24;

    let mut s = S1 { field1: 45, field2: S2 { field: 23 } };
    // sは可変なので、今度はOK
    s.field1 = 46;
    s.field2.field = 24;
}
Rustの可変性の伝播は、参照には及ばない。これは、C++において、constなオブジェクトからポインタ経由でconstでないオブジェクトを変更できるのと似ている。変更可能な参照をフィールドに含めるには、フィールドの型宣言に&mut属性を付ける必要がある。

struct S1 {
    f: int
}
struct S2<'a> {
    f: &'a mut S1   // 変更可能な参照
}
struct S3<'a> {
    f: &'a S1       // 変更不可能な参照
}

fn main() {
    let mut s1 = S1{f:56};
    let s2 = S2 { f: &mut s1};
    s2.f.f = 45;    // s2は不変だけど、問題ない
    // s2.f = &mut s1;  // 問題あり - s2は可変ではない
    let s1 = S1{f:56};
    let mut s3 = S3 { f: &s1};
    s3.f = &s1;      // 問題ない - s3は可変だから
    // s3.f.f = 45;  // 問題あり - s3.fは不変
}
(S2S3に付いている'aというパラメータは、ライフタイムを表す。もうすぐ解説します)

たまに、オブジェクト自体は論理的に不変(翻訳者注:原文ではmutableだがimmutableのtypoか?)なのに、内部的に可変であるべき部分を含むことがある。キャッシュや参照カウントを考えてほしい(これらは、本当の不変性はもたらさない。なぜなら、参照カウントの変更の効果が、デストラクタ経由で出てくるからだ)。
C++でなら、mutableキーワードを使ってオブジェクト自体がconstであっても、変更することができる。Rustでは、CellかRefCell構造体が使える。これらを使って不変なオブジェクトの一部を可変にすることができる。これは便利だが、ともすると、Rustで不変なオブジェクトを見かけたら注意を要するということである。一部が実際は可変かもしれないからね。

CellやRefCellを使えば、Rustの可変性や代入性に関する厳密なルールを回避することができる。CellやRefCellを使用するのは安全だ。なぜなら、コンパイラが静的にRustの不変性をチェックできなくても、実行時に保ってくれるからだ。CellもRefCellもシングルスレッド向けオブジェクトである。

Cell型は、コピー機構を持つ型(組み込み型のことだ)に使おう。Cell型には、保持している値を変更するget,setメソッドと、値でインスタンスを初期化するnewメソッドがある。
Cell型は、とても単純なオブジェクトだ。つまり、コピー機構を持つオブジェクトは(Rustでは)他に参照されるはずがなく、スレッド間で共有されることがないため、何も高度なことをする可能性がない。おかしな事態になりようがないのだ。

RefCell型は、ムーブ機構を持つ型に使おう。これは、Rustのほぼ全てを意味し、構造体オブジェクトがよく使われる例だ。
RefCell型もnewメソッドを使って生成し、setメソッドを持っている。RefCellオブジェクトの値を取り出すには、メソッド(borrow, borrow_mut, try_borrow, try_borrow_mut)を使って無所有権参照しなければならない。これらのメソッドは、RefCellに格納されたオブジェクトへの所有権なし参照を返す。
また、これらのメソッドも静的参照と同じルールに従い、可変無所有権参照は一つしか作れず、可変なものと不変なものを同時には作成できない。ただ、コンパイルエラーではなく、実行時エラーになる。
try_で始まるメソッドは、Option型を返し、成功時にはSome(val)を、失敗時にはNoneを得る。
値が参照されている間は、setメソッド呼び出しは失敗する。

次の例では、参照カウント式ポインタでRefCellオブジェクトを参照している(よく使われるユースケース)。

use std::rc::Rc;
use std::cell::RefCell;

struct S {
    field: int
}

fn foo(x: Rc<RefCell<S>>) {
    {
        let s = x.borrow();
        println!("フィールド、2回参照 {} {}", s.f, x.borrow().field);
        // let s= x.borrow_mut();    // エラー - xの中身はすでに無所有権参照中
    }

    let s = x.borrow_mut();  // OK。先ほどの無所有権参照はすでにスコープ外
    s.f = 45;
    // println!("フィールド {}", x.borrow().field);  // エラー - 同時に可変参照と不変参照はできない
    println!("フィールド {}", s.f);
}
CellかRefCellオブジェクトを使っているなら、なるべく小さなオブジェクトに配置すべきだ。つまり、構造体全体に置くよりも構造体の数フィールドに配置するのを選べということだ(翻訳者注:原文を読んでもよく意味が読み取れなかった)。
これらはシングルスレッドのロックオブジェクトと見なせばいい。1回のロックで処理がかち合う可能性が減るから、洗練されたロックの方がいい。


原文: https://github.com/nrc/r4cppp/blob/master/data%20types.md