forall w Scali

Jak pokazano poniżej, w Haskell, możliwe jest Przechowywanie w liście wartości z heterogenicznymi typami z pewnymi ograniczeniami kontekstu na nich:

data ShowBox = forall s. Show s => ShowBox s

heteroList :: [ShowBox]
heteroList = [ShowBox (), ShowBox 5, ShowBox True]

Jak mogę osiągnąć to samo w Scali, najlepiej bez podtypowania?

Author: missingfaktor, 2011-08-27

5 answers

Jak skomentował @ Michael Kohl, użycie forall w Haskell jest typem egzystencjalnym i może być dokładnie powtórzone w Scali za pomocą konstrukcji forSome lub symbolu wieloznacznego. Oznacza to, że odpowiedź @paradigmatic jest w dużej mierze poprawna.

Niemniej jednak czegoś tam brakuje w stosunku do oryginału Haskell, który polega na tym, że instancje jego typu ShowBox przechwytywają również odpowiednie instancje klasy Show type w sposób, który sprawia, że są one dostępne do użycia na elementach listy nawet gdy dokładny typ bazowy został określony egzystencjalnie. Twój komentarz do odpowiedzi @ paradigmatic sugeruje, że chcesz być w stanie napisać coś równoważnego do następującego Haskell,

data ShowBox = forall s. Show s => ShowBox s

heteroList :: [ShowBox]
heteroList = [ShowBox (), ShowBox 5, ShowBox True]

useShowBox :: ShowBox -> String
useShowBox (ShowBox s) = show s

-- Then in ghci ...

*Main> map useShowBox heteroList
["()","5","True"]

Odpowiedź Kima Stebela pokazuje kanoniczny sposób robienia tego w języku zorientowanym obiektowo, wykorzystując podtypy. Inne rzeczy są równe, to jest właściwy sposób, aby przejść w Scali. Jestem pewien, że wiesz o tym i masz dobre powody, aby unikać podtypowania i powielania Haskella podejście oparte na klasach typu w Scali. Zaczyna się ...

Zauważ, że w Haskell powyżej wystąpienia klasy Show type dla Unit, Int i Bool są dostępne w implementacji funkcji useShowBox. Jeśli spróbujemy bezpośrednio przetłumaczyć to na Scalę, otrzymamy coś w stylu,

trait Show[T] { def show(t : T) : String }

// Show instance for Unit
implicit object ShowUnit extends Show[Unit] {
  def show(u : Unit) : String = u.toString
}

// Show instance for Int
implicit object ShowInt extends Show[Int] {
  def show(i : Int) : String = i.toString
}

// Show instance for Boolean
implicit object ShowBoolean extends Show[Boolean] {
  def show(b : Boolean) : String = b.toString
}

case class ShowBox[T: Show](t:T)

def useShowBox[T](sb : ShowBox[T]) = sb match {
  case ShowBox(t) => implicitly[Show[T]].show(t)
  // error here      ^^^^^^^^^^^^^^^^^^^
} 

val heteroList: List[ShowBox[_]] = List(ShowBox(()), ShowBox(5), ShowBox(true))

heteroList map useShowBox

I to nie kompiluje się w useShowBox w następujący sposób,

<console>:14: error: could not find implicit value for parameter e: Show[T]
         case ShowBox(t) => implicitly[Show[T]].show(t)
                                      ^

Problem polega na tym, że w przeciwieństwie do przypadku Haskella, instancje Show type nie są propagowane z Argument ShowBox do ciała funkcji useShowBox, a więc nie są dostępne do użycia. Jeśli spróbujemy to naprawić, dodając dodatkowy kontekst związany z funkcją useShowBox,

def useShowBox[T : Show](sb : ShowBox[T]) = sb match {
  case ShowBox(t) => implicitly[Show[T]].show(t) // Now compiles ...
} 

To rozwiązuje problem w useShowBox, ale teraz nie możemy go używać w połączeniu z mapą na naszej egzystencjalnie ilościowej liście,

scala> heteroList map useShowBox
<console>:21: error: could not find implicit value for evidence parameter
                     of type Show[T]
              heteroList map useShowBox
                             ^

Dzieje się tak, ponieważ gdy useShowBox jest dostarczany jako argument do funkcji map, musimy wybrać instancję Show na podstawie informacji o typie w tym momencie. Oczywiście nie istnieje tylko jedna instancja Show, która wykona zadanie dla wszystkich elementów tej listy, a więc ta nie zostanie skompilowana (gdybyśmy zdefiniowali instancję Show dla dowolnej, to byłaby, ale nie o to tu chodzi ... chcemy wybrać instancję klasy type na podstawie najbardziej określonego typu każdego elementu listy).

