Fonksiyonel JavaScript kodunu nasıl okuyabilirim?


9

JavaScript'te fonksiyonel programlamanın altında yatan temel kavramların bazılarını / çoğunu / çoğunu öğrendiğime inanıyorum. Ancak, özellikle fonksiyonel kod, hatta yazdığım kod okumakta sorun var ve herkes bana yardımcı olabilecek herhangi bir işaretçi, ipuçları, en iyi uygulamalar, terminoloji, vb verebilir olup olmadığını merak ediyorum.

Aşağıdaki kodu alın. Bu kodu yazdım. İki nesne arasında, say {a:1, b:2, c:3, d:3}ve arasında benzerlik yüzdesi atamayı amaçlamaktadır {a:1, b:1, e:2, f:2, g:3, h:5}. Stack Overflow bu soruya yanıt olarak kod üretti . Posterin ne tür bir yüzde benzerlik taşıdığını tam olarak bilmiyordum, dört farklı tür sağladım:

  • 2. nesnede bulunan 1. nesnedeki anahtarların yüzdesi,
  • kopyalar da dahil olmak üzere 2. nesnede bulunabilen 1. nesnedeki değerlerin yüzdesi,
  • 2. nesnede bulunan ve kopyaya izin verilmeyen değerlerin yüzdesi ve
  • 2. nesnede bulunan 1. nesnedeki {key: value} çiftlerinin yüzdesi.

Makul bir zorunluluk koduyla başladım, ancak bunun işlevsel programlama için çok uygun bir sorun olduğunu çabucak fark ettim. Özellikle, karşılaştırmak istediğim özellik türünü tanımlayan yukarıdaki dört stratejinin her biri için bir işlev veya üç ayıklayabilseydim (örneğin, anahtarlar veya değerler vb.) Kodun geri kalanını tekrarlanabilir birimlere indirgeyebilir (kelimelerdeki oyunu affedebilir). BUNU KURU halde tut. Böylece fonksiyonel programlamaya geçtim. Sonuçtan oldukça gurur duyuyorum, bence oldukça zarif ve bence ne yaptığımı gayet iyi anladım.

Ancak, kodu kendim yazmış ve inşaat sırasında her parçasını anlamış olsam bile, şimdi tekrar baktığımda, hem belirli bir yarım satırın nasıl okunacağı hem de nasıl yapılacağı konusunda biraz şaşkın olmaya devam ediyorum. "grok" kod herhangi bir yarım satır aslında ne yapıyor. Kendimi hızlı bir şekilde spagetti karmaşasına dönüşen farklı parçaları bağlamak için zihinsel oklar yaparken buluyorum.

Peki, birisi bana daha kıvrımlı kod parçalarının bazılarını hem kısa hem de okuduğum şeyi anlamama katkıda bulunan bir şekilde nasıl "okuyacağımı" söyleyebilir mi? Beni en çok etkileyen parçalar üst üste birkaç yağ oku olan ve / veya üst üste birkaç parantez içeren bölümler. Yine, özlerinde, sonunda mantığı anlayabilirim, ancak (umarım) hızlı ve net bir şekilde ve doğrudan bir işlevsel JavaScript programlama hattını "almak" için daha iyi bir yol vardır.

Aşağıdan herhangi bir kod satırını ve hatta diğer örnekleri kullanmaktan çekinmeyin. Ancak, benden bazı ilk öneriler almak istiyorsanız, işte birkaç tane. Oldukça basit biriyle başlayın. Kodun sonuna yakın itibaren, bir işleve parametre olarak geçirilir, bu var: obj => key => obj[key]. Bunu nasıl okuyup anlıyoruz? Daha uzun bir örneği başından yakınlarından bir tam fonksiyon geçerli: const getXs = (obj, getX) => Object.keys(obj).map(key => getX(obj)(key));. Son mapkısım özellikle beni alıyor.

