2017-04-12 2 views
1

Ich möchte eine Art Sicherheit in der folgenden Situation erreichen.Scala: Typ Inferenz fehlt etwas

Grundsätzlich habe ich verschiedene Arten von Anfragen, die in einer Datenbank gespeichert sind, deren Typ mit einem String-Code identifiziert wird. Aus geschäftlichen Gründen entspricht dieser Code nicht den Klassennamen.

Jeder Anfragetyp enthält eine Art Nutzlast, der Typ der Nutzlast hängt direkt von der Art der Anfrage ab. Hier

ist eine vereinfachte Version von dem, was ich erreicht habe, so weit:

trait Request[Payload] { 
    def metadata: String // Not relevant 
    def payload: Payload 
} 

case class RequestWithString(override val metadata: String, override val payload: String) extends Request[String] 

case class AnotherTypeOfRequestWithString(override val metadata: String, override val payload: String) extends Request[String] 

case class RequestWithInt(override val metadata: String, override val payload: Int) extends Request[Int] 

object Request { 
    def apply(code: String)(metadata: String, payload: Any): Request[_] = code match { 
    case "S" => RequestWithString(metadata, payload.asInstanceOf[String]) 
    case "S2" => AnotherTypeOfRequestWithString(metadata, payload.asInstanceOf[String]) 
    case "I" => RequestWithInt(metadata, payload.asInstanceOf[Int]) 
    } 
} 

Dies ist nicht befriedigend, wie ich Scala möchte die Art der Nutzlast schließen Gießen zu vermeiden, und die (parametrisierte) Typ des zurückgegebenen Werts.

Was ich suche ist so etwas wie das:

object Request { 
    def apply[P, R <: Request[P]](code: String)(metadata: String, payload: P): R = code match { 
    case "S" => RequestWithString(metadata, payload) 
    case "S2" => AnotherTypeOfRequestWithString(metadata, payload) 
    case "I" => RequestWithInt(metadata, payload) 
    } 
} 

Aber dies scheint nicht zu arbeiten, kann ich nicht von einigen Typdiskrepanzen loszuwerden:

found : P 
required: String 
       case "S" => RequestWithString(metadata, payload) 
                ^

shouldn‘ Scala folgert, dass P in diesem Fall String ist? Was vermisse ich?

+0

Welche weiteren Verarbeitung sind Sie auf jede Art von Anfrage zu tun? –

+0

@YuvalItzchakov: MongoDB-Speicher mit ReactiveMongo, der dann einen lesbaren Text aus den Payload- und Remote-Daten erstellt, um den Text an ein entferntes System zu senden. Ich muss zu MongoDB lesen/schreiben, der Hauptzweck der Request.apply Funktion soll in einem BSONReader [Request] aufgerufen werden. –

+0

Aktuelle Antworten helfen sehr. Allerdings habe ich bearbeitet, um klarer zu machen, dass die Art der Anfrage nicht nur vom Typ der Nutzlast abhängt. Möglicherweise gibt es mehrere Unterklassen mit der gleichen Art von Payload. –

Antwort

2

Bewegen Sie die passende Entscheidungslogik zu einem typeclass:

// this typeclass holds the logic for creating a `Request` for 
// a particular payload 
sealed abstract class RequestPayloadType[T](val create: (String, T) => Request[T]) 
object RequestPayloadType { 
    implicit object StringPayloadType extends RequestPayloadType[String] (RequestWithString.apply) 
    implicit object IntPayloadType extends RequestPayloadType[Int] (RequestWithInt.apply) 
} 

object Request { 
    def apply[P:RequestPayloadType](metadata: String, payload: P): Request[P] = 
    implicitly[RequestPayloadType[P]].create(metadata, payload) 
} 

gemeinsame Muster in scala: Verschieben Sie den Code, die Kenntnis bestimmter Typen erfordert, zu einer Übersetzungseinheit, die dieses Wissen hat.

Denken Sie daran, es könnte zu nicht individuellen Anforderungsklassen sauberer sein müssen, und haben nur eine einzige parametrisierte ein:

case class Request [P:RequestPayloadType](metadata: String, payload: P) { 
    // delegate any code that needs to know the type to `implicitly[RequestPayloadType[T]]...` 
} 

sealed trait RequestPayloadType[T] { 
    // specify here code that needs to know the actual type, i.e: 
    // def encode (value: T): String // abstract 
    // def decode (value: String): T // abstract 
} 
object RequestPayloadType { 
    implicit object StringPayloadType extends RequestPayloadType[String] { 
    // implement here any `String` specific code, .i.e: 
    // def encode (s: String) = s 
    // ... 
    } 
    implicit object IntPayloadType extends RequestPayloadType[Int] { 
    // implement here any `Int` specific code, .i.e: 
    // def encode (i: Int) = i.toString 
    // ... 
    } 
} 
2

Ich kann ein paar wichtige Verbesserungen sehen. Lassen Sie uns von Anfang an beginnen, vor allem verwenden wir nie val innerhalb einer Eigenschaft für abstrakte Mitglieder, schauen Sie here.

trait Request[Payload] { 
    def metadata: String // Not relevant 
    def payload: Payload 
} 

Jetzt ist hier lassen:

object Request { 
    def apply[P, R <: Request[P]](code: String)(metadata: String, payload: P): R = code match { 
    case "S" => RequestWithString(metadata, payload) 
    case "I" => RequestWithInt(metadata, payload) 
    } 
} 

Sie die Bedeutung von P <: Request[P] Missverständnis, ist dies ein f-begrenzter Typ polymorpher param, die verwendet wird, was als Typ Verfeinerung bekannt ist, zum Beispiel kehrt die am spezifischsten Wrappertyp nach dem Aufrufen einer Methode, die für die obere Typgrenze definiert wurde, zB Methoden Request geben RequestWithInt statt einfach Request zurück. In Ihrem Fall glaube ich nicht, dass Sie auf jeden Fall den richtigen Ansatz wählen.

Sie würden es für eine Methode verwenden, die sowohl RequestWithString als auch RequestWithInt Instanzen als Parameter oder etwas ähnliches nimmt.

Jetzt in Ihrem Fall, was Sie tun sollten, ist eine ADT für Ihren Anfragetyp zu verwenden. So etwas wie RequestEncoder.

trait RequestEncoder[T] { 
    def encode(obj: T): String 
    def decode(obj: String): T 
} 

object RequestEncoder { 
    implicit val intEncoder = new RequestEncoder[Int] { 
    def encode(obj: Int): String = obj.toString 
    def decode(source: String): Int = source.toInt 
    } 
} 

trait Request[Payload : RequestEncoder] { 
    def metadata: String // Not relevant 
    def payload(source: Payload): String = implicitly[RequestEncoder[Payload]].encode(source) 
} 
+0

Ich habe das Merkmal bearbeitet, um die Verwendung von Vals zu vermeiden, da dies für das eigentliche Problem nicht relevant ist. Vielen Dank. Ich verstehe den Zweck des Encoders nicht, warum sollte ich die Payload zu/von einem String codieren/decodieren wollen? –

+0

@ K.C. Ich habe Ihnen einfach einen Ansatz gezeigt, über Typklassen und deren Verwendung gelesen. Der Punkt ist, dass Sie alle Methoden, die Sie generisch auf "Request" definieren möchten, definieren und sie mit dem Typklassenansatz pro Typ implementieren. – flavian