Aby to działało w taki sam sposób jak w Haskell, musimy wyraźnie propagować instancje Show w ciele useShowBox. To może wyglądać tak:

case class ShowBox[T](t:T)(implicit val showInst : Show[T])

val heteroList: List[ShowBox[_]] = List(ShowBox(()), ShowBox(5), ShowBox(true))

def useShowBox(sb : ShowBox[_]) = sb match {
  case sb@ShowBox(t) => sb.showInst.show(t)
}

Następnie w REPL,

scala> heteroList map useShowBox
res7: List[String] = List((), 5, true)

Zauważ, że usunęliśmy przypisany kontekst w ShowBox tak, że mamy jawną nazwę (showInst) dla wystąpienia Show dla zawartej wartości. Następnie w ciele useShowBox możemy go jawnie zastosować. Zauważ również, że dopasowanie wzorca jest niezbędne, aby upewnić się, że typ egzystencjalny otworzymy tylko raz w ciele funkcji.

Jak powinno być oczywiste, jest to o wiele bardziej vebose niż równoważny Haskell i zdecydowanie polecam użycie rozwiązania opartego na podtypie w Scali, chyba że masz bardzo dobre powody, aby zrobić inaczej.

Edit

Jak wspomniano w komentarzach, definicja Scala ShowBox powyżej ma widoczny parametr typu, który nie jest obecny w oryginale Haskell. Myślę, że to całkiem pouczające zobaczyć, jak możemy to naprawić używając abstrakcyjnych typów.

Najpierw zamieniamy parametr type na abstrakcyjny Typ member i zastąp parametry konstruktora abstrakcyjnymi vals,

trait ShowBox {
  type T
  val t : T
  val showInst : Show[T]
}

Teraz musimy dodać metodę fabryczną, którą klasy case dałyby nam za Darmo,

object ShowBox {
  def apply[T0 : Show](t0 : T0) = new ShowBox {
    type T = T0
    val t = t0
    val showInst = implicitly[Show[T]]
  } 
}

Możemy teraz używać zwykłego Showboxa tam, gdzie wcześniej używaliśmy Showboxa [_]... element typu abstrakcyjnego odgrywa dla nas rolę kwantyfikatora egzystencjalnego,

val heteroList: List[ShowBox] = List(ShowBox(()), ShowBox(5), ShowBox(true))

def useShowBox(sb : ShowBox) = {
  import sb._
  showInst.show(t)
}

heteroList map useShowBox

(Warto zauważyć, że przed wprowadzeniem explict forSome i wildcards w Scali było to dokładnie tak, jak reprezentowałby typy egzystencjalne.)

Mamy teraz egzystencjalne dokładnie w tym samym miejscu, co jest w oryginalnym Haskell. Myślę, że to jest tak blisko wiernego wykonania, jak można dostać w Scali.

 60
Author: Miles Sabin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-27 22:08:45

Podany przez Ciebie przykład dotyczy typu egzystencjalnego . Zmieniam nazwę konstruktora danych ShowBox na SB, aby odróżnić go od typu :

data ShowBox = forall s. Show s => SB s

Mówimy, że s jest "egzystencjalny", ale forall tutaj jest uniwersalny kwantyfikator, który odnosi się do SB konstruktora danych. Jeśli zapytamy o Typ konstruktora SB z włączonym jawnym forall, staje się to znacznie jaśniejsze:

SB :: forall s. Show s => s -> ShowBox

To znaczy {[9] } jest rzeczywiście zbudowany z trzech rzeczy:

  1. typ s
  2. wartość typu s
  3. instancja Show s.

Ponieważ typ s staje się częścią konstruowanego ShowBox, jestegzystencjalnie kwantyfikowany . Jeśli Haskell obsługiwał składnię do kwantyfikacji egzystencjalnej, moglibyśmy napisać ShowBox jako alias typu:

type ShowBox = exists s. Show s => s

Scala wspiera tego rodzaju kwantyfikację egzystencjalną, a odpowiedź Milesa podaje szczegóły używając cechy, która składa się dokładnie z te trzy rzeczy powyżej. Ale ponieważ jest to pytanie o "forall in Scala", zróbmy to dokładnie tak, jak robi to Haskell.

Konstruktory danych w Scali nie mogą być jednoznacznie kwantyfikowane za pomocą forall. Jednak każda metoda na module może być. Można więc efektywnie wykorzystać polimorfizm konstruktora typów jako kwantyfikację uniwersalną. Przykład:

