A Swift Tour にある例文
enum ServerResponse {
case Result(String, String)
case Error(String)
}
let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Error("Out of cheese.")
switch success {
case let .Result(sunrise, sunset):
let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Error(error):
let serverResponse = "Failure... \(error)"
}
抜粋: Apple Inc. “The Swift Programming Language”。 iBooks. https://itun.es/jp/jEUH0.l
正常系と異常系を奇麗に扱える例ですよね。
なのでこれを汎用化したいなーと思いました。
enumをgenericsにしてみる
まず思いつくのが
// compile error
enum Response<ResultType, ErrorType>{
case Result( ResultType )
case Error( ErrorType )
}
let a = Response <Int,String>.Error("error")
switch a {
case let .Result2( res ):
println("\(res)")
case let .Error2( err ):
println(err)
}
という書き方。
でも
error: unimplemented IR generation feature non-fixed multi-payload enum layout
コンパイルエラーになります。
Containerを使う形で実装してみる
そこでエラーと結果を扱うgenericなコンテナクラスを作り、それらを引数にとる形に変えてみます。
class ResultContainer<T>
{
private let r : T
init( r : T){ self.r = r }
func result() -> T { return r }
}
class ErrorContainer<T>
{
private let e : T
init( e : T){ self.e = e }
func error() -> T { return e }
}
enum Response<ResultType, ErrorType>{
case Result( ResultContainer<ResultType> )
case Error( ErrorContainer<ErrorType> )
}
エラーがでなくなりました。
genericsなclassとはいえ実装が書かれたからでしょうか?
生成関数を用意
生成クラスにしても良いんですが長くなっちゃうので関数で用意してます。
enumに生成関数を持たせられるかなというのも試したのですがうまくいかず。
// 正常系な結果を生成
func GenResult< ResultType, ErrorType >( result: ResultType ) -> Response<ResultType, ErrorType>
{
return .Result( ResultContainer<ResultType>( r: result ) )
}
// 異常系な結果を生成
func GenError< ResultType, ErrorType >( error: ErrorType ) -> Response<ResultType, ErrorType> {
return .Error( ErrorContainer<ErrorType>( e: error ) )
}
使い方(実装側)
func invokeInt( a: Int, b: Int ) -> Response<(Int,Int) , String>
{
if ( a < 0 ){ return GenError( "Invalid Argment a") }
if ( b < 0 ){ return GenError( "Invalid Argment b") }
return GenResult((a,b))
}
func invokeString( a: String?, b: String? ) -> Response<(String,String) , String>
{
if ( a == nil ){ return GenError( "Invalid Argment a") }
if ( b == nil ){ return GenError( "Invalid Argment b") }
return GenResult((a!,b!))
}
使い方(呼び出し側)
func checkInt( response: Response<(Int,Int) , String> )
{
switch response{
case let .Result( res ):
let (a,b) = res.result()
println("\(a) : \(b)")
case let .Error( err ):
println("Failure... \(err.error())")
}
}
func checkString( response: Response<(String,String) , String> )
{
switch response{
case let .Result( res ):
let (a,b) = res.result()
println("\(a) : \(b)")
case let .Error( err ):
println("Failure... \(err.error())")
}
}
checkInt( invokeInt( -1, -1 ) )
checkInt( invokeInt( -1, 0 ) )
checkInt( invokeInt( 0, -1 ) )
checkInt( invokeInt( 0, 0 ) )
checkString( invokeString( nil, nil ) )
checkString( invokeString( nil, "d" ) )
checkString( invokeString( "c", nil ) )
checkString( invokeString( "c", "d" ) )
結果
Failure... Invalid Argment a
Failure... Invalid Argment a
Failure... Invalid Argment b
0 : 0
Failure... Invalid Argment a
Failure... Invalid Argment a
Failure... Invalid Argment b
c : d
呼び出し側で error() や result() といった中身の取り出しのコードが増えてしまっている点が微妙ですが汎用化はできました。
出来ればコンパイルエラーになってしまったコードが通ると素直でいいんですけどねー