challenge メソッド名一覧の表示

リフレクション系のお題の続編です。

「ある与えられたオブジェクトtargetのメソッドのうち、 "test_"で始まるものをすべて呼びだす」というコードを書いてください。 引数に関しては都合のいいように仮定して構いません(全部0個、など)。

メソッドという概念がない言語の場合は、 「複数の関数への参照を持っているようなオブジェクト(たとえばパッケージとかモジュールとか)から"test_"で始まる関数をすべて呼び出す」と読み替えても構いません。

Posted feedbacks - Nested

Flatten Hidden
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import types

class A:
    def __init__(self):
        self.bar = 0
        self.test_bar = 1
        self.baz = []
        self.test_baz = {}
    def foo(self):
        print "foo"
    def test_foo(self):
        print "test_foo"
    def boo(self):
        print "boo"
    def test_boo(self):
        print "test_boo"

def call_tests(obj):
    for name in dir(obj):
        if name.startswith("test_"):
            attr = getattr(obj, name)
            if isinstance(attr, types.MethodType):
                attr()

def main():
    call_tests(A())

if __name__ == '__main__':
    main()
上のコードをJavaScriptに移植。IE6とFirefox2で動作確認。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function A()
{
    this.bar = 1;
    this.test_bar = 2;
    this.baz = [];
    this.test_baz = {};
}

A.prototype.foo = function()
{
    alert("foo");
}

A.prototype.test_foo = function()
{
    alert("test_foo");
}

A.prototype.boo = function()
{
    alert("boo");
}

A.prototype.test_boo = function()
{
    alert("test_boo");
}

function call_tests(obj)
{
    for (var name in obj)
    {
        var attr = obj[name];

        if (attr instanceof Function && name.indexOf("test_") == 0)
        {
            attr();
        }
    }
}

call_tests(new A());
1
2
3
4
5
6
7
8
9
class Foo
  private;   def test_foo()   puts("test_foo_private") end
  public;   def test_foo2()   puts("test_foo_public") end
  protected; def test_fuga()  puts("test_fuga") end
  public;    def public_foo() puts("public_foo")   end
end

obj = Foo.new
["methods","private_methods"].collect{|m| obj.send(m).grep(/^test_/)}.flatten.each{|m| obj.instance_eval m}
1
info commands target::test_*
呼び出すって実行するところまでだと気づいたので修正。引数はなしで。
1
foreach cmd [info commands target::test_*] { $cmd }
Common Lispのメソッドはクラス内の概念ではないので「複数の関数への参照を持っているようなオブジェクト(たとえばパッケージとかモジュールとか)から"test_"で始まる関数をすべて呼び出す」 関数名はLisp的にはtest-なんだけどね~
1
2
3
4
5
6
7
(require :cl-ppcre)
(defun test_1 () 1)
(defun test_2 () 2)
(defun test_3 () 3)

(loop for f in (ppcre:regex-apropos-list "^test_" *package*)
   collect (cons f (funcall f)))        ; => ((TEST_1 . 1) (TEST_3 . 3) (TEST_2 . 2))
変数の定義でもシンボルはインターンされるので fboundp を。ついでに大文字小文字を区別する別回答。 funcall は関数を探しにいきますので symbol-function は不要ですがなんとなく。
1
2
3
4
(defun call-test (pakcage)
  (do-symbols (symbol pakcage)
    (when (and (fboundp symbol) (ppcre:scan "^test_" (symbol-name symbol)))
      (format t "~A => ~A~%" symbol (funcall (symbol-function symbol))))))
ぎゃぁ、fboundpが抜けてたorzorzorz
1
2
3
4
5
6
7
8
9
(require :cl-ppcre)
(defun test_1 () 1)
(defun test_2 () 2)
(defun test_3 () 3)
(defparameter test_var 0)

(loop for f in (ppcre:regex-apropos-list "^test_" *package*)
   when (fboundp f)
   collect (cons f (funcall f)))        ; => ((TEST_1 . 1) (TEST_3 . 3) (TEST_2 . 2))
get_class_methods関数を使うと簡単にメソッド一覧を取得できます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
class Doukaku
{
    function Doukaku()
    {
    }

    function test_Doukaku()
    {
    }

    function hello()
    {
        print("hello world!");
    }

    function test_hello()
    {
    }
}

$doukaku = new Doukaku();
$result = array();
foreach (get_class_methods($doukaku) as $method) {
    if (strpos($method, "test_") === 0) {
        $result[] = $method;
    }
}

var_dump($result);
?>
実行するとこが抜けてたorz
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
class Doukaku
{
    function php()
    {
        print("php\n");
    }

    function test_php()
    {
        print("test_php\n");
    }

    function hello()
    {
        print("hello\n");
    }

    function test_hello()
    {
        print("test_hello\n");
    }
}

$doukaku = new Doukaku();
$result = array();
foreach (get_class_methods($doukaku) as $method) {
    if (strpos($method, "test_") === 0) {
        $doukaku->$method();
    }
}

?>
環境選びますがfnmatchとかいかがざんすか?
1
2
3
4
5
foreach (get_class_methods($doukaku) as $method) {
    if (fnmatch("test_*", $method)) {
        $doukaku->$method();
    }
}
setAccessibleしないといけないのがなんとも。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Foo {
  private def test_foo = println("test_foo_private")
  def test_foo2 = println("test_foo_public")
  protected def test_fuga = println("test_fuga")
  def public_foo = println("public_foo")
}

