HLists, tuples yazmanın kıvrımlı bir yolundan başka bir şey değil mi?


144

HListlerin kullanılamadığı (veya daha doğrusu, normal listelere göre herhangi bir fayda sağlamadığı) kanonik kullanım durumlarını belirlemek için farklılıkların nerede olduğunu ve daha genel olarak bulmakla gerçekten ilgileniyorum.

( TupleNScala'da 22 (inanıyorum) olduğunun farkındayım , oysa birinin tek bir HList'e ihtiyacı var, ancak bu ilgilendiğim kavramsal farklılık türü değil.)

Aşağıdaki metinde birkaç soru işaretledim. Aslında onlara cevap vermek gerekli olmayabilir, daha çok benim için belirsiz olan şeyleri belirtmek ve tartışmayı belirli yönlere yönlendirmek içindir.

Motivasyon

İnsanlar HLists kullanmayı önerdi nerede (örneğin sağladığı şekilde Geçenlerde SO üzerinde cevapların bir çift gördüm şekilsiz bir silinen cevap dahil) bu soruya . Bu yol açtı bu tartışma da bu soruyu yol açtı.

giriş

Bana öyle geliyor ki, hlists sadece elementlerin sayısını ve kesin tiplerini statik olarak bildiğinizde yararlıdır. Sayı aslında çok önemli değil, ancak değişen ancak statik olarak kesin olarak bilinen türlerin unsurlarıyla bir liste oluşturmanız gerekmiyor gibi görünüyor, ancak sayılarını statik olarak bilmiyorsunuz. Soru 1: Böyle bir örneği, örneğin bir döngüde bile yazabilir misiniz? Sezgim, statik olarak bilinmeyen sayıda rasgele öğeye (belirli bir sınıf hiyerarşisine göre rastgele) sahip statik olarak kesin bir listeye sahip olmanın sadece uyumlu olmadığıdır.

HLists ve Tuples karşılaştırması

Bu doğruysa, yani, sayı ve türü statik olarak biliyorsunuz - Soru 2: neden sadece n-tuple kullanmıyorsunuz? Elbette, bir HList'in üzerine eşzamanlı olarak harita çizebilir ve katlayabilirsiniz (ki, aynı zamanda tipik olarak değil , bir demet kullanarak da yapabilirsiniz productIterator), ancak öğelerin sayısı ve türü statik olarak bilindiği için muhtemelen sadece demet bileşenlerine erişebilirsiniz doğrudan ve işlemleri gerçekleştirin.

Öte yandan, fbir liste üzerinde eşlediğiniz işlev tüm öğeleri kabul edecek kadar genelse - Soru 3: Neden onu kullanmıyorsunuz productIterator.map? Tamam, yöntem aşırı yüklemesinden ilginç bir fark gelebilir: aşırı yüklenmiş birkaç tane varsa f, hlist tarafından sağlanan daha güçlü tip bilgisine sahip olmak (productIterator'ın aksine) derleyicinin daha spesifik bir seçim yapmasına izin verebilir f. Ancak, yöntemler ve işlevler aynı olmadığından bunun Scala'da gerçekten işe yarayıp yaramayacağından emin değilim.

HLists ve kullanıcı girişi

Öğelerin sayısını ve türlerini statik olarak bilmeniz gereken aynı varsayım üzerine inşa etmek - Soru 4: Hlists, öğelerin herhangi bir kullanıcı etkileşimine bağlı olduğu durumlarda kullanılabilir mi? Örneğin, bir döngü içindeki öğelerle bir liste oluşturduğunuzu düşünün; elemanlar belli bir koşul oluşana kadar bir yerden (UI, yapılandırma dosyası, aktör etkileşimi, ağ) okunur. Listenin türü ne olurdu? Bir arabirim belirtimi için benzer getElements: HList [...], statik olarak bilinmeyen uzunluk listeleriyle çalışmalı ve bir sistemdeki bileşen A'nın bileşen B'den rastgele öğelerin böyle bir listesini almasına izin verir.

Yanıtlar:


144

Bir ila üç arasındaki soruları ele almak: ana uygulamalardan biri, HListsarity ü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 HListsiçin tanımlama grubu argümanlardan Arity üzerinde soyut (veya bir şey dengi) flattenbuna 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 => Teşleme productIteratorsize mükemmel bir şekilde hizmet edecektir. Ancak formun işlevleri Any => Ttipik 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 HLists'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


2
Cevabınızın son kısmından biraz şaşkınım - ama aynı zamanda çok ilgilendi! Harika cevabınız ve birçok referansınız için teşekkürler, yapacak çok şeyim var gibi görünüyor :-)
Malte Schwerhoff