Not zamanla bu noktada ben değilim Lütfen değil vs. Haskell yapılan atıflar veya sembolik soyut gösterimde veya-işlemden temelleri, arayan ne am seyir için ben sessizce ağız kod satırı bakarak kullanımın oldukça İngilizce cümleler olduğunu. Özellikle tam olarak buna değinen referanslarınız varsa, harika, ama aynı zamanda bazı temel ders kitaplarını okumam gerektiğini söyleyen cevapları da aramıyorum. Bunu yaptım ve (en azından önemli miktarda) mantığı alıyorum. Ayrıca, (bu tür girişimler memnuniyetle karşılanacaktır) kapsamlı cevaplara ihtiyacım yok: Aksi takdirde zahmetli kod tek bir satır okuma için zarif bir yol sağlayan kısa cevaplar bile takdir edilecektir.

Sanırım bu sorunun bir kısmı: Fonksiyonel kodu doğrusal olarak okuyabilir miyim , bilirsiniz, soldan sağa ve yukarıdan aşağıya? Ya da kod sayfasında kesinlikle doğrusal olmayan spagetti benzeri kabloların zihinsel bir resmini oluşturmak zorunda mı? Ve eğer bunu yapmak gerekiyorsa , yine de kodu okumalıyız, peki doğrusal metni alıp spagetti'yi nasıl bağlarız?

Herhangi bir ipucu takdir edilecektir.

const obj1 = { a:1, b:2, c:3, d:3 };
const obj2 = { a:1, b:1, e:2, f:2, g:3, h:5 };

// x or X is key or value or key/value pair

const getXs = (obj, getX) =>
  Object.keys(obj).map(key => getX(obj)(key));

const getPctSameXs = (getX, filter = vals => vals) =>
  (objA, objB) =>
    filter(getXs(objB, getX))
      .reduce(
        (numSame, x) =>
          getXs(objA, getX).indexOf(x) > -1 ? numSame + 1 : numSame,
        0
      ) / Object.keys(objA).length * 100;

const pctSameKeys       = getPctSameXs(obj => key => key);
const pctSameValsDups   = getPctSameXs(obj => key => obj[key]);
const pctSameValsNoDups = getPctSameXs(obj => key => obj[key], vals => [...new Set(vals)]);
const pctSameProps      = getPctSameXs(obj => key => JSON.stringify( {[key]: obj[key]} ));

console.log('obj1:', JSON.stringify(obj1));
console.log('obj2:', JSON.stringify(obj2));
console.log('% same keys:                   ', pctSameKeys      (obj1, obj2));
console.log('% same values, incl duplicates:', pctSameValsDups  (obj1, obj2));
console.log('% same values, no duplicates:  ', pctSameValsNoDups(obj1, obj2));
console.log('% same properties (k/v pairs): ', pctSameProps     (obj1, obj2));

// output:
// obj1: {"a":1,"b":2,"c":3,"d":3}
// obj2: {"a":1,"b":1,"e":2,"f":2,"g":3,"h":5}
// % same keys:                    50
// % same values, incl duplicates: 125
// % same values, no duplicates:   75
// % same properties (k/v pairs):  25

Yanıtlar:


18

Bunu okumakta zorlanıyorsunuz çünkü bu örnek çok okunaklı değil. Hiçbir suç amaçlanmamıştır, internette bulduğunuz örneklerin oldukça büyük bir kısmı da değildir. Birçok insan sadece hafta sonları fonksiyonel programlama ile oynar ve asla üretim fonksiyonel kodunu uzun vadede korumakla uğraşmak zorunda kalmaz. Ben daha böyle yazmak istiyorum:

function mapObj(obj, f) {
  return Object.keys(obj).map(key => f(obj, key));
}

function getPctSameXs(obj1, obj2, f) {
  const mapped1 = mapObj(obj1, f);
  const mapped2 = mapObj(obj2, f);
  const same = mapped1.filter(x => mapped2.indexOf(x) != -1);
  const percent = same.length / mapped1.length * 100;
  return percent;
}

const getValues = (obj, key) => obj[key];
const valuesWithDupsPercent = getPctSameXs(obj1, obj2, getValues);

Bazı nedenlerden dolayı, birçok insan kafasında fonksiyonel kodun büyük bir iç içe ifadenin belirli bir estetik "görünümüne" sahip olması gerektiği fikrine sahiptir. Sürümümün tüm noktalı virgüllerle bir şekilde zorunlu koda benzemesine rağmen, her şey değişmez, bu nedenle tüm değişkenleri değiştirebilir ve isterseniz büyük bir ifade alabilirsiniz. Gerçekten de spagetti versiyonu kadar "işlevsel" ama daha okunabilir.

