OCamlの訪問者デザインパターン
-
03-07-2019 - |
質問
OCamlのOO構造と型システムを使用して訪問者デザインパターンを実装しようとしていますが、要素のインスタンス化時に問題が発生しています。
class virtual ['hrRep] employee = object
method virtual receiveEvaluation : 'hrRep -> unit
method virtual getName : string
end;;
class ['hrRep] accountant myName = object (self : 'a)
inherit ['hrRep]employee
val name = myName
method receiveEvaluation rep = rep#visitAccountant self
method getName = name
end;;
class ['hrRep] salesman myName = object (self : 'a)
inherit ['hrRep]employee
val name = myName
method receiveEvaluation rep = rep#visitSalesman self
method getName = name
end;;
class virtual ['accountant, 'salesman] hrRep = object (self)
method virtual visitSalesman : 'salesman -> unit
method virtual visitAccountant : 'accountant -> unit
end;;
class ['employee, 'salesman] lowerLevelHRRep =
object (self) inherit ['employee, 'salesman]hrRep
method visitSalesman s = print_endline ("Visiting salesman "^s#getName)
method visitAccountant a =
print_endline ("Visiting accountant "^a#getName)
end;;
let s1 : (<visitSalesman : 'a -> unit>) salesman = new salesman "Bob";;
let a1 : (<visitAccountant : 'a -> unit>) accountant = new accountant "Mary";;
let s2 : (<visitSalesman : 'a -> unit>) salesman = new salesman "Sue";;
let h1 : (<getName : string>, <getName : string>) lowerLevelHRRep = new lowerLevelHRRep;;
s1#receiveEvaluation h1;;
コンパイル時に表示されるエラー:
The type of this expression, <visitSalesman : 'a -> unit; _.. > salesman as 'a,
contains type variables that cannot be generalized.
ただし、コードは、 salesman
をインスタンス化する行を除いてコンパイルされます。
クラスの機能を維持しながら、 salesman
をインスタンス化するにはどうすればよいですか?
編集 receiveEvaluationの呼び出しで受信したエラー:
This expression has type (<getName:string>, < getName:string>) lowerLevelHRRep
but is here used with type <visitSalesman : 'a salesman -> unit > as 'a.
2番目のオブジェクトタイプにはメソッド visitAccountant
がありません。
解決
編集-回答を3つの主要なポイントに分けました。最初のコンパイルエラーの解決、再帰的ソリューション、パラメーター化されたソリューション
コンパイルエラーの解決
コードはトップレベルで正常に動作することに注意してください:
# let s = new salesman ();;
val s : < visitSalesman : 'a -> unit; _.. > salesman as 'a = <obj>
この種のコンパイルエラーは、通常、型注釈を追加することで解決され、コンパイラが型を理解するのに役立ちます。トップレベルが親切にそれを教えてくれたので、インスタンス化を変更できます:
let s : (< visitSalesman : 'a -> unit>) salesman = new salesman ();;
そしてこれはコンパイルされます!
再帰的ソリューション
再帰クラスを使用することで複雑さを軽減できます。これにより、パラメータ化されたクラスの必要性は完全になくなりますが、すべてのオブジェクトを同じソースファイルで定義する必要があります。
class virtual employee =
object
method virtual receiveEvaluation:(hrrep -> unit)
end
and accountant =
object(self)
inherit employee
method receiveEvaluation:(hrrep -> unit) = fun rep -> rep#visitAccountant (self :> accountant)
end
and salesman =
object (self)
inherit employee
method receiveEvaluation:(hrrep -> unit) = fun rep -> rep#visitSalesman (self :> salesman)
end
and hrrep =
object
method visitSalesman:(salesman -> unit) = fun s -> print_endline ("Visiting salesman")
method visitAccountant:(accountant -> unit) = fun s -> print_endline ("Visiting accountant")
end
let s = new salesman;;
let e = (s :> employee);;
let v = new hrrep;;
e#receiveEvaluation v;;
これは、「Visting salesman」を印刷します。従業員への強制は、これを現実世界のシナリオに近づけることです。
パラメータ化されたソリューション
もう一度問題を見ると、この時点で他のすべてのタイプがわかっているため、パラメータ化されたhrRepを持つ必要はないと思います。従業員クラスをパラメータ化するだけで、次のようになります:
class virtual ['a] employee =
object
method virtual receiveEvaluation : 'a -> unit
method virtual getName : string
end
class ['a] accountant name =
object(self)
inherit ['a] employee
val name = name
method receiveEvaluation rep = rep#visitAccountant self
method getName = "A " ^ name
end
class ['a] salesman name =
object(self)
inherit ['a] employee
val name = name
method receiveEvaluation rep = rep#visitSalesman self
method getName = "S " ^ name
end
class virtual hrRep =
object
method virtual visitAccountant : hrRep accountant -> unit
method virtual visitSalesman : hrRep salesman -> unit
end
class lowerLevelHRRep =
object
inherit hrRep
method visitAccountant a = print_endline ("Visiting accountant " ^ a#getName)
method visitSalesman s = print_endline ("Visiting salesman " ^ s#getName)
end;;
let bob = new salesman "Bob";;
let mary = new accountant "Mary";;
let sue = new salesman "Sue";;
let h = new lowerLevelHRRep;;
bob#receiveEvaluation h;;
mary#receiveEvaluation h;;
sue#receiveEvaluation h;;
これは以下を返します:
訪問セールスマンSボブ
会計士のメアリーを訪問
訪問セールスマンSスー
このソリューションの利点は、従業員が訪問者について知る必要がないため、独自のコンパイルユニットで定義できるため、新しいタイプの従業員を追加する際のコードのクリーン化と再コンパイルの削減につながります。