Bu yöntem saf mı?


9

Aşağıdaki uzantı yöntemi var:

    public static IEnumerable<T> Apply<T>(
        [NotNull] this IEnumerable<T> source,
        [NotNull] Action<T> action)
        where T : class
    {
        source.CheckArgumentNull("source");
        action.CheckArgumentNull("action");
        return source.ApplyIterator(action);
    }

    private static IEnumerable<T> ApplyIterator<T>(this IEnumerable<T> source, Action<T> action)
        where T : class
    {
        foreach (var item in source)
        {
            action(item);
            yield return item;
        }
    }

Diziyi döndürmeden önce her öğeye bir eylem uygular.

Pure(Resharper ek açıklamalarından) özniteliğini bu yönteme uygulayıp uygulamadığımı merak ediyordum ve buna karşı ve ona karşı argümanlar görebiliyorum.

Artıları:

  • kesinlikle bu konuşma olduğunu saf; bir sekansta çağırmak sekansı değiştirmez (yeni bir sekans döndürür) veya gözlemlenebilir bir durum değişikliği yapmaz
  • sonucu kullanmadan çağırmak açıkça bir hatadır, çünkü dizi numaralandırılmadıkça hiçbir etkisi yoktur, bu yüzden Resharper'ın bunu yaparsam beni uyarmasını istiyorum.

Eksileri:

  • olsa Applyyöntem kendisini saf, elde edilen sekansı numaralandırma olacaktır (yöntemin noktası olan) gözlemlenebilir durum değişiklik. Örneğin, items.Apply(i => i.Count++)her numaralandırıldığında öğelerin değerlerini değiştirir. Saf niteliği uygulamak muhtemelen yanıltıcıdır ...

Ne düşünüyorsun? Özelliği uygulamalı mıyım, uygulamam mı?


Yanıtlar:


15

Hayır saf değildir, çünkü yan etkisi vardır. Somut actionolarak her öğeyi çağırıyor . Ayrıca, iplik güvenli değildir.

Saf fonksiyonların en önemli özelliği, herhangi bir sayıda çağrılabilmesi ve asla aynı değeri döndürmekten başka bir şey yapmamasıdır. Bu senin durumun değil. Ayrıca, saf olmak, giriş parametrelerinden başka bir şey kullanmamanız anlamına gelir. Bu, herhangi bir iş parçacığından herhangi bir zamanda çağrılabileceği ve beklenmedik davranışlara neden olamayacağı anlamına gelir. Yine, bu sizin fonksiyonunuzun durumu değildir.

Ayrıca, bir konuda yanılıyor olabilirsiniz: fonksiyon saflığı artıları veya eksileri ile ilgili bir soru değildir. Tek bir şüphe bile, yan etkisi olabileceğinden, saf olmamasını sağlamak için yeterlidir.

Eric Lippert iyi bir noktaya değiniyor . Karşı argümanımın bir parçası olarak http://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspx kullanacağım . Özellikle çizgi

Saf bir yöntemin, saf yönteme girdikten sonra oluşturulan nesneleri değiştirmesine izin verilir.

Diyelim ki böyle bir yöntem oluşturuyoruz:

int Count<T>(IEnumerable<T> e)
{
    var enumerator = e.GetEnumerator();
    int count = 0;
    while (enumerator.MoveNext()) count ++;
    return count;
}

İlk olarak, bu GetEnumeratorda saf olduğunu varsayar (bu konuda gerçekten herhangi bir kaynak bulamıyorum). Öyleyse, yukarıdaki kurala göre, bu yönteme [Saf] ile açıklama ekleyebiliriz, çünkü yalnızca vücudun içinde oluşturulan örneği değiştirir. Bundan sonra bunu ve ApplyIteratorsaf işleve neden olması gereken şeyi oluşturabiliriz , değil mi?

Count(ApplyIterator(source, action));

Hayır. Bu kompozisyon her ikisinde de saf Countve ApplyIteratorsaf değildir. Ama bu argümanı yanlış bir şekilde inşa ediyor olabilirim. Yöntem içinde oluşturulan örneklerin saflık kuralından muaf olduğu fikrinin yanlış olduğunu veya en azından yeterince spesifik olmadığını düşünüyorum.


1
+1 işlev saflığı, artıları veya eksileri ile ilgili bir sorun değildir. İşlev saflığı, kullanım ve güvenlikle ilgili bir ipucudur. Garip bir şekilde OP koymak yeterli where T : class, ancak OP basitçe koymak where T : strutsaf olurdu.
ArT'ler