Burada ifadeler çok küçük parçalara bölünür ve alan için anlamlı isimler verilir. Yuvalama, gibi ortak işlevler mapObjadlandırılmış bir işleve çekilerek önlenir . Lambdalar, bağlamda açık bir amacı olan çok kısa fonksiyonlar için ayrılmıştır.

Okunması zor bir kodla karşılaşırsanız, daha kolay olana kadar yeniden düzenleyin. Biraz pratik gerektirir, ama buna değer. Fonksiyonel kod, zorunlu olduğu kadar okunabilir olabilir. Aslında, genellikle moreso, çünkü genellikle daha özlüdür.


Kesinlikle hiçbir suç alınmadı! Hala biliyorum ki sürdürecek olan bazı ilgili söz konusu belki benim iddialar, fonksiyonel programlama hakkında bir şeyler ne kadar biraz fazla belirtti ben biliyorum idi. Ben gerçekten göreceli bir acemiyim. Bu özel girişimin nasıl bu kadar özlü, net ama yine de işlevsel bir şekilde yeniden yazılabileceğini görmek altın gibi görünüyor ... teşekkür ederim. Yeniden yazmanızı dikkatlice inceleyeceğim.
Andrew Willems

1
Uzun zincirlere sahip olmanın ve / veya yöntemlerin yuvalanmasının gereksiz ara değişkenleri ortadan kaldırdığını söylediğini duydum. Buna karşılık, cevabınız zincirlerimi / yuvalamayı iyi adlandırılmış ara değişkenler kullanarak ara bağımsız ifadelere böler. Kodunuzu bu durumda daha okunabilir buluyorum, ama ne kadar genel olmaya çalıştığınızı merak ediyorum. Uzun yöntem zincirlerinin ve / veya derin yuvalamanın genellikle kaçınılması gereken bir anti-desen olduğunu mu söylüyorsunuz, yoksa önemli faydalar sağladıkları zamanlar mı var? Ve bu sorunun cevabı, işlevsel ve zorunlu kodlama için farklı mıdır?
Andrew Willems

3
Ara değişkenlerin ortadan kaldırılmasının netlik kazandırabileceği bazı durumlar vardır. Örneğin, FP'de neredeyse hiçbir zaman bir dizinin dizinini istemezsiniz. Ayrıca bazen ara sonuç için harika bir isim yoktur. Benim tecrübelerime göre, çoğu insan başka şekilde çok fazla hata yapma eğilimindedir.
Karl Bielefeldt

6

Fonksiyonel Javascript bahsediyor çoğu insan haritaları, filtreleri kullanarak olabilir ve azaltır ama - ben bu olduğunu söyleyebilirim JavaScript derece işlevsel çalışması (bir sürü yapmadık kodunuzu kendi üst düzey fonksiyonlarını tanımlayan olan biraz daha gelişmiş), ancak Haskell'de bunu yaptım ve en azından bazı deneyimlerin tercüme ettiğini düşünüyorum. Öğrendiğim şeylere birkaç ipucu vereceğim:

İşlev türlerini belirlemek gerçekten önemlidir. Haskell, bir işlevin türünün ne olduğunu belirtmenizi gerektirmez, ancak tanımdaki türü dahil etmek okumayı çok daha kolay hale getirir. Javascript açık şekilde aynı şekilde yazmayı desteklemese de, tür tanımını bir yoruma dahil etmemek için hiçbir neden yoktur, örneğin:

// getXs :: forall O, F . O -> (O -> String -> F) -> [F]
const getXs = (obj, getX) =>
    Object.keys(obj).map(key => getX(obj)(key));

Bunun gibi tür tanımlarıyla çalışma konusunda biraz pratik yaparak, bir işlevin anlamını daha açık hale getirir.

