Bir ila üç arasındaki soruları ele almak: ana uygulamalardan biri, HLists
arity üzerinde soyutlamaktır. Arity tipik olarak bir soyutlamanın herhangi bir kullanım yerinde statik olarak bilinir, ancak bölgeden bölgeye değişir. Bunu şekilsizin örneklerinden alın ,
def flatten[T <: Product, L <: HList](t : T)
(implicit hl : HListerAux[T, L], flatten : Flatten[L]) : flatten.Out =
flatten(hl(t))
val t1 = (1, ((2, 3), 4))
val f1 = flatten(t1) // Inferred type is Int :: Int :: Int :: Int :: HNil
val l1 = f1.toList // Inferred type is List[Int]
val t2 = (23, ((true, 2.0, "foo"), "bar"), (13, false))
val f2 = flatten(t2)
val t2b = f2.tupled
// Inferred type of t2b is (Int, Boolean, Double, String, String, Int, Boolean)
Kullanmadan HLists
için tanımlama grubu argümanlardan Arity üzerinde soyut (veya bir şey dengi) flatten
buna bu iki çok farklı şekillerde argümanları kabul edip bir tür güvenli bir şekilde onları dönüştürebilir bir tek uygulama olması imkansız olurdu.
Arity üzerinde soyutlama yeteneğinin, sabit aritelerin dahil olduğu her yerde ilgi çekmesi muhtemeldir: ayrıca yöntem / fonksiyon parametre listelerini ve vaka sınıflarını içeren tuples. Neredeyse otomatik olarak tür sınıfı örnekleri elde etmek için keyfi vaka sınıflarının çağrısını nasıl özetleyebileceğimize ilişkin örnekler için buraya bakın ,
// A pair of arbitrary case classes
case class Foo(i : Int, s : String)
case class Bar(b : Boolean, s : String, d : Double)
// Publish their `HListIso`'s
implicit def fooIso = Iso.hlist(Foo.apply _, Foo.unapply _)
implicit def barIso = Iso.hlist(Bar.apply _, Bar.unapply _)
// And now they're monoids ...
implicitly[Monoid[Foo]]
val f = Foo(13, "foo") |+| Foo(23, "bar")
assert(f == Foo(36, "foobar"))
implicitly[Monoid[Bar]]
val b = Bar(true, "foo", 1.0) |+| Bar(false, "bar", 3.0)
assert(b == Bar(true, "foobar", 4.0))
Burada çalışma zamanı yinelemesi yoktur , ancak (veya eşdeğer yapıların) kullanımının ortadan kaldırabileceği çoğaltma vardır HLists
. Tabii ki, tekrarlayan kazan plakasına toleransınız yüksekse, önem verdiğiniz her şekil için birden fazla uygulama yazarak aynı sonucu elde edebilirsiniz.
Üçüncü soruda, "... bir liste üzerinde eşlediğiniz f işlevi tüm öğeleri kabul edecek kadar genel ise ... neden productIterator.map aracılığıyla kullanmıyorsunuz?" Bir HList üzerinden eşlediğiniz işlev gerçekten formdaysa, Any => T
eşleme productIterator
size mükemmel bir şekilde hizmet edecektir. Ancak formun işlevleri Any => T
tipik olarak ilginç değildir (en azından dahili olarak dökülmedikleri sürece değildir). şekilsiz, derleyicinin türe özgü vakaları tam olarak şüphelendiğiniz şekilde seçmesine izin veren bir çeşit polimorfik fonksiyon değeri sağlar. Örneğin,
// size is a function from values of arbitrary type to a 'size' which is
// defined via type specific cases
object size extends Poly1 {
implicit def default[T] = at[T](t => 1)
implicit def caseString = at[String](_.length)
implicit def caseList[T] = at[List[T]](_.length)
}
scala> val l = 23 :: "foo" :: List('a', 'b') :: true :: HNil
l: Int :: String :: List[Char] :: Boolean :: HNil =
23 :: foo :: List(a, b) :: true :: HNil
scala> (l map size).toList
res1: List[Int] = List(1, 3, 2, 1)
Dördüncü soru ile ilgili olarak, kullanıcı girişi ile ilgili, dikkate alınması gereken iki durum vardır. Birincisi, bilinen bir statik durumun elde edilmesini garanti eden bir bağlamı dinamik olarak oluşturabildiğimiz durumlardır. Bu tür senaryolarda, şekilsiz tekniklerin uygulanması mükemmel bir şekilde mümkündür, ancak statik koşulun çalışma zamanında elde edilmemesi durumunda alternatif bir yol izlememiz gerektiği açıkça görülmektedir . Şaşırtıcı olmayan bir şekilde bu, dinamik koşullara duyarlı yöntemlerin isteğe bağlı sonuçlar vermesi gerektiği anlamına gelir. İşte HList
s'yi kullanan bir örnek ,
trait Fruit
case class Apple() extends Fruit
case class Pear() extends Fruit
type FFFF = Fruit :: Fruit :: Fruit :: Fruit :: HNil
type APAP = Apple :: Pear :: Apple :: Pear :: HNil
val a : Apple = Apple()
val p : Pear = Pear()
val l = List(a, p, a, p) // Inferred type is List[Fruit]
Türü l
, listenin uzunluğunu veya öğelerinin kesin türlerini yakalamaz. Bununla birlikte, belirli bir forma sahip olmasını beklersek (yani, bilinen, sabit bir şemaya uyması gerekiyorsa), bu gerçeği oluşturmaya ve buna göre hareket etmeye çalışabiliriz,
scala> import Traversables._
import Traversables._
scala> val apap = l.toHList[Apple :: Pear :: Apple :: Pear :: HNil]
res0: Option[Apple :: Pear :: Apple :: Pear :: HNil] =
Some(Apple() :: Pear() :: Apple() :: Pear() :: HNil)
scala> apap.map(_.tail.head)
res1: Option[Pear] = Some(Pear())
Belirli bir listenin gerçek uzunluğunu umursamayabileceğimiz, başka bir listeyle aynı uzunluktan başka durumlar da vardır. Yine bu, şekilsiz olarak hem tamamen statik olarak hem de yukarıdaki gibi karışık bir statik / dinamik bağlamda destekleyen bir şeydir. Genişletilmiş bir örnek için buraya bakın .
Gözlemlediğiniz gibi, bu mekanizmaların hepsinin en azından şartlı olarak statik tip bilgilerinin mevcut olması gerektiği ve bu tekniklerin tamamen harici olarak sağlanan türsüz veriler tarafından yönlendirilen tamamen dinamik bir ortamda kullanılmasını engelleyecek gibi gözüktüğü doğrudur. Ancak, 2.10'da Scala yansımasının bir bileşeni olarak çalışma zamanı derleme desteğinin ortaya çıkmasıyla, bu bile çözülemez bir engel değil ... bir tür hafif evreleme sağlamak için çalışma zamanı derlemesini kullanabilir ve statik yazımımızı çalışma zamanında gerçekleştirebiliriz dinamik verilere yanıt olarak: aşağıdakilerden alıntı ... tam örnek için bağlantıyı takip edin,
val t1 : (Any, Any) = (23, "foo") // Specific element types erased
val t2 : (Any, Any) = (true, 2.0) // Specific element types erased
// Type class instances selected on static type at runtime!
val c1 = stagedConsumeTuple(t1) // Uses intString instance
assert(c1 == "23foo")
val c2 = stagedConsumeTuple(t2) // Uses booleanDouble instance
assert(c2 == "+2.0")
Eminim @PLT_Borat bağımlı yazım programlama dilleri ;-) hakkında onun adaçayı yorumları göz önüne alındığında, bu konuda söylenecek bir şey olacak