4
Bu cevaba katılmıyorum. Aramanın sequence.Apply(action)yan etkisi yoktur; eğer öyleyse, sahip olduğu yan etkiyi belirtiniz. Şimdi çağırmanın sequence.Apply(action).GetEnumerator().MoveNext()bir yan etkisi var, ama bunu zaten biliyorduk; numaralandırıcıyı değiştirir! sequence.Apply(action)Arama safsız olduğu için neden MoveNextsaf sequence.Where(predicate)olarak düşünülmeli , ancak saf olarak kabul edilmeli? sequence.Where(predicate).GetEnumerator().MoveNext()her parça saf değildir.
Eric Lippert

@EricLippert İyi bir noktaya değindin. Ancak, GetEnumerator'ı aramak yeterli olmaz mı? Saf olduğunu düşünebilir miyiz?
Euphoric

@Euphoric: GetEnumeratorBir numaralandırıcıyı başlangıç ​​durumuna tahsis etmenin yanı sıra, çağrılan hangi gözlenebilir yan etki üretir?
Eric Lippert

1
@EricLippert Peki neden Enumerable.Count .NET Kod Sözleşmeleri tarafından Saf olarak kabul edilir? Bağlantım yok, ancak görsel stüdyoda onunla oynadığımda, özel saf olmayan sayımı kullandığımda uyarı alıyorum, ancak sözleşme Enumerable.Count ile gayet iyi çalışıyor.
Euphoric

18

Ben hem katılmıyorum coşkulu ve Robert Harvey 'in cevapları. Kesinlikle bu saf bir işlevdir; problem şu

Diziyi döndürmeden önce her öğeye bir eylem uygular.

ilk "o" nun ne anlama geldiği çok açık değil. "Bu" bu işlevlerden biri anlamına geliyorsa, bu doğru değildir; bu işlevlerin hiçbiri bunu yapmaz; MoveNextdizinin numaralandırıcının bunu yapar ve bu "döner" üzerinden öğesi Currentdeğil iade ederek mülkiyet.

Bu diziler tembel bir şekilde numaralandırılır , hevesle değil, bu yüzden dizi geri döndürülmeden önce eylemin uygulanması kesinlikle söz konusu değildir Apply. Bir numaralandırıcıda çağrılırsa , dizi döndürüldükten sonra eylem uygulanır MoveNext.

Not ettiğiniz gibi, bu işlevler bir işlem ve bir dizi alır ve bir dizi döndürür; çıktı girdiye bağlıdır ve hiçbir yan etki üretilmez, bu yüzden bunlar saf işlevlerdir.

Şimdi, sonuç dizisinin numaralandırıcısını oluşturup sonra bu yineleyicide MoveNext'i çağırırsanız, MoveNext yöntemi saf değildir, çünkü eylemi çağırır ve bir yan etki üretir. Ama zaten MoveNext'in saf olmadığını biliyorduk çünkü numaralandırıcıyı değiştiriyor!

Şimdi, sorunuza gelince, özniteliği uygulamalısınız: Özniteliği uygulamam çünkü ilk etapta bu yöntemi yazmam . Bir diziye bir eylem uygulamak istersem yazarım

foreach(var item in sequence) action(item);

ki bu gayet açık.


2
Sanırım bu yöntem ForEachkasıtlı olarak Linq'nin bir parçası olmayan uzatma yöntemiyle aynı torbaya düşüyor çünkü hedefi yan etkiler üretmek ...
Thomas Levesque

1
@ThomasLevesque: Tavsiyem bunu asla yapmamak . Bir sorgu bir soruyu cevaplamalı , bir diziyi değiştirmemelidir ; bu yüzden onlara sorgu denir . Diziyi sorgulandığı gibi değiştirmek muazzam derecede tehlikelidir . Örneğin, böyle bir sorgu daha sonra Any()zaman içinde birden çok çağrıya maruz kalırsa ne olacağını düşünün ; eylem tekrar tekrar gerçekleştirilecek, ancak sadece ilk öğede! Bir sekans bir değerler sekansı olmalıdır ; bir dizi eylem istiyorsanız o zaman bir IEnumerable<Action>.
Eric Lippert

2
Bu cevap suları aydınlattığından daha fazla çamurluyor. Söylediğiniz her şey tartışmasız doğru olsa da, değişmezlik ve saflık ilkeleri, düşük düzeyli uygulama ayrıntıları değil, üst düzey programlama dili ilkeleridir. İşlevsel düzeyde çalışan programcılar , iç işleyişinin saf olup olmadığı değil, kodlarının işlevsel düzeyde nasıl davrandığıyla ilgilenmektedir . Bunlar neredeyse kesin konum saf değil düşük yeterince giderseniz kaputun altında. Hepimiz genellikle bunları kesinlikle saf olmayan Von Neumann mimarisinde çalıştırıyoruz.
Robert Harvey