trait Forall[F[_]] {
  def apply[A]: F[A]
}

Typ Scala Forall[F], biorąc pod uwagę pewne F, jest wtedy odpowiednikiem typu Haskell forall a. F a.

Możemy użyć tej techniki, aby dodać ograniczenia argumentu type.

trait SuchThat[F[_], G[_]] {
  def apply[A:G]: F[A]
}

Wartość typu F SuchThat G jest jak wartość typu Haskell forall a. G a => F a. Instancja G[A] jest domyślnie sprawdzana przez Scalę, jeśli istnieje.

Możemy użyć tego do zakodowania twojego ShowBox ...
import scalaz._; import Scalaz._ // to get the Show typeclass and instances

type ShowUnbox[A] = ({type f[S] = S => A})#f SuchThat Show

sealed trait ShowBox {
  def apply[B](f: ShowUnbox[B]): B  
}

object ShowBox {
  def apply[S: Show](s: => S): ShowBox = new ShowBox {
    def apply[B](f: ShowUnbox[B]) = f[S].apply(s)
  }
  def unapply(b: ShowBox): Option[String] =
    b(new ShowUnbox[Option[String]] {
      def apply[S:Show] = s => some(s.shows)
  })
}

val heteroList: List[ShowBox] = List(ShowBox(()), ShowBox(5), ShowBox(true))

Metoda ShowBox.apply jest powszechnie kwantyfikowanym konstruktorem danych. Możesz zobaczyć, że zajmuje Typ S, instancję Show[S] i wartość typu S, podobnie jak Wersja Haskell.

Oto przykład sposób użycia:

scala> heteroList map { case ShowBox(x) => x }
res6: List[String] = List((), 5, true)

W Scali kodowanie bezpośrednie może polegać na użyciu klasy case:

sealed trait ShowBox
case class SB[S:Show](s: S) extends ShowBox {
  override def toString = Show[S].shows(s)
}

Potem:

scala> val heteroList = List(ShowBox(()), ShowBox(5), ShowBox(true))
heteroList: List[ShowBox] = List((), 5, true)

W tym przypadku, {[35] } jest zasadniczo równoważne List[String], ale możesz użyć tej techniki z innymi cechami niż Show, aby uzyskać coś bardziej interesującego.

To wszystko przy użyciu Show typeklasa z Scalaz .

 24
Author: Apocalisp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-04 16:23:41

Nie sądzę, aby tłumaczenie 1 do 1 z Haskell do Scali było tutaj możliwe. Ale dlaczego nie chcesz użyć podtypowania? Jeśli typy, których chcesz użyć (takie jak Int), nie mają metody show, możesz nadal dodawać ją poprzez niejawne konwersje.

scala> trait Showable { def show:String }
defined trait Showable

scala> implicit def showableInt(i:Int) = new Showable{ def show = i.toString }
showableInt: (i: Int)java.lang.Object with Showable

scala> val l:List[Showable] = 1::Nil
l: List[Showable] = List($anon$1@179c0a7)

scala> l.map(_.show)
res0: List[String] = List(1)
 5
Author: Kim Stebel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-22 19:25:53

( Edit : Dodawanie metod do pokazania, odpowiedzi na komentarz. )

Myślę, że możesz uzyskać to samo używając metod niejawnych z ograniczeniami kontekstu:

trait Show[T] {
  def apply(t:T): String
}
implicit object ShowInt extends Show[Int] {
  def apply(t:Int) = "Int("+t+")"
}
implicit object ShowBoolean extends Show[Boolean] {
  def apply(t:Boolean) = "Boolean("+t+")"
}

case class ShowBox[T: Show](t:T) {
  def show = implicitly[Show[T]].apply(t)
}

implicit def box[T: Show]( t: T ) =
  new ShowBox(t)

val lst: List[ShowBox[_]] = List( 2, true )

println( lst ) // => List(ShowBox(2), ShowBox(true))

val lst2 = lst.map( _.show )

println( lst2 ) // => List(Int(2), Boolean(true))
 3
Author: paradigmatic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-27 10:59:31

Dlaczego nie:

trait ShowBox {
    def show: String
}

object ShowBox {
    def apply[s](x: s)(implicit i: Show[s]): ShowBox = new ShowBox {
        override def show: String = i.show(x)
    }
}

Jak sugerowały odpowiedzi władz, Często dziwi mnie, że Scala potrafi przetłumaczyć "potwory typu Haskell" na bardzo prosty.

 0
Author: MB_,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-28 13:48:06