Adlandırma, belki de prosedürel programlamadan daha da önemlidir. Birçok işlevsel program, konvansiyonda ağır olan çok kısa bir tarzda yazılır (örneğin, 'xs' bir liste / dizi ve 'x' in bir öğe olduğu konvansiyonu çok yaygındır), ancak bu stili anlamadığınız sürece kolayca daha ayrıntılı adlandırma öneririm. Kullandığınız belirli adlara bakıldığında, "getX" bir tür opaktır ve bu nedenle "getXs" de pek yardımcı olmaz. Ben "getXs" "ApplyToProperties" gibi bir şey çağırır ve "getX" muhtemelen "propertyMapper" olacaktır. "getPctSameXs" daha sonra "yüzdelikPropertiesSameWith" ("ile").

Bir diğer önemli şey deyimsel kod yazmaktır . a => b => some-expression-involving-a-and-bCurried işlevler üretmek için bir sözdizimi kullandığınızı fark ediyorum . Bu ilginç ve bazı durumlarda yararlı olabilir, ancak burada curried işlevlerden yararlanan hiçbir şey yapmıyorsunuz ve bunun yerine geleneksel çoklu argüman işlevlerini kullanmak daha deyimsel Javascript olacaktır. Bunu yapmak, bir bakışta neler olduğunu görmeyi kolaylaştırabilir. Ayrıca const name = lambda-expression, işlevlerini tanımlamak için de kullanıyorsunuz ; function name (args) { ... }bunun yerine , kullanmak daha deyimsel olurdu . Anlamsal olarak biraz farklı olduklarını biliyorum, ancak bu farklılıklara güvenmiyorsanız, mümkünse daha yaygın varyantı kullanmanızı öneririm.


5
Tipler için +1! Dilin onlara sahip olmaması, onları düşünmek zorunda olmadığınız anlamına gelmez . ECMAScript için çeşitli dokümantasyon sistemlerinin fonksiyon türlerini kaydetmek için bir yazı dili vardır. Bazı ECMAScript IDE'lerinin de bir tür dili vardır (ve genellikle ana dokümantasyon sistemleri için tür dillerini de anlarlar) ve bu tür ek açıklamaları kullanarak temel tür denetimi ve sezgisel ipucu bile yapabilirler .
Jörg W Mittag

Bana çiğnemem gereken çok şey verdin: tanımları, anlamlı isimleri, deyimleri kullanarak ... teşekkürler! Olası yorumlardan sadece birkaçı: Bazı bölümleri kıvrımlı işlevler olarak yazmak istemiyordum; Yazarken kodumu yeniden düzenlediğim için bu şekilde geliştiler. Şimdi bunun nasıl gerekli olmadığını görebiliyorum ve hatta sadece bu iki fonksiyondan parametreleri tek bir fonksiyon için iki parametreye birleştirmek sadece daha mantıklı değil aynı zamanda kısa biti en az daha okunabilir hale getiriyor.
Andrew Willems

@ JörgWMittag, türlerin önemi hakkındaki yorumlarınız ve yazdığınız diğer yanıtın bağlantısı için teşekkür ederiz. WebStorm kullanıyorum ve diğer yanıtınızı nasıl okuduğuma göre, WebStorm jsdoc benzeri ek açıklamaları nasıl yorumlayacağını biliyor. Yorumunuzdan, jsdoc ve WebStorm'un sadece zorunlu olan kodu değil, ek açıklama eklemek için birlikte kullanılabileceğini varsayıyorum, ancak bunu gerçekten bilmek için daha fazla araştırmak zorunda kalacağım. Daha önce jsdoc ile oynadım ve şimdi WebStorm ve ben orada işbirliği yapabileceğimi biliyorum, bu özelliği / yaklaşımı daha fazla kullanacağımı umuyorum.
Andrew Willems

@Jules, sadece yukarıda benim yorum atıfta bulundu Körili hangi işlevi netleştirmek için: Eğer ima gibi, her örneğini obj => key => ...basitleştiirlebilir (obj, key) => ...sonradan çünkü getX(obj)(key)ayrıca basitleştiirlebilir get(obj, key). Aksine, başka bir curried işlevi, (getX, filter = vals => vals) => (objA, objB) => ...en azından kodun geri kalanı bağlamında yazıldığı gibi kolayca basitleştirilemez.
Andrew Willems
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.