val foo = new Foo
foo.getClass.getDeclaredMethods.filter(_.getName.startsWith("test_"))
   .map(x=>{x.setAccessible(true);x}).foreach(_.invoke(foo,Array()))
inspect モジュールの出番。 classmethod も呼んでしまうのはどうにかならんかな。
1
2
3
4
5
import inspect

def call_tests(target):
    methods = [method for name, method in inspect.getmembers(target, inspect.ismethod) if name.startswith('test_')]
    for method in methods: method()
Pythonのクラスメソッドはあくまで
「呼んだときに第一引数にクラスオブジェクトが渡されるメソッド」
というだけなので、
単純に「xのメソッド」と言った場合には
classmethodやstaticmethodでラップしてあるメソッドも含まれるわけです。

なのでもしそれらを省きたければim_selfがインスタンス自身かどうかをチェックすればいいと思います。
下のコードならばtest_methodだけが呼ばれます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import inspect

class Foo():
    @classmethod
    def test_classmethod(cls):
        print cls

    @staticmethod
    def test_staticmethod(x):
        print x

    def test_method(self):
        print self

target = Foo()
for name, method in inspect.getmembers(target):
    if inspect.ismethod(method):
        if method.im_self == target:
            print "call", name
            method()
    
気づいたのですが、同一性の判定には、
if method.im_self is target: のように
==よりisを使った方がいいかもしれません。

なるほど、たしかに__eq__がオーバーライドされて同じインスタンスではなくてもTrueを返すようになっている可能性があるので==で判定してはダメですね。
おぉ。なるほど。と、おもったら、im_self をチェックするというのネタを以前自分のブログに書いていた orz それはそれとして、「x のメソッド」と表現したときに、メッセージのレシーバが x でないものを含めるかどうかというのはなかなか微妙な話かもしれない。
微妙な話ですね。
「単純に「xのメソッド」と言った場合には
classmethodやstaticmethodで
ラップしてあるメソッドも含まれる」
というのは言い過ぎだったかも。
「メソッド」という言葉がの定義と、
classmethodやstaticmethodの実装を
追わないと正確なことは言えなかったですね。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.lang.reflect.Method;

public class TestMethods {
    
    public static void test(Object target) {
        Class targetClass = target.getClass();
        Method[] methods = targetClass.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().startsWith("test_")) {
                try {
                    method.invoke(target);
                }
                catch (Exception e) {}
            }
        }
    }
    
    public static void main(String[] args) {
        test(new Test());
    }
    
}

