2013-07-16 2 views
16

다른 유형의 값 (Double, String, Int, ...)을 넣는 Map이 필요합니다. 키는 String이 될 수 있습니다.지도 스칼라의 다른 유형

이 작업을 수행 할 수있는 방법이 있나요 나는 이미 제네릭 형식

class Container[T](element: T) { 
    def get: T = element 
} 

val d: Container[Double] = new Container(4.0) 
val str: Container[String] = new Container("string") 
val m: Map[String, Container] = Map("double" -> d, "string" -> str) 

와 그것을 시도 map.apply(k)

val map: Map[String, SomeType] = Map() 
val d: Double = map.apply("double") 
val str: String = map.apply("string") 

등으로 정확한 유형을 얻을 수 있지만, Container 때문에 가능하지 않도록 매개 변수를 취합니다. 이것에 대한 해결책이 있습니까?

+1

가능한 복제본 http://stackoverflow.com/questions/4309835/scala-reflection/4310959 –

답변

15

이 지금 shapeless 매우 간단을

scala> import shapeless._ ; import syntax.singleton._ ; import record._ 
import shapeless._ 
import syntax.singleton._ 
import record._ 

scala> val map = ("double" ->> 4.0) :: ("string" ->> "foo") :: HNil 
map: ... <complex type elided> ... = 4.0 :: foo :: HNil 

scala> map("double") 
res0: Double with shapeless.record.KeyTag[String("double")] = 4.0 

scala> map("string") 
res1: String with shapeless.record.KeyTag[String("string")] = foo 

scala> map("double")+1.0 
res2: Double = 5.0 

scala> val map2 = map.updateWith("double")(_+1.0) 
map2: ... <complex type elided> ... = 5.0 :: foo :: HNil 

scala> map2("double") 
res3: Double = 5.0 

티 이 답변의 날짜 현재 모양이없는 2.0.0-SNAPSHOT입니다.

+3

그것은 꽤 산뜻합니다. 그러나 HList를지도로 사용할 때 액세스 특성은 무엇입니까? 기본적으로 단일 링크 된 목록이므로 각 요소를 확인해야하므로 조회는 O (N)이됩니다. 맞습니까? 또한 몇 가지 요소 이상을 가지고 있으면 복잡한 유형을 생성하지 않습니까? –

3

(a)는 스칼라 용기가 그들 내부에 위치 있는지에 대한 형식 정보를 추적하지 않으며,

(b)는 간단한 String 매개 변수/키와의 수/적용 방법에 대한 반환 "유형"예정 메서드가 적용될 객체의 지정된 인스턴스에 대해 정적이어야합니다.

이것은 재검토해야하는 디자인 결정과 매우 흡사합니다. 이 작업을 수행하려면 AnyDoubleString 모두의 슈퍼이기 때문에

1

당신은, AnyContainer의 유형을 지정해야 할 것이다.

val d: Container[Any] = new Container(4.0) 
val str: Container[Any] = new Container("string") 
val m: Map[String, Container[Any]] = Map("double" -> d, "string" -> str) 

아니면 불변 더 이상 유형의하지 않도록 일을 더 쉽게하기 위해, 당신은 Container의 정의를 변경할 수 있습니다 : 나는 벌거 벗은 map.apply()이 무엇을 얻을 수있는 방법이 있다고 생각하지 않습니다

class Container[+T](element: T) { 
    def get: T = element 
    override def toString = s"Container($element)" 
} 

val d: Container[Double] = new Container(4.0) 
val str: Container[String] = new Container("string") 
val m: Map[String, Container[Any]] = Map("double" -> d, "string" -> str) 
+0

이미 시도했습니다. 그러나 만약 내가'Cointainer'를'Any'라고 지정하면'map.apply (k)'에'Any' 값을 얻습니다. 'val d : Double = map.apply ("double")'불가능합니다. – 3x14159265

2

가 너는 원할거야. 다른 대답이 제시 하듯이, 일종의 컨테이너 클래스가 필요합니다.

sealed trait MapVal 
case class StringMapVal(value: String) extends MapVal 
case class DoubleMapVal(value: Double) extends MapVal 
case class IntMapVal(value: Int) extends MapVal 

val myMap: Map[String, MapVal] =                
    Map("key1" -> StringMapVal("value1"), 
     "key2" -> DoubleMapVal(3.14), 
     "key3" -> IntMapVal(42)) 

myMap.keys.foreach { k => 
    val message = 
    myMap(k) match { // map.apply() in your example code 
     case StringMapVal(x) => "string: %s".format(x) 
     case DoubleMapVal(x) => "double: %.2f".format(x) 
     case IntMapVal(x) => "int: %d".format(x) 
    } 
    println(message) 
} 

sealted trait의 주요 장점은 컴파일 시간을 패턴 매칭 아닌 완전한 일치를 확인하는 것입니다 : 여기에 (이 경우, 문자열, 더블, INT) 값이 특정 유형으로 제한하는 예입니다 .

나는이 방법이 Scala 표준에 비해 비교적 간단하기 때문에이 접근법을 좋아합니다. 당신은 좀 더 강력한 것을 위해 잡초로 갈 수 있지만 제 생각에는 당신은 꽤 빨리 수익을 줄이려고합니다.

1

방법이 있지만 복잡합니다. Unboxed union types in Scala을 참조하십시오. 기본적으로 IntDouble을 모두 수용하려면 유형으로 Map을 입력해야합니다. 또한 컴파일 시간에 높은 가격을 지불하게됩니다.

21

이것은 간단하지 않습니다.

