Co to jest manifest w Scali i kiedy go potrzebujesz?

134

Od Scali 2.7.2 istnieje coś, co nazywa Manifestsię obejściem wymazywania typu Java. Ale jak Manifestdokładnie działa i dlaczego / kiedy należy go użyć?

Post na blogu Manifests: Reified Types autorstwa Jorge Ortiza wyjaśnia niektóre z nich, ale nie wyjaśnia, jak używać go razem z ograniczeniami kontekstu .

Co to jest ClassManifest, jaka jest różnica Manifest?

Mam kod (będący częścią większego programu, nie można go tu łatwo dołączyć), który zawiera ostrzeżenia dotyczące usuwania typów; Podejrzewam, że mogę je rozwiązać za pomocą manifestów, ale nie wiem dokładnie, jak.

Jesper
źródło
2
Na liście mailingowej odbyła się dyskusja na temat różnicy Manifest / ClassManifest, patrz scala-programming-language.1934581.n4.nabble.com/…
Arjan Blokzijl

Odpowiedzi:

199

Kompilator zna więcej informacji o typach niż środowisko wykonawcze maszyny JVM może z łatwością przedstawić. Manifest to sposób, w jaki kompilator wysyła do kodu w czasie wykonywania komunikat międzywymiarowy dotyczący utraconych informacji o typie.

Jest to podobne do tego, jak Kleptonianie pozostawili zakodowane wiadomości w zapisach kopalnych i „śmieciowym” DNA ludzi. Ze względu na ograniczenia prędkości światła i pola rezonansu grawitacyjnego nie mogą się bezpośrednio komunikować. Ale jeśli wiesz, jak dostroić się do ich sygnału, możesz skorzystać na sposoby, których nie możesz sobie wyobrazić, decydując o tym, co zjeść na lunch lub w którą lotto zagrać.

Nie jest jasne, czy Manifest przyniósłby korzyści dla błędów, które widzisz, nie znając więcej szczegółów.

Jednym z typowych zastosowań manifestów jest to, że kod zachowuje się inaczej w zależności od statycznego typu kolekcji. Na przykład, co by było, gdybyś chciał traktować List [String] inaczej niż inne typy List:

 def foo[T](x: List[T])(implicit m: Manifest[T]) = {
    if (m <:< manifest[String])
      println("Hey, this list is full of strings")
    else
      println("Non-stringy list")
  }

  foo(List("one", "two")) // Hey, this list is full of strings
  foo(List(1, 2)) // Non-stringy list
  foo(List("one", 2)) // Non-stringy list

Oparte na refleksji rozwiązanie tego problemu wymagałoby prawdopodobnie sprawdzenia każdego elementu listy.

Wiązanie kontekstu wydaje się najbardziej odpowiednie do używania klas typu w scali i jest dobrze wyjaśnione tutaj przez Debasish Ghosh: http://debasishg.blogspot.com/2010/06/scala-implicits-type-classes-here-i.html

Ograniczenia kontekstu mogą również sprawić, że sygnatury metod będą bardziej czytelne. Na przykład powyższa funkcja może zostać ponownie napisana przy użyciu ograniczeń kontekstu, takich jak:

  def foo[T: Manifest](x: List[T]) = {
    if (manifest[T] <:< manifest[String])
      println("Hey, this list is full of strings")
    else
      println("Non-stringy list")
  }
Mitch Blevins
źródło
25

Nie jest to pełna odpowiedź, ale jeśli chodzi o różnicę między Manifesti ClassManifest, możesz znaleźć przykład w artykule Scala 2.8Array :

Pozostaje tylko pytanie, jak zaimplementować tworzenie ogólnych tablic. W przeciwieństwie do Javy, Scala umożliwia tworzenie nowych instancji, Array[T]gdzie Tjest parametrem typu. Jak można to zaimplementować, biorąc pod uwagę fakt, że w Javie nie istnieje jednolita reprezentacja tablicowa?

Jedynym sposobem na to jest wymaganie dodatkowych informacji o środowisku wykonawczym, które opisują typ T. Scala 2.8 ma do tego nowy mechanizm, który nazywa się Manifestem . Obiekt typu Manifest[T]dostarcza kompletnych informacji o typie T.
Manifestwartości są zwykle przekazywane w niejawnych parametrach; a kompilator wie, jak je skonstruować dla typów znanych statycznie T.

Istnieje również słabsza forma o nazwie, ClassManifestktórą można skonstruować, znając tylko klasę najwyższego poziomu typu, niekoniecznie znając wszystkie jego typy argumentów .
Ten typ informacji o czasie wykonywania jest wymagany do utworzenia tablicy.

Przykład:

Tę informację trzeba podać, przekazując ClassManifest[T]do metody jako niejawny parametr:

def  tabulate[T](len:Int,  f:Int=>T)(implicit m:ClassManifest[T]) =  { 
  val  xs  =  new  Array[T](len) 
  for   (i  <- 0  until   len)  xs(i)   = f(i) 
  xs 
} 

Jako forma skrócona T, zamiast parametru typu można użyć kontekstu związanego1 ,

(Zobacz to pytanie SO dla ilustracji )

dając:

def  tabulate[T:    ClassManifest](len:Int,  f:Int=>T)  =  { 
  val  xs  =  new  Array[T](len) 
  for   (i  <- 0  until   len)  xs(i)   = f(i) 
  xs 
} 

Podczas wywoływania tabulate dla typu, takiego jak Int, lub String, List[T]kompilator Scala może utworzyć manifest klasy, który zostanie przekazany jako niejawny argument do tabeli.

VonC
źródło
25

Manifest miał na celu reifikację typów ogólnych, które są usuwane z typów w celu uruchomienia na maszynie JVM (która nie obsługuje typów ogólnych). Miały jednak poważne problemy: były zbyt uproszczone i nie były w stanie w pełni obsługiwać systemu typów Scali. W związku z tym zostały one przestarzałe w Scali 2.10 i zostały zastąpione przez TypeTags (które są zasadniczo tym, czego sam kompilator Scali używa do reprezentowania typów, a zatem w pełni obsługuje typy Scala). Aby uzyskać więcej informacji na temat różnicy, zobacz:

Innymi słowy

kiedy to potrzebujesz?

Przed 2013-01-04, kiedy została wydana Scala 2.10 .

Ślimak mechaniczny
źródło
Nie jest to jeszcze przestarzałe (ale będzie), ponieważ odbicie Scali jest nadal eksperymentalne w wersji 2.10.
Keros
Przed 2013-01-04 lub jeśli korzystasz z interfejsu API, który jest na nim oparty.
David Moles
1

Sprawdźmy też manifestw scalasources ( Manifest.scala), widzimy:

Manifest.scala:
def manifest[T](implicit m: Manifest[T])           = m

A więc jeśli chodzi o następujący przykładowy kod:

def foo[A](somelist: List[A])(implicit m: Manifest[A]): String = {
  if (m <:< manifest[String]) {
    "its a string"
  } else {
    "its not a string"
  }
}

widzimy, że manifest functionwyszukuje niejawny, m: Manifest[T]który spełnia type parameterpodane przez Ciebie w naszym przykładowym kodzie manifest[String]. Więc kiedy zadzwonisz do czegoś takiego:

if (m <:< manifest[String]) {

sprawdzasz, czy bieżący, implicit mktóry zdefiniowałeś w swojej funkcji, jest typu, manifest[String]a ponieważ manifestjest funkcją typu manifest[T], szukałby określonego manifest[String]i znalazłby, czy istnieje taki niejawny.

Tomer Ben David
źródło