2
@ThomasEding: Yöntem çağırmaz action, bu yüzden saflığı actionönemsizdir. Ben biliyorum görünüyor o çağırır gibi action, ancak bu yöntem iki yöntem, bir sýralayýcý döndüren bir ve bir biri için bir sözdizimsel şeker MoveNextnumaralandırıcının. Birincisi açıkça saf, ikincisi açıkça değil. Şöyle bak: IEnumerable ApplyIterator(whatever) { return new MyIterator(whatever); }Saf olduğunu söyleyebilir misin ? Çünkü bu gerçekte olan işlev.
Eric Lippert

1
@ThomasEding: Bir şey eksik; yineleyiciler böyle çalışmaz. ApplyIteratorYöntem döner hemen . Döndürülen nesnenin numaralandırıcısına ApplyIteratoryapılan ilk çağrı yapılana kadar gövdesinde hiçbir kod çalıştırılmaz MoveNext. Artık bildiğinize göre, bu bulmacanın cevabını çıkarabilirsiniz: blogs.msdn.com/b/ericlippert/archive/2007/09/05/… Cevap burada: blogs.msdn.com/b/ericlippert/archive /
2007/09/06

3

Bu saf bir işlev değildir, bu yüzden Saf niteliğini uygulamak yanıltıcıdır.

Saf işlevler orijinal koleksiyonu değiştirmez ve etkisi olmayan bir eylemi geçip geçmemeniz önemli değildir; hala saf olmayan bir işlevdir çünkü amacı yan etkilere neden olmaktır.

İşlevi saflaştırmak istiyorsanız, koleksiyonu yeni bir koleksiyona kopyalayın, Eylem'in yeni koleksiyona yaptığı değişiklikleri uygulayın ve orijinal koleksiyonu değiştirmeden yeni koleksiyonu döndürün.


Aynı koleksiyonu içeren yeni bir sıra döndürdüğü için orijinal koleksiyonu değiştirmez; bu yüzden onu saflaştırmayı düşünüyordum. Ancak, sonucu numaralandırdığınızda öğelerin durumunu değiştirebilir.
Thomas Levesque

Eğer itembir referans türüdür, iade rağmen orijinal toplama değiştirme oluyor itembir yineleyici içinde. Bkz. Stackoverflow.com/questions/1538301
Robert Harvey

1
Koleksiyonu derin kopyalamış olsa bile, ona actionaktarılan öğeyi değiştirmek dışında yan etkileri olabileceğinden , hala saf olmayacaktır .
Idan Arye

@IdanArye: Doğru, Eylemin de saf olması gerekir.
Robert Harvey

1
@IdanArye: Action'a ()=>{}dönüştürülebilir ve saf bir işlevdir. Çıktıları yalnızca girdilerine bağlıdır ve gözlemlenebilir yan etkisi yoktur.
Eric Lippert

0

Bence, bir Eylem alması (ve PureAction gibi bir şey değil) onu saflaştırmıyor.

Ve Eric Lippert'e bile katılmıyorum. Bunu "() => {} Eylem'e dönüştürülebilir ve saf bir işlev olarak yazdı. Çıktıları yalnızca girdilerine bağlı ve gözlemlenebilir yan etkileri yok" yazdı.

Bir temsilci kullanmak yerine ApplyIterator öğesinin Action adlı bir yöntemi çağırdığını düşünün.

Eylem safsa, ApplyIterator da saftır. Eylem saf değilse, ApplyIterator saf olamaz.

Delege türü göz önüne alındığında (gerçek verilen değer değil), saf olacağının garantisi yoktur, bu nedenle yöntem yalnızca delege saf olduğunda saf bir yöntem gibi davranacaktır. Yani, gerçekten saf hale getirmek için, saf bir delege almalıdır (ve var olan bir delegeyi [Saf] olarak ilan edebiliriz, böylece bir PureAction'a sahip olabiliriz).

Farklı bir şekilde açıklamak gerekirse, bir Saf yöntem aynı girdiler göz önüne alındığında her zaman aynı sonucu vermeli ve gözlemlenebilir değişiklikler üretmemelidir. ApplyIterator öğesine aynı kaynak verilebilir ve iki kez temsilci atanabilir, ancak temsilci bir başvuru türünü değiştiriyorsa, bir sonraki yürütme farklı sonuçlar verir. Örnek: Temsilci item.Content + = "Değiştirildi" gibi bir şey yapıyor;

Bu nedenle, "string kapsayıcıları" (string türünde Content özelliğine sahip bir nesne) listesi üzerinde ApplyIterator kullanarak şu orijinal değerlere sahip olabiliriz:

Test

Test2

İlk yürütme işleminden sonra listede aşağıdakiler bulunur:

Test Changed

Test2 Changed

Ve bu 3. kez:

Test Changed Changed

Test2 Changed Changed

Böylece, delege saf olmadığından ve her yürütme farklı bir sonuç üreteceğinden, 3 kez çağrılırsa çağrıyı 3 kez yürütmekten kaçınmak için hiçbir optimizasyon yapılamayacağı için listenin içeriğini değiştiriyoruz.

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.