1
Arity üzerinde soyutlama son derece yararlıdır. ScalaMock, ne yazık ki, hatırı sayılır çoğaltma gelen uğrar çeşitli çünkü FunctionNnasıl soyut Arity üzerinde özellikleri bilmiyorum: github.com/paulbutcher/ScalaMock/blob/develop/core/src/main/... github.com/paulbutcher/ScalaMock/blob / develop / core / src / main /… Ne yazık ki "gerçek" FunctionNs ile başa çıkmak zorunda olduğum göz önüne alındığında, bunu önlemek için Shapeless kullanabileceğim hiçbir şekilde farkında değilim
Paul Butcher

1
Birinci soruda yer alan (oldukça yapay) bir örnek - ideone.com/sxIw1 - oluşturdum. Bu, belki de "dinamik verilere yanıt olarak çalışma zamanında gerçekleştirilen statik yazım" ile birlikte listelerden yararlanabilir mi? (İkincisinin tam olarak ne hakkında olduğundan emin değilim)
Malte Schwerhoff

18

Açık olmak gerekirse, bir HList aslında Tuple2üstte biraz farklı şeker bulunan bir yığından başka bir şey değildir .

def hcons[A,B](head : A, tail : B) = (a,b)
def hnil = Unit

hcons("foo", hcons(3, hnil)) : (String, (Int, Unit))

Yani sorunuz aslında iç içe tuples ile düz tuples arasındaki farklar hakkındadır, ancak ikisi izomorfiktir, bu yüzden sonunda kütüphane işlevlerinin kullanılabileceği ve hangi gösterimin kullanılabileceği dışında hiçbir fark yoktur.


tuples hlists ve yine geri eşlenebilir, bu yüzden açık bir izomorfizm var.
Erik Kaplun

10

Tuples ile yapamayacağınız birçok şey var:

  • genel başa ekleme / ekleme işlevi yazma
  • ters fonksiyon yaz
  • concat işlevi yazma
  • ...

Bunların hepsini elbette tuples ile yapabilirsiniz, ancak genel durumda değil. Yani HLists kullanmak kodunuzu daha KURU yapar.


8

Bunu çok basit bir dilde açıklayabilirim:

Grup ve liste adlandırma anlamlı değildir. HLists, HTuples olarak adlandırılabilir. Fark, Scala + Haskell'de bunu bir demetle (Scala sözdizimini kullanarak) yapabilmenizdir:

def append2[A,B,C](in: (A,B), v: C) : (A,B,C) = (in._1, in._2, v)

herhangi bir türden tam olarak iki elemandan oluşan bir giriş demetini almak için, üçüncü bir öğe ekleyin ve tam olarak üç öğeyle birlikte tam olarak yazılmış bir demet döndürün. Ancak bu, türler üzerinde tamamen genel olsa da, giriş / çıkış uzunluklarını açıkça belirtmek zorundadır.

Bir Haskell tarzı HList'in yapmanıza izin verdiği şey, bu uzunluğu genel yapmaktır, böylece herhangi bir tuple / list uzunluğuna ekleyebilir ve tamamen statik olarak yazılmış bir tuple / listeyi geri alabilirsiniz. Bu avantaj, tam olarak n ints listesine int ekleyebileceğiniz ve açıkça n belirtmeden tam olarak (n + 1) ints içerecek şekilde statik olarak yazılan bir listeyi geri alabileceğiniz homojen olarak yazılan koleksiyonlar için de geçerlidir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.