Processingで無名関数的なことしてみた
はじめに
自分は大学でProcessingを勉強し始め、その後Rust言語を使うようになりました。まあProcessingは狭い世界で、やれることが全然少ない!Rust最高!となったわけですが、Processingも長いこと使っていて愛着があるので、なにかProcessingにはない機能を再現してみたいと思いました。その一つが無名関数です。
無名関数が欲しくなったワケ
事の発端は、Processingで素数を求めるプログラムを作成していた時です。すぐに完成したのですが、Rustならもっと短くかけるのでは?と思い実装してみました。以下がこれらのコードです。
// Processing
ArrayList<Integer> exception_list = new ArrayList();
loop: for(int n=2; n<100; n++){
for(int m: exception_list){
if(n % m == 0){ continue loop; }
}
println(n);
exception_list.add(n);
}
// Rust
let mut exception_list = vec![];
'Loop: for n in 2..100 {
if exception_list.iter().any(|m|n % m == 0) { continue 'Loop; }
println!("{}", n);
exception_list.push(n);
}
アルゴリズムが簡単なのでそこまでの変化ではありませんでしたが、Rustの場合、簡単に説明すると「配列の全要素に対して、それを引数にとってbool値を返す関数を適用させ、その中の一回でもtrueが返った場合に」trueを返すany()
というメソッドがあります。
Processingでは無名関数(クロージャやラムダ式とも言います)が使えないのですが、これと同じことはできないかと考えていました。
インスタンス化と同時に関数を定義
Processingではnew ClassName(...)
によりインスタンスを作りますが、その際に関数のオーバーライド(abstractクラスに関しては定義)ができます。書いている途中で思い出しましたが、リスナーを登録するときにも使われていますね。
abstract class MyClass {
abstract void func();
}
void setup() {
MyClass c = new MyClass(){
public void func(){ // publicでなければエラー
println("Hello, world!");
}
};
}
そこで以下のようにして、機能だけは無名関数のようなものを実装してみました。
abstract class fn<R, A> {
abstract R func(A arg);
}
class Vec<T> {
ArrayList<T> list = new ArrayList();
void add(T item) {
this.list.add(item);
}
boolean any(fn f) {
for(T item: this.list){
if( (boolean)f.func(item) ){ return true; }
}
return false;
}
}
void setup() {
Vec<Integer> vec = new Vec();
for(int n=2; n<100; n++){
final int _n = n;
if(vec.any(new fn<Boolean, Integer>(){
public Boolean func(Integer m){ return _n % m == 0; }
})){
continue;
}
println(n);
vec.add(n);
}
}
...前より長いです。しかも書きづらいです。だから機能だけは無名関数なんです。
では解説していきます。
abstract class fn<R, A> {
abstract R func(A arg);
}
まずは無名関数というものを定義していきます(定義した時点で無名じゃないというツッコミはNGです)。
abstract
キーワードにより、このクラスは関数を定義させなければ使えなくなりました。必須のコードではありませんが、このクラスのfunc
関数はデフォルト実装をもたせたくないので悪くはないかなと。
<R, A>
の部分なのですが、これはジェネリクスというもので、動的に型を決定できます。「なにかしらの型ではあるが、今はまだわからない。とりあえず名前だけは作っておくから使えるよ。」といった意味でとらえていただければ問題ありません。
class Vec<T> {
ArrayList<T> list = new ArrayList();
void add(T item) {
this.list.add(item);
}
boolean any(fn<Boolean, T> f) {
for(T item: this.list){
if( f.func(item) ){ return true; }
}
return false;
}
}
ProcessingのArrayList
はもちろんですがRustで使われているようなany
メソッドを持っていません。ので、ラッパークラス的なものを作成していきます。ここでもジェネリクスが用いられており、ArrayList
同様どんな型に対してもインスタンスを作成できます。これはany
メソッドを持ったArrayList
と考えてください。
ここて定義されているany
メソッドは、型パラメータがBoolean
とT
(このVec
クラスで用いているT
と同じ型)であるfn
クラスのオブジェクトを引数にとり、f
という名前で引数変数を作っています。拡張for文(foreachみたいなもの)によって渡されたリストの中身を引数として、func
関数を呼び出しています。ただ、func
関数はまだ定義していませんでしたね?これは、引数として与える際に実装しています。次をご覧ください。
void setup(){
Vec<Integer> vec = new Vec();
for(int n=2; n<100; n++){
final int _n = n;
if(vec.any(new fn<Boolean, Integer>(){
public Boolean func(Integer m){ return _n % m == 0; }
})){
continue;
}
println(n);
vec.add(n);
}
}
Integer
型の可変長配列が欲しいので、Vec<Integer>
という型のインスタンスを作ります。
変数vec
に対してこの部分5行目でany
メソッドを使っており、引数にはfn
クラスのインスタンスを渡しています。ここでfunc
関数を定義を行なっています。
Vec<Integer>
型のオブジェクトvec
に実装されているany
メソッドの引数は、T
がInteger
に決定したのでfn<Boolean, Integer>
となります。したがって、ここで作られるfn
クラスの型パラメータも合わせて設定しないといけません。同時に、実装するfunc
関数の引数と返り値の型も正しく揃えます。
定義したfunc
関数は単純で、_n
という整数を与えられた整数で割り切れるか否かを返しているだけです。この_n
なのですが、単純にn
を渡そうとするとエラーになってしまいます。しょうがなくエラー回避のためにfinal
宣言をした変数_n
を使用しています。
あとは同じですね。結果的にそれまで求めた素数で割り切れなかった場合に、出力してからリストに加えています(このアルゴリズムをエラトステネスの篩といいます)。
まとめ
外で定義されている変数を使うのに一手間必要だったり(そもそもfinal
宣言することでできなくなることもあるのでは...)、無名関数と言いつつ無名じゃなかったりと、実用的かと言われれば微妙ではありますが、このようにして関数を渡すこともできるという記事でした。
Author And Source
この問題について(Processingで無名関数的なことしてみた), 我々は、より多くの情報をここで見つけました https://qiita.com/mwataame/items/760138a4ddfd81575b76著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .