追記 2024/06/27 |
この記事で、「関数をオブジェクトとして扱えるか」「関数を変数に格納できるか」について、「できる」と書きましたが、この記事で書いた内容では、全くできていませんでした。 |
先に結論
試行錯誤の結果、タイトルに書いたことは、「関数をオブジェクトとして扱える」、「関数を変数に格納できる」の両者ともできるという結果になりました。
今回の方法を考えるきっかけ
VBAでツールを作っていて、「プログラミングでの抽象化」ということを考えていた時、「猫型の蓄音機は 1 分間に 45 回にゃあと鳴く」というページを見ました。このページのnoteという個所に、「高階関数」というものが紹介されていました。
この「高階関数」は数学で「変数に関数を代入する」というようなもののようでしたが、これを見ていてふと以前から、プログラムでも「変数に関数を代入する(格納する)」ことを考えていたことを思い出しました。
ただし、以前考えていたのは、正確には「変数を使って、条件分岐などで、使用される関数を変える(関数を選択する)」に近かったです。
以前考えていたのは、多分、VB.NETやJavaであれば、何らかのクラスをスーパークラスとすれば、これを継承した複数のサブクラスは、スーパークラスの変数にどれも格納することができる。ですのので、条件によって変数に格納するオブジェクトを変えれば、それぞれのメソッドが実行されるので、「条件によって呼ばれる関数(オブジェクトのメソッド)を変える」ということができる。ということです。また、インターフェースを使用しても似たようなことができると思います。
ただし、これがうまくいっても、変数に格納されるのは、メソッド(関数)が属しているオブジェクトです。
先ほどのページを見ていてなぜかふと「メソッドが何らかのオブジェクト型に定義されていれば(戻り値が何らかのオブジェクトであれば)そのオブジェクト型の変数に格納できる」ということを思いつき、これなら「継承」を書くこともできず、インタフェースも定義できないVBAでも、「関数を変数に格納」して、「条件によって呼ばれる関数(オブジェクトのメソッド)を変える」ということができるのではないか、と思いました。
その方法について、今回書きます。
方法とソース
1 メソッドを、何らかのオブジェクト型に定義して、そのオブジェクト型の変数に格納する。(そのオブジェクトの基本形となる形を使用した例)
① メソッドを、何らかのオブジェクト型に定義する。(その基本形)
タイトルの通り、メソッドを何らかのオブジェクトのオブジェクト型に定義します。これは、戻り値の型をそのオブジェクト型に定義するということになります。「何らかオブジェクト」の基本形として。「戻り値」を格納するための変数を宣言して、これをプロパティとしたクラスを定義します。
「変数に格納する関数」は、ほかの何かのオブジェクトのメソッドとなりますが、戻り値が「オブジェクト」となるため、本来ならメソッドの「戻り値」として返したかった値は戻り値で返せなくなします。その本来戻り値として返したかった値を、このプロパティに設定します。
FunctionBase
Option Explicit
Public intReturn As Integer
コードとしては上記のみとなります。
② 変数に格納したい関数(実際には何かのオブジェクトのメソッド)を、上記①のオブジェクト型に定義する。
今回は、今回変数に格納する関数は、何かのオブジェクトのメソッドとします。この、変数に格納することになるであろうメソッドを、上記①のオブジェクト型にします。
例えば、
本来
Public Function Addition(x As Integer, y As Integer) As Integer
Addition = x + y
End Function
となるメソッド(関数)を
Public Function Addition(x As Integer, y As Integer) As FunctionBase
Set Addition = New FunctionBase
Addition.intReturn = x + y
End Function
のように定義する、ということです。
下記に示すのが、今回の例となるオブジェクト全体です。
計算するためのオブジェクトのようなものをイメージしました。
Calculations
Option Explicit
Public Function Addition(x As Integer, y As Integer) As FunctionBase
Set Addition = New FunctionBase
Addition.intReturn = x + y
End Function
Public Function Subtraction(x As Integer, y As Integer) As FunctionBase
Set Subtraction = New FunctionBase
Subtraction.intReturn = x - y
End Function
Public Function Multiplication(x As Integer, y As Integer) As FunctionBase
Set Multiplication = New FunctionBase
Multiplication.intReturn = x * y
End Function
Public Function Division(x As Integer, y As Integer) As FunctionBase
Set Division = New FunctionBase
Division.intReturn = x / y
End Function
③ このオブジェクトを使用する際に、①のオブジェクト型で宣言した変数に、②のオブジェクトのメソッドを格納できるので、条件分岐などで使用する。
Module1
Option Explicit
Sub Call_Macro1()
Macro1 "+", 10, 20
Macro1 "-", 10, 20
Macro1 "*", 40, 30
Macro1 "/", 40, 30
Macro1 "A", 40, 30
End Sub
Sub Macro1(calcu_mode As String, x As Integer, y As Integer)
Dim obj_Calcu As Calculations
Dim obj_FuncCalcu As FunctionBase
Set obj_Calcu = New Calculations
Set obj_FuncCalcu = New FunctionBase
If calcu_mode = "+" Then
Set obj_FuncCalcu = obj_Calcu.Addition(x, y)
ElseIf calcu_mode = "-" Then
Set obj_FuncCalcu = obj_Calcu.Subtraction(x, y)
ElseIf calcu_mode = "*" Then
Set obj_FuncCalcu = obj_Calcu.Multiplication(x, y)
ElseIf calcu_mode = "/" Then
Set obj_FuncCalcu = obj_Calcu.Division(x, y)
Else
obj_FuncCalcu.intReturn = 0
End If
MsgBox "答えは " & obj_FuncCalcu.intReturn & " です"
End Sub
上記のように、変数にメソッドを格納して、条件分岐することができます。
このようなことができるようになることが今回の目的です。
2 変数に格納する メソッド(関数)の型となるオブジェクトに、メソッドなどを追加する。(関数の型となるオブジェクトに機能追加した場合の使用例)
さらに、関数の型となるオブジェクトをエンハンスすることで、いろいろできそうです。
FunctionResultOut
Option Explicit
Public intReturn As Integer
Sub DsipMsg()
MsgBox "答えは " & Me.intReturn & " です"
End Sub
Sub WriteResult()
Debug.Print "答えは " & Me.intReturn & " です"
End Sub
Sub DsipMsgAndWrite()
Me.DsipMsg
Me.WriteResult
End Sub
Calculations2
Option Explicit
Public Function Addition(x As Integer, y As Integer) As FunctionResultOut
Set Addition = New FunctionResultOut
Addition.intReturn = x + y
End Function
Public Function Subtraction(x As Integer, y As Integer) As FunctionResultOut
Set Subtraction = New FunctionResultOut
Subtraction.intReturn = x - y
End Function
Public Function Multiplication(x As Integer, y As Integer) As FunctionResultOut
Set Multiplication = New FunctionResultOut
Multiplication.intReturn = x * y
End Function
Public Function Division(x As Integer, y As Integer) As FunctionResultOut
Set Division = New FunctionResultOut
Division.intReturn = x / y
End Function
Module1
Sub Call_Macro2()
Macro2 "M", 10, 20
Macro2 "W", 30, 20
Macro2 "MR", 40, 30
End Sub
Sub Macro2(Out_mode As String, x As Integer, y As Integer)
Dim obj_Calcu As Calculations2
Dim obj_FuncCalcu As FunctionResultOut
Set obj_Calcu = New Calculations2
Set obj_FuncCalcu = New FunctionResultOut
Set obj_FuncCalcu = obj_Calcu.Addition(x, y)
If Out_mode = "M" Then
obj_FuncCalcu.DsipMsg
ElseIf Out_mode = "W" Then
obj_FuncCalcu.WriteResult
Else
obj_FuncCalcu.DsipMsgAndWrite
End If
End Sub
書いている私も、これでどのようなことができそうか、まだよくわかっていないです。しかし、こんなことできるんだ、というような気持ちだし、こんなことできるならもっと早く気づいていればよかった、という気もします。
また、多分、変数に格納したい関数は、引数の数などに関係なく格納でき、C言語のポインタ関数ともちょっと違う気がします。(C言語のポインタ関数については、概要は知っていましたが、C言語は経験も浅く、詳しくは知りません。)
これでは、何でもありなのでは?という気もしますが、その分、むしろ使い勝手が悪いかもしれない、ということも考えています。
今は、ほかにやりたいこともあるし、応用の仕方までは研究できそうにないですが、ぜひ活用してほしいと思っています。