값의 유형은 키에 따라 다릅니다. 그래서 열쇠는 그 값이 어떤 타입인지에 관한 정보를 가지고 있어야합니다. 이것은 일반적인 패턴입니다. 예를 들어 SBT (예 : SettingsKey[T] 참조) 및 쉐이프 레스 레코드 (Example 참조)에서 사용됩니다. 그러나 SBT에서 키는 자체의 거대하고 복잡한 클래스 계층이며 셰이프가없는 HList는 매우 복잡하며 원하는 것 이상을 수행합니다.

이렇게 구현할 수있는 방법은 다음과 같습니다.키는 유형을 알고 있으며, 레코드를 작성하거나 레코드에서 값을 가져 오는 유일한 방법이 키입니다. Map [Key, Any]를 내부적으로 저장소로 사용하지만 캐스트가 숨겨져 성공할 수 있습니다. 키로 레코드를 생성하는 연산자와 레코드를 병합하는 연산자가 있습니다. 대괄호를 사용하지 않고 레코드를 연결할 수 있도록 연산자를 선택했습니다.

sealed trait Record { 

    def apply[T](key:Key[T]) : T 

    def get[T](key:Key[T]) : Option[T] 

    def ++ (that:Record) : Record 
} 

private class RecordImpl(private val inner:Map[Key[_], Any]) extends Record { 

    def apply[T](key:Key[T]) : T = inner.apply(key).asInstanceOf[T] 

    def get[T](key:Key[T]) : Option[T] = inner.get(key).asInstanceOf[Option[T]] 

    def ++ (that:Record) = that match { 
    case that:RecordImpl => new RecordImpl(this.inner ++ that.inner) 
    } 
} 

final class Key[T] { 
    def ~>(value:T) : Record = new RecordImpl(Map(this -> value)) 
} 

object Key { 

    def apply[T] = new Key[T] 
} 

사용 방법은 다음과 같습니다. 우선 몇 가지 키를 정의 :

val a = Key[Int] 
val b = Key[String] 
val c = Key[Float] 

을 다음 기록

val record = a ~> 1 ++ b ~> "abc" ++ c ~> 1.0f 

키를 사용하여 기록에 액세스 할 때, 다시 올바른 유형의 값을 얻을 것이다

scala> record(a) 
res0: Int = 1 

scala> record(b) 
res1: String = abc 

scala> record(c) 
res2: Float = 1.0 
을 생성하는 데 사용할

저는 이런 종류의 데이터 구조가 매우 유용하다는 것을 알았습니다. 사례 클래스가 제공하는 것보다 더 많은 유연성이 필요할 때도 있지만, Map [String, Any]처럼 완전히 유형이 안전하지 않은 무언가에 의지하고 싶지는 않습니다. 이것은 좋은 중간계입니다.


편집 : 다른 옵션은 (이름, 유형) 쌍을 실제 키로 내부적으로 사용하는지도를 사용하는 것입니다. 값을 얻을 때 이름과 유형을 모두 제공해야합니다. 잘못된 유형을 선택하면 항목이 없습니다. 그러나 이것은 바이트를 넣고 int를 꺼내려고 할 때와 같이 오류가 발생할 가능성이 큽니다. 그래서 나는 이것이 좋은 생각이 아니라고 생각합니다.

import reflect.runtime.universe.TypeTag 

class TypedMap[K](val inner:Map[(K, TypeTag[_]), Any]) extends AnyVal { 
    def updated[V](key:K, value:V)(implicit tag:TypeTag[V]) = new TypedMap[K](inner + ((key, tag) -> value)) 

    def apply[V](key:K)(implicit tag:TypeTag[V]) = inner.apply((key, tag)).asInstanceOf[V] 

    def get[V](key:K)(implicit tag:TypeTag[V]) = inner.get((key, tag)).asInstanceOf[Option[V]] 
} 

object TypedMap { 
    def empty[K] = new TypedMap[K](Map.empty) 
} 

사용법 : 나는 마침내 내 경우에는 최선을 다했다 내 자신의 솔루션을 발견

scala> val x = TypedMap.empty[String].updated("a", 1).updated("b", "a string") 
x: TypedMap[String] = [email protected] 

scala> x.apply[Int]("a") 
res0: Int = 1 

scala> x.apply[String]("b") 
res1: String = a string 

// this is what happens when you try to get something out with the wrong type. 
scala> x.apply[Int]("b") 
java.util.NoSuchElementException: key not found: (b,Int) 
+1

이것은 좋은 패턴입니다. 불행히도'apply' 메소드에 주석을 달아 주어야합니다. 어쨌든, 지금까지 가장 좋은 해결책이라고 생각합니다. 당신의 대답에 감사드립니다! – 3x14159265

+0

두 번째 것을 의미합니까? ClassTag를 사용하면 JVM에 해당하지 않는 스칼라 유형을 넣을 때 문제가 발생합니다. 예를 들어 List [Int]에 넣고 List [String]을 요청할 수 있습니다. List [Int]와 List [String]에 동일한 지우기가 있기 때문에 뭔가를 얻을 수 있습니다. 이 경우에도 작동하게하려면 TypeTag 대신 JVM 유형뿐만 아니라 전체 스칼라 유형을 캡처해야합니다. 그에 따라 예제를 업데이트했습니다. –

+0

첫 번째 패턴은 엉성한 모든 일반적인 마법이 필요하지 않지만 여러 가지 유형의 값이있는 컨테이너가 필요하면 매우 유용합니다. 감사합니다. – Egregore

3

:

case class Container[+T](element: T) { 
    def get[T]: T = { 
     element.asInstanceOf[T] 
    } 
} 

val map: Map[String, Container[Any]] = Map("a" -> Container[Double](4.0), "b" -> Container[String]("test")) 
val double: Double = map.apply("a").get[Double] 
val string: String = map.apply("b").get[String]