class Test {
    public void test_public() {
        System.out.println("public method");
    }
    protected void test_protected() {
        System.out.println("protected method");
    }
    void test_package_private() {
        System.out.println("package private method");
    }
    private void test_private() {
        System.out.println("private method");
    }
}
setAccessible(true); が抜けているので、 private メソッドを実行すると IllegalAccessException が発生すると思います。 もっとも、Exception を握りつぶしていらっしゃるので、実行結果からは private メソッドがスルーされているように見えますが。
Squeak Smalltalk で。
1
target class selectors select: [:sel | sel beginsWith: #test]
スーパークラス定義のメソッドを考慮するのをすっかり
忘れていました(^_^;)。

ということで改めて。
1
target class allSelectors select: [:sel | sel beginsWith: #test]
以下の特徴を持つ。
・call_tests()の第一引数にはクラスとインスタンスのどちらでも渡せる。
・ベースクラスのメソッドも呼ぶか、指定したクラスのみのメソッドしか呼ばないかを指定可能。
・staticmethodを呼ぶかどうかを指定可能。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import types

class A:
	a1 = 1
	test_a1 = 1
	def __init__(self):
		self.a2 = 2
		self.test_a2 = 2
	def a3(self):
		print "a3 called!"
	def test_a3(self):
		print "test_a3 called!"

class B(A):
	def b1(self):
		print 'b1 called!'
	def test_b1(self):
		print 'test_b1 called!'
	@staticmethod
	def test_b2():
		print 'test_b2 called!'

def call_tests(obj, single_level=False, call_static=True):
	if isinstance(obj, types.InstanceType):
		cls = obj.__class__
	elif isinstance(obj, types.ClassType):
		cls = obj
		obj = obj()  # obj is bound to instance object.
	else:
		return
	# make dict of attributes
	names = cls.__dict__
	for base in cls.__bases__:
		names.update(base.__dict__)
	if single_level:
		for base in cls.__bases__:
			for name in base.__dict__:
				if hasattr(base, name):
					del names[name]
	for name in sorted(names):
		if name[:5] == 'test_':
			if call_static:
				try:
					getattr(obj, name)()  # EAFP
				except:
					continue
			else:
				f = names[name]
				if callable(f):
					f(obj)

if __name__ == '__main__':
	call_tests(B, 0, 0)
	print '==='
	call_tests(B, 1, 0)
	print '==='
	call_tests(B, 0, 1)
	print '==='
	call_tests(B, 1, 1)
# output:
# test_a3 called!
# test_b1 called!
# ===
# test_b1 called!
# ===
# test_a3 called!
# test_b1 called!
# test_b2 called!
# ===
# test_b1 called!
# test_b2 called!
以下を修正。
・cls.__bases__で、1つ上の階層のベースクラスしかとれていなかったので、再帰的にベースクラスを探すようにした。 
・継承階層で同じ名称のメソッドをオーバーライドしていた場合、一番子供側のメソッドを呼び出すようにした。 
・新型クラスに対して、 isinstance(obj, types.InstanceType) と isinstance(obj, types.ClassType) の
  両方ともFalseになってしまって対応していなかったので対応した。 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def call_tests(obj, single_level=False, call_static=True):
    if hasattr(obj, '__bases__'):
        # class object
        cls = obj
        obj = obj()  # obj is bound to instance object.
    else:
        if not hasattr(obj, '__class__'): return  # non class type
        # instance object
        cls = obj.__class__
    
    # make dict of attributes
    bases = []
    def base_classes(cls):
        if cls.__bases__:
            bases.extend(cls.__bases__)
            for base in cls.__bases__:
                base_classes(base)
    base_classes(cls)
    
    names = {}
    names.update(cls.__dict__)
    for base in bases:
        for attr, val in base.__dict__.items():
            if not names.has_key(attr):
                names[attr] = val
    
    if single_level:
        for base in bases:
            for name in base.__dict__:
                if names.has_key(name):
                    del names[name]
    
    # call function of attributes
    for name in sorted(names):
        if name[:5] == 'test_':
            if call_static:
                f = getattr(obj, name)
                if callable(f): f()
            else:
                f = names[name]
                if callable(f): f(obj)
すみません。PrologとPythonの言語の選択間違えました(;_;)。にしおさん、修正お願いします。
だいぶ言語が増えてきたので選びづらいですよね。JavaScriptとかで
今までに投稿したことのある言語を簡単に選べるようにしようとは思いつつ
まだできていません。
引数つきのメソッドでも呼び出せるようにしてみました。プリミティブ型なら 0(または false)、参照型なら null を渡すようにしています。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.lang.reflect.Method;

public class Sample {
    public static void callTest(Object target) throws Exception {
        Class c = target.getClass();
        Method[] ms = c.getMethods();
        for (int i = 0; i < ms.length; i++) {
            if (ms[i].getName().startsWith("test_")) {
                Class[] argTypes = ms[i].getParameterTypes();
                Object[] args = new Object[argTypes.length];
                for (int j = 0; j < argTypes.length; j++) {
                    if (argTypes[j] == Character.TYPE) {
                        args[j] = Character.valueOf((char) 0);
                    } else if (argTypes[j] == Boolean.TYPE) {
                        args[j] = Boolean.valueOf(false);
                    } else if (argTypes[j].isPrimitive()) {
                        args[j] = Byte.valueOf((byte)0);
                    } else {
                        args[j] = null;
                    }
                }
                ms[i].invoke(target, args);
            }
        }
    }

    public void test_1(int i) {
        System.out.println("test_1 called");
    }

    private int test_2() {
        System.out.println("test_2 called");
        return 0;
    }

    public static void test_3(char c) {
        System.out.println("test_3 called");
    }

    public void test_4(byte by, boolean b, short s, String str, double d) {
        System.out.println("test_4 called");
    }

    public static void main(String[] args) throws Exception {
        callTest(new Sample());
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.Reflection;
class Program
{
  class Test
  {
    void test_1() { Console.WriteLine("1"); }
    static void test_2() { Console.WriteLine("2"); }
  }
  static void Main()
  {
    CallTest(new Test());
  }
  static void CallTest(object o)
  {
    foreach (MethodInfo mi in o.GetType().GetMethods(
      BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
      if (mi.Name.StartsWith("test_")) mi.Invoke(o, new object[0]);
  }
}
題意の解釈によって色々書き方があると思いますが、一例として。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
obj <- list(
        test_f1 = function(){ print("f1") }, # OK
        text_f2 = function(){ print("f2") }, # NG
        txet_f3 = function(){ print("f3") }, # NG
        tesx_f4 = function(){ print("f4") }, # NG
        test_f5 = function(){ print("f5") }, # OK
        test_f6 = "f6"                       # NG (not a function)
)

dummy <- sapply(obj[grep("^test_", names(obj))], function(f){if(is.function(f)) f()})
もう少し題意を反映した感じで書いてみました。
Rのメソッドは、初めにgenericなメソッドを定義した後
「generic method名.クラス名」という形式で定義します。

generic methodを実行すると、第一引数のクラスによって
適切なメソッドが実行されます。

> test_f1(target)
[1] "f1"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# define generic methods
test_f1 <- function(x){ UseMethod("test_f1") }
text_f2 <- function(x){ UseMethod("text_f2") }
txet_f3 <- function(x){ UseMethod("txet_f3") }
tesx_f4 <- function(x){ UseMethod("tesx_f4") }
test_f5 <- function(x){ UseMethod("test_f5") }

# define class and object
setClass("doukaku", representation(x ="numeric", y="numeric"))
target <- new("doukaku", x=1, y=2)

# define methods
test_f1.doukaku <- function(x){ print("f1") } # OK
text_f2.doukaku <- function(x){ print("f2") } # NG
txet_f3.doukaku <- function(x){ print("f3") } # NG
tesx_f4.doukaku <- function(x){ print("f4") } # NG
test_f5.doukaku <- function(x){ print("f5") } # OK

dummy <- sapply(grep("^test_", methods(class=attr(target, "class")), value=TRUE),
                  (function(x) do.call(x, c(target))))
C言語にメソッドはないので共有ライブラリの関数を列挙し、呼び出すようにしました。

1. 共有ライブラリをコンパイルします。
% gcc --shared call_tests.c -o tests.so

2. 本体をコンパイルします。
% gcc call_tests.c -o call_tests -ldl -lbfd

3. 実行します
% ./call_tests ./tests.so
hello2
hello1
hello3
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <bfd.h>

void test_hello1()
{
    printf("hello1\n");
}
void test_hello2()
{
    printf("hello2\n");
}
void test_hello3()
{
    printf("hello3\n");
}

int main(int argc, char *argv[]){
    void *handle;
    void (*func)();
    bfd *abfd;
    asymbol *store;
    int symcount;
    void *minisyms;
    size_t size;
    int i;
    bfd_byte *from, *fromend;
    asymbol *sym;
    const char *name;

    if(argc < 2) return EXIT_FAILURE;
    handle = dlopen(argv[1], RTLD_LAZY);
    if(!handle) return EXIT_FAILURE;
    abfd = bfd_openr(argv[1], NULL);
    if(!abfd) return EXIT_FAILURE;
    bfd_check_format(abfd, bfd_object);
    store = bfd_make_empty_symbol(abfd);
    symcount = bfd_read_minisymbols(abfd, 0, &minisyms, &size);
    from = (bfd_byte *)minisyms;
    fromend = from + symcount * size;
    for (; from < fromend; from += size){
        sym = bfd_minisymbol_to_symbol(abfd, 0, from, store);
        if(sym->flags != (BSF_FUNCTION | BSF_GLOBAL)) continue;
        name = bfd_asymbol_name(sym);
        if(strncmp(name, "test_", 5)) continue;
        func = dlsym(handle, name);
        func();
    }
    
    bfd_close(abfd);
    dlclose(handle);
    return EXIT_SUCCESS;
}
GaucheではCommonLispと同様に、メソッドはクラスでなく総称関数に所属します。
ここではモジュールの中から探してみることにします。

使い方の例:
(define-module foo
  (define (test_1) (print 1))
  (define (test_2) (print 2))
  (define test_3 "I'm variable!")
  (define (test_4) (print 4)))

(call-tests (find-module 'foo))
1
2
3
4
5
6
7
(define (call-tests module)
  (hash-table-for-each (module-table module)
                       (lambda (k v)
                         (and-let* ([ (#/^test_/ (x->string k)) ]
                                    [val (global-variable-ref module k)]
                                    [ (procedure? val) ])
                           (val)))))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Object
  def call_test(prvt = false)
    test_methods = self.methods.grep(/\Atest_/)
    test_methods += self.private_methods.grep(/\Atest_/) if prvt
    test_methods.each{|method|
      yield method, self.__send__(method)
    }
  end
end

if $0 == __FILE__
  class Sample
    def test_a
      true
    end
  end

  o = Sample.new
  o.call_test{|m, r|
    puts "#{m} #{r}"
  }
end
CPANだよりですが。。。^^;
1
2
3
4
5
6
7
8
9
#!/usr/local/bin/perl

use target;
use Class::Inspector;

foreach my $f (@{(Class::Inspector->functions( 'target' ))[0]}) {
    print eval{target->$f()} if ($f =~ /^test_.+$/);
}
exit;
Class::Inspector, Scalar::Util使用版
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/perl

package Foo;

{
    no strict 'refs';

    for my $method (qw/foo bar baz test_foo test_bar test_baz/) {
        *{"Foo::$method"} = sub {
            print $method . "\n";
        };
    }
}

sub new {
    bless {} => shift;
}

package main;

use strict;
use warnings;

use Scalar::Util qw(blessed);
use Class::Inspector;

sub call_methods_by_regex {
    my ($target, $regex) = @_;

    return unless (my $class = blessed($target));
    return unless (ref $regex eq 'Regexp');

    my @methods = grep { /$regex/o } @{Class::Inspector->methods($class, 'public')};

    for my $method (@methods) {
        $target->$method();
    }
}

call_methods_by_regex(Foo->new, qr/^test_/);
CPANに頼らない版
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/perl

package Foo;

{
    no strict 'refs';

    for my $method (qw/foo bar baz test_foo test_bar test_baz/) {
        *{"Foo::$method"} = sub {
            print $method . "\n";
        };
    }
}

sub new {
    bless {} => shift;
}

package main;

use strict;
use warnings;

{
    no strict 'refs';
    sub call_methods_by_regex {
        my ($target, $regex) = @_;
        my $class = ref $target;

        return if (!$class || $class =~ /^(SCALAR|ARRAY|HASH|CODE|GLOB|LVALUE)$/);
        return unless (ref $regex eq 'Regexp');

        my @methods = 
            grep { *{${$class . "::"}{$_}}{CODE} } 
            grep { /$regex/o } 
                keys %{$class . "::"};
        
        for my $method (@methods) {
            $target->$method();
        }
    }
}

call_methods_by_regex(Foo->new, qr/^test_/);
いけるのはpublishedで宣言されたもののみです。
ちなみにお題のタイトルのようにメソッド名一覧を表示する場合は、
Pos関数内で使ってるPShortString(...)^をWritelnしてやればOKです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
program EnumMethodNames;

{$APPTYPE CONSOLE}

type
    THoge = class(TObject)
    published
        procedure test_foo;
    end;
    THogeEx = class(THoge)
    published
        procedure test_bar;
        procedure foobar;
    end;
    THogeMethod = procedure of object;

procedure THoge.test_foo;
begin
    Writeln('foo');
end;

procedure THogeEx.test_bar;
begin
    Writeln('bar');
end;

procedure THogeEx.foobar;
begin
    Writeln('foobar');
end;

procedure EnumMethods(target: TObject);
var
    ref: TClass;
    table: PChar;
    count: Word;
    method: TMethod;
begin
    ref := target.ClassType;
    while ref <> nil do begin
        table := PPointer(PChar(ref) + vmtMethodTable)^;
        if Assigned(table) then begin
            count := PWord(table)^;
            Inc(table, SizeOf(Word));
            while count > 0 do begin
                if Pos('test_', PShortString(table+SizeOf(Word)+SizeOf(Pointer))^) = 1 then begin
                    method.Code := PPointer(table+SizeOf(Word))^;
                    method.Data := target;
                    THogeMethod(method);
                end;
                Inc(table, PWord(table)^);
                Dec(count);
            end;
        end;
        ref := ref.ClassParent;
    end;
end;

var
    Hoge: THoge;

begin
    Hoge := THogeEx.Create;
    try
        EnumMethods(Hoge);
    finally
        Hoge.Free;
    end;
end.
コンパイルには -package ghc が必要。
引数で指定されたモジュールが export し、且つ test_ で始まる全部の IO モナドを実行する。
型は IO () でなければならず、そうでないものが存在したら実行時エラーになる。
GHC 6.6.1 以外の GHC を使う場合は ghcLibDir の値を訂正する事。

% ghc --make Main -package ghc
% ./Main Test
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import qualified GHC
import qualified DynFlags
import qualified Name
import Data.List
import System

{-
例: Test.hs

module Test where

test_foo :: IO ()
test_foo = putStrLn "called test_foo"

test_bar :: IO ()
test_bar = putStrLn "called test_bar"

% ghc --make Test -package-name test
% ar cqs libHSTest.a Test.o
% ld -r --whole-archive -o HSTest.o libHSTest.a (MacOS X 以外の場合)
% ld -r -all_load -o HSTest.o libHSTest.a       (MacOS X の場合)
-}

ghcLibDir :: String
ghcLibDir = "/usr/local/lib/ghc-6.6.1" -- % ghc --print-libdir

main = GHC.defaultErrorHandler DynFlags.defaultDynFlags $
       do [modName] <- getArgs
          session <- GHC.newSession GHC.Interactive (Just ghcLibDir)
          f0      <- GHC.getSessionDynFlags session
          GHC.setSessionDynFlags session f0 { GHC.hscTarget = GHC.HscInterpreted }

          t  <- GHC.guessTarget modName Nothing
          GHC.addTarget session t
          f  <- GHC.getSessionDynFlags session
          sf <- GHC.defaultCleanupHandler f (GHC.load session GHC.LoadAllTargets)
          case sf of
            GHC.Failed  
                -> fail "Failed to load the module!"
            GHC.Succeeded
                -> do self <- GHC.findModule session (GHC.mkModuleName modName) Nothing
                      runNullaryIOMonads session self (any (== "test_") . inits)

runNullaryIOMonads :: GHC.Session -> GHC.Module -> (String -> Bool) -> IO ()
runNullaryIOMonads session self f
    = do Just modInfo <- GHC.getModuleInfo session self
         
         let allFuncs      = map Name.getOccString $ GHC.modInfoExports modInfo
             selectedFuncs = filter f allFuncs

         GHC.setContext session [self] []
         mapM_ run selectedFuncs
    where
      run :: String -> IO ()
      run fName
          = GHC.runStmt session (fName ++ " :: IO ()")
            >> return ()

	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Foo {
  test_foo(){println("test_foo")}
  test_foo2(){println("test_foo2")}
  test_foo3(){println("test_foo3")}
  public_foo(){println("public_foo")}
}

f = Foo()
for(m:f.class.methods[{m->m.name.startsWith("test_")}]){
  m.invoke(f, [])
}
1
2
3
$target | get-member | 
  where { $_.name -like "test_*" } |
  foreach { invoke-expression ("`$target." + $_.name + "()") }
誰でも思いつく単純な回答
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Target
 def test_foo
  p "foo"
 end
 def test_bar
  p "bar"
 end
 def test_baz
  p "baz"
 end
 def not_test
  p "not test!!"
 end
end
  #=> nil

target = Target.new
  #=> #<Target:0x107d1d0>

target.methods.grep(/^test_.*$/).each {|i| target.__send__ i }
"baz"
"foo"
"bar"
  #=> ["test_baz", "test_foo", "test_bar"]
引数0で。
1
2
3
4
5
Public Sub CallMethods(ByVal obj As Object)
    For Each m As MethodInfo In obj.GetType.GetMember("test_*", MemberTypes.Method, BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.Static)
        m.Invoke(obj, Nothing)
    Next
End Sub
指定された *.awk ファイルをサーチして、
test_XXX という関数をすべて実行するような .awk ファイルを作り、
その .awk ファイルを実行します。
対象にするのは、
^function +test_.*
というパターンにマッチする関数定義だけです。

ex)
> gawk -f testrunner.awk *.awk

test_XXX をすべて呼び出すために、_tmp_runner.awk というファイルを
生成します。このファイルは削除せず残します。
_tmp_runner.awk と同時に、test_XXX が含まれていた .awk ファイルをすべて、
読み込みます。それ以外の .awk ファイルは読みません。
このあたりの仕様は、実用性を考えると検討したほうがいいかも。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
BEGIN {
	TMPFILE = "_tmp_runner.awk"
	awk_cmd = ARGV[0]
	targets = " "

	print "BEGIN {" > TMPFILE
}

/^function +test_.*\(/ {
	fname = gensub(/^function +(test_[^(]*)\(.*$/, "\\1", "")
	printf "\t%s()\n", fname >> TMPFILE

	if(index(targets, " -f " FILENAME " ") == 0) {
		targets = targets "-f " FILENAME " "
	}
}

END {
	print "}" >> TMPFILE
	close(TMPFILE)

	CMD = sprintf("%s -f%s %s", awk_cmd, TMPFILE, targets)
	system(CMD)
}
とりあえず、シェル内で既に定義されている全ての関数の中から、test_ で始まるものを実行してみました。
1
2
3
declare -fp  | grep '^test_.*() $' | while read func paren; do
  $func
done
super クラスも検索するようにしてみました。

「bar: || "bar".p」というのは引数なしの lambda 関数を bar に束縛しているような感じです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
String::start_with: method(sub) {
    return this.split("").take(sub.length).to_a.join("") == sub;
}

TargetTest: class {
    test_foo: || "test_foo".p;
    test_bar: || "test_bar".p;
    foo: || "foo".p;
    bar: || "bar".p;
}
TargetTest2: class(TargetTest) {
    test_foo: || "test_foo_sub".p;
    test_baz: || "test_baz".p;
}

TestRunner: class {
    suite: method(target, inherited_too: true) fiber {
        klasses: [target];
        if (inherited_too) {
            klasses ~= target.each_ancestor.to_a;
        }

        override: Map();
        klasses {|klass|
            klass.each_member {|name, x, meth|
                if (! override[name] && name.start_with("test_")) {
                    yield meth;
                }
               override[name] = true;
            }
        }
    }

    run: method(target) {
        suite(target) {
            it();
        }
    }
}

runner: TestRunner();
"---".p;
runner.run(TargetTest2);
"---".p;
runner.run(TargetTest);

	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
:- module(target, []).
test_a :- writeln(test_a).
test_b :- writeln(test_b).
test_x(_) :- writeln(test_x).
no_test :- writeln(no_test).

% 指定されたモジュールの'test_'で始まる引数0の述語を全て実行
call_tests(Module) :-
	forall((  current_predicate(Module:Predicate/0),
	          atom_prefix(Predicate, test_)),
	       Module:Predicate).

:- call_tests(target).
%=> test_a test_b

	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Object callMethods := method(str,
    self slotNames foreach(slotname,
        if(slotname beginsWithSeq(str),
            self perform(slotname)
        )
    )
)

obj := Object clone
obj test_1 := method(
    "test_1" println
)
obj foobar := method(
    "foobar" println
)
obj test_2 := method(
    "test_2" println
)
obj test3 := method(
    "test3" println
)
obj test_4 := "fuga"

obj callMethods("test_")
Perlでワンライナーで書いているブログがあったのでリンク。

	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#import <Foundation/Foundation.h>
#import <objc/objc-class.h>

@interface TestClass : NSObject {
}
+ (void)test_class_method;
+ (void)testClassMethod;
- (void)test_instance_method;
- (void)testInstanceMethod;
@end

@implementation TestClass
+ (void)test_class_method {
	NSLog( @"test_class_method called" );
}
+ (void)testClassMethod {
	NSLog( @"testClassMethod called" );
}
- (void)test_instance_method {
	NSLog( @"test_instance_method called" );
}
- (void)testInstanceMethod {
	NSLog( @"testInstanceMethod called" );
}
@end

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
	Class cls[2] = { [TestClass class]->isa, [TestClass class] };
	TestClass* testClass = [[[TestClass alloc] init] autorelease];
	
	//forループ1回目はクラスメソッド、2回目はインスタンスメソッドの実行
	for ( int i = 0; i < 2; i++ ) {
		void* iterator = 0;
		struct objc_method_list* mlist;
		
		while ( mlist = class_nextMethodList( cls[i], &iterator ) ) {
			for ( int j = 0; j < mlist->method_count; j++ ) {
				Method method = mlist->method_list + j;
				NSString* methodName = [NSString stringWithUTF8String:(const char*)method->method_name];
				if ( [methodName hasPrefix:@"test_"] ) {
					if ( i == 0 )
						[TestClass performSelector:method->method_name];
					else
						[testClass performSelector:method->method_name];
				} else
					NSLog( @"Skip %@", methodName );
			}
		}
	}
	
    [pool release];
    return 0;
}
foo:get(erlang). とかで使えます。
1
2
3
4
5
6
-module(foo).
-export([get/1]).

get(PackageName) ->
  [X || {X, Y} <- proplists:get_value(exports, PackageName:module_info()),
    lists:suffix("_test", atom_to_list(X))].
お題を誤って理解してしまった為、前回投稿したコードは、_test が関数名の末尾に付いた関数の一覧を返します。

今回、投稿するコードは、test_ が関数名の先頭に付いた関数の一覧を返します。

修正箇所は、lists:suffix/2 を lists:prefix/2 にかえただけです。
1
2
3
4
5
6
-module(foo).
-export([get/1]).

get(PackageName) ->
  [X || {X, Y} <- proplists:get_value(exports, PackageName:module_info()),
    lists:prefix("test_", atom_to_list(X))].

[obj].class.Operations で、[obj]のクラスのメソッド一覧を取得できます。 上記で取得できるのはOperationクラスで、 Operation.Name でオペレーション名を取得できます。

念のためoperation, functionの両方で試しましたが、 両方ともOperationsで取得できました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import java.lang.System;

public class TestClass {
    public function test_function();
    public operation test_operation();
    public function notest_function();
    public operation notest_operation();
}

var a = new TestClass();

var testNames = select op.Name from op in a.class.Operations 
    where op.Name.startsWith("test_");

for(name in testNames) {
    System.out.println(name);
}

Privateメソッドも実行するために.

1
2
3
4
5
6
7
STR = 'test_'
module ExeTest_
  def exe_test_(x); eval(x) end
end
target = Object.new
target.extend ExeTest_
target.methods.grep(/\A#{STR}/).each{|x| target.exe_test_(x)}
1
2
3
4
5
outputMethod( target, "test_" )

def outputMethod(object, prefix){
    object.class.metaClass.methods.findAll{ it.name.startsWith(prefix) }.each{ println it.name }
}

暇潰しにやってみた。

反則っちゃ反則なんだけどPCLベースのCLOS実装やってる処理系だと似たようなことできるんちゃうかな

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(use-package :sb-mop)
(defclass c0 ()
  ((x :initarg :x :accessor x)
   (y :initarg :y :accessor y)))
(defgeneric magnitude (obj))
(defmethod magnitude ((obj c0))
  (sqrt (+ (expt (x obj) 2) (expt (y obj) 2))))
(mapcar #'(lambda (m)
        (slot-value (slot-value m 'SB-PCL::%GENERIC-FUNCTION)
            'SB-PCL::NAME))
    (car (slot-value (find-class 'c0) 'SB-PCL::DIRECT-METHODS)))

ここでまさかのC++。ええと、WindowsでCOM使っています。VBやスクリプト言語用のIDispatch/ITypeInfoでリフレクションに相当する情報が得られるので、そこからメソッド名test_で始まるものを選び、IDipatchのInvokeメソッドで呼び出しています。

main関数より後ろはヘルパ群、ITypeInfoの型情報やIDispatch対応オブジェクトの作成などといった関数が並んでいます。

ITypeInfoはIDLから作るしかないと思っていたところ、このプログラムのようにCreateDispTypeInfo関数を使うとC++ソースコードの記述だけでITypeInfoを作れることを知りました。これを知らなければこの課題をC++でやることはなかったでしょう。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
//#define WIN32_LEAN_AND_MEAN

#include <iostream>

#include <ole2.h>
#include <windows.h>
#include <comdef.h>

#include <boost/implicit_cast.hpp>
using boost::implicit_cast;

// ここでは再発明しましたが、COMSTL (WinSTLの兄弟)のcom_exceptionがお薦め
class ComException
{
public:
    explicit ComException(HRESULT hr) : hr(hr) {}
    ComException(const ComException& rhs) : hr(rhs.hr) {}
    ComException& operator =(const ComException& rhs)
    {
        hr = rhs.hr;
        return *this;
    }
    HRESULT GetErrorCode() const
    {
        return hr;
    }
    //swapは使わないので省略
public:
    HRESULT hr;
};

void ThrowIfFailed(HRESULT hr)
{
    if (FAILED(hr))
    {
        throw ComException(hr);
    }
}

// COM関係のヘッダに#define interface structとある。
interface __declspec(uuid("c70cd2de-1285-4ec5-a78a-5600cf6fc79a")) __declspec(novtable)
ITest : IUnknown
{
    STDMETHOD(test_Hello)() PURE;
    STDMETHOD(test_Goodbye)() PURE;
    STDMETHOD(piyo_Dummy)() PURE;
    // マクロを全て展開するとこうなる。
    // virtual __declspec(nothrow) HRESULT __stdcall test_Hello() = 0;
};

// ITestの実装例
class TestImpl : public ITest
{
public:
    TestImpl() : refCount(1) {}

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv)
    {
        if (riid == __uuidof (IUnknown))
        {
            *ppv = implicit_cast<IUnknown*>(this);
        }
        else if (riid == __uuidof (ITest))
        {
            *ppv = implicit_cast<ITest*>(this);
        }
        else
        {
            *ppv = 0;
            return E_NOTIMPL;
        }
        return S_OK;
    }

    ULONG STDMETHODCALLTYPE AddRef()
    {
        return ++refCount;
    }

    ULONG STDMETHODCALLTYPE Release()
    {
        if (--refCount)
        {
            delete this;
            return 0;
        }
        return refCount;
    }

    HRESULT STDMETHODCALLTYPE test_Hello()
    {
        std::cout << "Hello, world!" << std::endl;
        return S_OK;
    }
    HRESULT STDMETHODCALLTYPE test_Goodbye()
    {
        std::cout << "Goodbye!" << std::endl;
        return S_OK;
    }
    HRESULT STDMETHODCALLTYPE piyo_Dummy()
    {
        return E_NOTIMPL;
    }
private:
    ULONG refCount;
};

// もし、IホゲPtrが無くてエラーになるなら。
//_COM_SMARTPTR_TYPEDEF(ITypeInfo, __uuidof (ITypeInfo));
//_COM_SMARTPTR_TYPEDEF(IUnknown, __uuidof (IUnknown));
//_COM_SMARTPTR_TYPEDEF(IDispatch, __uuidof (IDispatch));

ITypeInfoPtr GetFirstInterfaceTypeInfo(ITypeInfo* pti);
IDispatchPtr CreateDispTest(ITypeInfo* pti);
ITypeInfoPtr GetTestTypeInfo();

int main()
{
    try
    {
        ThrowIfFailed(OleInitialize(0));
        ITypeInfoPtr pti = GetTestTypeInfo(); //クラスを表すITypeInfo
        IDispatchPtr target = CreateDispTest(pti); //オブジェクトの作成
        ITypeInfoPtr ptiInterface = GetFirstInterfaceTypeInfo(pti); //ITestを表すITypeInfo
        TYPEATTR* pta;
        ThrowIfFailed(ptiInterface->GetTypeAttr(&pta));
        //DISPPARAMS dp2 = {};
        //ThrowIfFailed(pdisp->Invoke(2, IID_NULL, LOCALE_SYSTEM_DEFAULT,
        //    DISPATCH_METHOD, &dp2, 0, 0, 0));
        for (int i = 0; i < pta->cFuncs; ++i)
        {
            // i個目の関数のMEMIDを取得
            FUNCDESC* pfd;
            ThrowIfFailed(ptiInterface->GetFuncDesc(i, &pfd));
            // その関数の名前を取得
            _bstr_t name;
            UINT count;
            ThrowIfFailed(ptiInterface->GetNames(pfd->memid, name.GetAddress(), 1, &count));
            if (wmemcmp(name, L"test_", 5) == 0)
            {
                 // その関数がtest_で始まっていれば引数なしで呼び出す
                DISPPARAMS dp = {};
                ThrowIfFailed(target->Invoke(pfd->memid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                    DISPATCH_METHOD, &dp, 0, 0, 0));
            }
            ptiInterface->ReleaseFuncDesc(pfd);
        }
        ptiInterface->ReleaseTypeAttr(pta);
    }
    catch(ComException const& e)
    {
        LPSTR msg;
        FormatMessageA(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
            NULL, e.GetErrorCode(), LANG_USER_DEFAULT,
            reinterpret_cast<LPSTR>(&msg), 0, 0);
        std::cerr << msg << std::endl;
        LocalFree(msg);
    }
    OleUninitialize();
}

// CreateDispTypeInfoを使ったお手軽ITypeInfoの作成。
ITypeInfoPtr GetTestTypeInfo()
{
    static METHODDATA md[] =
    {
        {
            /* szName = */ L"test_Hello",
            /* ppdata = */ 0, //引数情報
            /* dispid = */ 1, //IDispatch::Invokeで呼び出すときの識別番号。適当な正の値で良い。
            /* iMeth = */ 3, //VTBL上のインデックス
            /* cc = */ CC_STDCALL, //呼出規約
            /* cArgs = */ 0, //引数の個数
            /* wFlags = */ DISPATCH_METHOD, //メソッド・プロパティの種別
            /* vtReturn = */ VT_EMPTY, //戻り値の型
        },
        {
            /* szName = */ L"test_Goodbye",
            /* ppdata = */ 0,
            /* dispid = */ 2,
            /* iMeth = */ 4,
            /* cc = */ CC_STDCALL,
            /* cArgs = */ 0,
            /* wFlags = */ DISPATCH_METHOD,
            /* vtReturn = */ VT_EMPTY,
        },
        {
            /* szName = */ L"piyo_Dummy",
            /* ppdata = */ 0,
            /* dispid = */ 3,
            /* iMeth = */ 5,
            /* cc = */ CC_STDCALL,
            /* cArgs = */ 0,
            /* wFlags = */ DISPATCH_METHOD,
            /* vtReturn = */ VT_EMPTY,
        },
    };
    static INTERFACEDATA id =
    {
        md, ARRAYSIZE(md), // ARRAYSIZEは<windows.h>の中で定義されている配列の要素数を求めるマクロ
    };
    ITypeInfoPtr pti;
    ThrowIfFailed(CreateDispTypeInfo(&id, LOCALE_SYSTEM_DEFAULT, &pti));
    return pti;
}

// TestImplのディスパッチオブジェクトを作成。
// ただし、ITypeInfを引数として与えること。
IDispatchPtr CreateDispTest(ITypeInfo* pti)
{
    TestImpl* p = new TestImpl;
    IUnknown* punkDispObj;
    ThrowIfFailed(CreateStdDispatch(p, p, pti, &punkDispObj));
    return punkDispObj;
}

// あるITypeInfoから、1番目に継承もしくは実装しているインタフェースを返すヘルパ。
ITypeInfoPtr GetFirstInterfaceTypeInfo(ITypeInfo* pti)
{
    ITypeInfoPtr res;
    HREFTYPE hrt;
    ThrowIfFailed(pti->GetRefTypeOfImplType(0, &hrt));
    ThrowIfFailed(pti->GetRefTypeInfo(hrt, &res));
    return res;
}

Index

Feed

Other

Link

Pathtraq

loading...