2013-01-02 24 views
7

Özellikle Scala gibi işlevsel dilleri bildiğim halde, Clojure'da oldukça yeniyim.Clojure koleksiyonları üzerindeki işlemler

Clojure'daki koleksiyonlarda çalışmanın deyimsel yolunun ne olduğunu bulmaya çalışıyorum. Özellikle map gibi işlevlerin davranışı ile kafam karıştı. , Clojure içinde, yerine

List(1, 2, 3) map (2 *) == List(2, 4, 6) 
Set(1, 2, 3) map (2 *) == Set(2, 4, 6) 
Vector(1, 2, 3) map (2 *) == Vector(2, 4, 6) 

:

Scala olarak, büyük bir özen map her zaman orijinal koleksiyon aynı tip bir koleksiyon döndürür, böylece yapımında alınır sürece bu mantıklıdır olarak Anladığım kadarıyla, map veya filter gibi çoğu işlem, hevesli veri yapılarında çağrıldığında bile, tembeldir. Bu,

'un bir vektör yerine tembel bir listenin yapılmasının garip bir sonucudur.

Genel olarak, tembel operasyonları tercih etsem de, yukarıdaki kafa karıştırıcıyı buluyorum. Aslında, vektörler listelenmeyen belirli performans özelliklerini garanti eder.

Yukarıdaki sonucu kullandığımı ve sonuna eklediğimi varsayalım. Doğru bir şekilde anladığımda, sonuca eklemedene kadar sonuç değerlendirilmez, o zaman değerlendirilir ve bir vektör yerine bir liste alırım; bu yüzden sonuna eklemek için onu geçmek zorundayım. Elbette bunu daha sonra bir vektöre dönüştürebilirim, fakat bu karmaşıklaşır ve gözden kaçabilir.

Eğer doğru anlıyorsam, map polimorfiktir ve uygulamak bir sorun olmaz çünkü vektörler üzerinde bir vektör döndürür, listelerde bir liste, akışlarda bir akış (bu sefer tembel semantik ile) ve benzeri . Clojure'ın ve onun deyimlerinin temel tasarımı hakkında bir şey kaçırdığımı düşünüyorum.

Klojure veri yapıları üzerindeki temel işlemlerin yapının önüne geçmemesinin nedeni nedir?

+0

Harita için kaynak koduna bakın. Harita, koleksiyonun türünü umursamıyor. Koleksiyonun türünü hatırlayan ve en sonunda koleksiyonun türüne dönüştürülen bir harita üzerinde bir makro oluşturabilirsiniz. https://github.com/clojure/clojure/blob/master/src/clj/clojure/core/reducers.clj –

+1

https://github.com/ adresinde clojure.algo.generic.functor/fmap adresine bir göz atın. Girdi türünü koruyan bir 'map' uygulaması için clojure/algo.generic. – Alex

cevap

7

Clojure'da birçok işlev Seq özetine dayanmaktadır. Bu yaklaşımın yararı, her koleksiyon için bir işlev yazmak zorunda kalmamanızdır - koleksiyonunuz bir dizi (bir kafa ve muhtemelen bir kuyruk gibi) olarak görülebildiği sürece, tüm fonksiyonlar. Sekans ve çıkış sekanslarını alan fonksiyonlar, çok daha komplekstir ve dolayısıyla kullanımlarını belirli bir toplama tipine sınırlayan fonksiyonlardan daha fazla kullanılabilir. İstediğiniz zaman kendi fonksiyonunuzu yazarken özel durumlar gibi işlem yapmanıza gerek kalmaz: Kullanıcı bana bir vektör verirse, bir vektör, vb. Döndürmek zorunda kalırsınız. Fonksiyonunuz, diğer boru hattı boru hattında olduğu kadar iyi olacaktır. seq işlevi.

Haritanın tembel bir şekilde döndürülmesinin nedeni tasarım seçimidir. Clojure'da lazyness, bu fonksiyonel yapıların çoğu için varsayılan değerdir. Eğer ara koleksiyonlar olmaksızın paralellik gibi başka bir davranışa sahip olmak istiyorsanız, redüktör kütüphanesine bir göz atın: http://clojure.com/blog/2012/05/08/reducers-a-library-and-model-for-collection-processing.html

Performans ilerledikçe, harita her zaman bir koleksiyonda n kez bir koleksiyonda bir işlev uygulamak zorundadır. Son eleman, bu yüzden performansı her zaman O (n) veya daha kötüsü olacaktır. Bu durumda vektör veya liste fark yaratmaz. Tembelliğin size sağlayacağı yarar, listenin sadece ilk bölümünü ne zaman tüketirsiniz. Haritanın çıktısının sonuna bir şey eklemeniz gerekiyorsa, bir vektör gerçekten daha verimlidir. Bu durumda mapv'u (Clojure 1.4'te eklenmiştir) kullanabilirsiniz: bir koleksiyon alır ve bir vektör çıkarır.Bunun için çok iyi bir nedeniniz varsa, bu performans optimizasyonları hakkında sadece endişelenirim. Çoğu zaman buna değmez.

burada seq soyutlama hakkında daha fazla bilgi: 1.4 filterv olan Clojure ilave edildi http://clojure.org/sequences

başka vektör-dönen yüksek sıralı fonksiyonu.

+1

Liste vs vektörünün performans farkı yaratmadığını söyleyemem - bu, "map" sonuçlarının nasıl kullanılacağını açıklıyor - örn. '(nth (harita # (*% 2) gerçekten uzun-vektör) 10000)' – Alex

+0

@Alex, haklısınız, bu yorumu göndermeden önce cevabımı zaten değiştirdim –

+1

Bir başka nokta da sekans oluşturma çok ucuz; Vektörler yaratmak, hala ucuz iken, büyük ölçüde daha pahalıdır. 'map', ucuz bir şeyi kibarca yapıyor ve eğer bir sebepten ötürü ihtiyaç duyuyorsanız, onu bir vektöre dönüştürmene izin veriyor. Ama ayrıca: Haritalamak istediğiniz bir vektörünüz varsa, sadece bir diziye ihtiyaç duyarsınız, bir vektör değil. – amalloy