ਇਮੀਊਟੇਬਿਲਿਟੀ, ਪਿਊਰ ਫੰਕਸ਼ਨ ਅਤੇ map/filter ਵਰਗੀਆਂ ਫੰਕਸ਼ਨਲ ਧਾਰਣਾਵਾਂ ਲੋਕਪ੍ਰਿਯ ਭਾਸ਼ਾਵਾਂ ਵਿੱਚ ਮੁੜ-ਉਭਰ ਰਹੀਆਂ ਹਨ। ਜਾਣੋ ਇਹ ਕਿਉਂ ਮਦਦਗਾਰ ਹਨ ਅਤੇ ਕਦੋਂ ਵਰਤਣੇ ਚਾਹੀਦੇ ਹਨ।

“ਫੰਕਸ਼ਨਲ ਪ੍ਰੋਗ੍ਰਾਮਿੰਗ ਧਾਰਣਾਵਾਂ” ਦਰਅਸਲ ਆਮ ਆਦਤਾਂ ਅਤੇ ਭਾਸ਼ਾਈ ਫੀਚਰ ਹਨ ਜੋ ਗਣਨਾ ਨੂੰ ਮੁੱਲਾਂ ਵਾਂਗ ਦੇਖਦੇ ਹਨ, ਨਾ ਕਿ ਲਗਾਤਾਰ ਬਦਲ ਰਹੀ ਚੀਜ਼ਾਂ ਵਾਂਗ।
ਇੱਕ ਸਧਾਰਨ imperative ਕੋਡ ਦੀ ਥਾਂ, ਜੋ ਕਹਿੰਦਾ ਹੈ “ਇਹ ਕਰੋ, ਫਿਰ ਉਸਨੂੰ ਬਦਲੋ,” ਫੰਕਸ਼ਨਲ-ਸਟਾਈਲ ਕੋਡ ਇਸ ਤਰ੍ਹਾਂ ਝੁਕਦਾ ਹੈ: “ਇਨਪੁੱਟ ਲਓ, ਆਉਟਪੁੱਟ ਵਾਪਸ ਕਰੋ।” ਜਿੰਨਾ ਹੋਰ ਤੁਹਾਡੇ ਫੰਕਸ਼ਨ ਭਰੋਸੇਮੰਦ ਤਬਦੀਲੀਆਂ ਵਾਂਗ ਵਰਤਦੇ ਹਨ, ਉਤਨਾ ਹੀ ਪ੍ਰੋਗਰਾਮ ਦਾ ਵਿਵਹਾਰ ਅਨੁਮਾਨ ਲਗਾਉਣਾ ਆਸਾਨ ਹੁੰਦਾ ਹੈ।
ਜਦ ਲੋਕ ਕਹਿੰਦੇ ਹਨ ਕਿ Java, Python, JavaScript, C#, ਜਾਂ Kotlin “ਵਧੇਰੇ ਫੰਕਸ਼ਨਲ” ਹੋ ਰਹੇ ਹਨ, ਉਹ ਇਹ ਨਹੀਂ ਕਹਿ ਰਹੇ ਕਿ ਇਹ ਭਾਸ਼ਾਅ ਪੂਰੀ ਤਰ੍ਹਾਂ ਪਿਊਰ ਫੰਕਸ਼ਨਲ ਭਾਸ਼ਾਵਾਂ ਬਣ ਰਹੀਆਂ ਹਨ।
ਮਤਲਬ ਇਹ ਹੈ ਕਿ ਮੈਨਸਟ੍ਰੀਮ ਭਾਸ਼ਾ ਡਿਜ਼ਾਇਨ ਵਧੀਆ ਵਿਚਾਰਾਂ (ਜਿਵੇਂ lambdas ਅਤੇ higher-order functions) ਉਧਾਰ ਲੈ ਰਿਹਾ ਹੈ—ਤਾਕਿ ਜਦ ਜ਼ਰੂਰਤ ਹੋਵੇ ਤੁਸੀਂ ਆਪਣੇ ਕੋਡ ਦੇ ਕੁਝ ਹਿੱਸਿਆਂ ਨੂੰ ਫੰਕਸ਼ਨਲ ਅੰਦਾਜ਼ ਵਿੱਚ ਲਿਖ ਸਕੋ ਅਤੇ ਜਦ ਪਰਸਪਸ਼ਟ ਹੋਵੇ ਤਾਂ ਜਾਂ ਤਰਤੀਬੀ/OO ਢੰਗ ਦੌਰਾਨ ਰਹੋ।
ਫੰਕਸ਼ਨਲ ਧਾਰਣਾਵਾਂ ਅਕਸਰ hidden state ਨੂੰ ਘਟਾ ਕੇ ਅਤੇ ਵਿਵਹਾਰ ਨੂੰ ਤਰਕ-ਯੋਗ ਬਣਾ ਕੇ software ਰੱਖ-ਰਖਾਅ ਨੂੰ ਬਿਹਤਰ ਬਣਾਉਂਦੀਆਂ ਹਨ। ਇਹ concurrency ਵਿੱਚ ਵੀ ਮਦਦ ਕਰ ਸਕਦੀਆਂ ਹਨ, ਕਿਉਂਕਿ ਸਾਂਝਾ ਮਿਊਟੇਬਲ ਸਟੇਟ race conditions ਦਾ ਇੱਕ ਵੱਡਾ ਸਰੋਤ ਹੁੰਦਾ ਹੈ।
ਪਰ ਟਰੇਡ-ਆਫ਼ ਸੱਚ-ਮੁਚ ਮੌਜੂਦ ਹਨ: ਵਾਧੂ abstraction ਅਜਨਾ unfamiliar ਲੱਗ ਸਕਦਾ ਹੈ, ਇਮੀਊਟੇਬਿਲਿਟੀ ਕੁਝ ਹਾਲਤਾਂ ਵਿੱਚ overhead ਵਧਾ ਸਕਦੀ ਹੈ, ਅਤੇ “ਚਲਾਕ” composition readability ਨੂੰ ਨੁਕਸਾਨ ਪਹੁੰਚਾ ਸਕਦਾ ਹੈ ਜੇ ਜ਼ਰੂਰਤ ਤੋਂ ਜ਼ਿਆਦਾ ਵਰਤਿਆ ਜਾਵੇ।
ਇੱਥੇ “ਫੰਕਸ਼ਨਲ ਧਾਰਣਾਵਾਂ” ਦੇ ਉਹ ਅਸਲੀ ਮਤਲਬ ਹਨ ਜੋ ਇਸ ਲੇਖ ਵਿੱਚ ਵਰਤੇ ਜਾਣਗੇ:
ਇਹ ਇੱਕ ਧਰਮ-ਭਾਵ ਨਾ ਹੋ ਕੇ ਵਰਤਣਯੋਗ ਸੰਦ ਹਨ—ਮਕਸਦ ਉਹਨਾਂ ਨੂੰ ਓਸ ਥਾਂ ਵਰਤਣਾ ਹੈ ਜਿੱਥੇ ਕੋਡ ਸਧਾਰਣ ਅਤੇ ਸੁਰੱਖਿਅਤ ਬਣਦਾ ਹੈ।
ਫੰਕਸ਼ਨਲ ਪ੍ਰੋਗ੍ਰਾਮਿੰਗ ਨਵਾਂ ਰੁਝਾਨ ਨਹੀਂ ਹੈ; ਇਹ ਵਿਚਾਰਾਂ ਦਾ ਇੱਕ ਸੈੱਟ ਹੈ ਜੋ ਹਰ ਵਾਰੀ ਮੁੜ ਆਉਂਦਾ ਹੈ ਜਦੋਂ ਮੈਨਸਟ੍ਰੀਮ ਵਿਕਾਸ ਨੂੰ ਸਕੇਲਿੰਗ ਦੀਆਂ ਸਮੱਸਿਆਵਾਂ ਦਾ ਸਾਹਮਣਾ ਕਰਨਾ ਪੈਂਦਾ—ਵੱਡੇ ਸਿਸਟਮ, ਵੱਡੀਆਂ ਟੀਮਾਂ, ਅਤੇ ਨਵਾਂ ਹਾਰਡਵੇਅਰ।
1950-60 ਦੇ ਦਹਾਕਿਆਂ ਵਿੱਚ Lisp ਵਰਗੀਆਂ ਭਾਸ਼ਾਵਾਂ ਨੇ ਫੰਕਸ਼ਨਾਂ ਨੂੰ ਅਸਲ ਮੁੱਲਾਂ ਵਾਂਗ ਸਵੀਕਾਰ ਕੀਤਾ—ਜਿਨ੍ਹਾਂ ਨੂੰ ਅਸੀਂ ਹੁਣ higher-order functions ਕਹਿੰਦੇ ਹਾਂ। ਉਸੇ ਦੌਰ ਨੇ lambda ਨੋਟੇਸ਼ਨ ਦੀ ਜੜਾਂ ਵੀ ਦਿੱਤੀਆਂ।
1970-80 ਦੇ ਦਹਾਕਿਆਂ ਵਿੱਚ ML ਅਤੇ ਬਾਅਦ ਵਿੱਚ Haskell ਵਰਗੀਆਂ ਫੰਕਸ਼ਨਲ ਭਾਸ਼ਾਵਾਂ ਨੇ immutability ਅਤੇ ਮਜ਼ਬੂਤ type-driven ਡਿਜ਼ਾਈਨ ਨੂੰ ਅਗੇ ਵਧਾਇਆ, ਜ਼ਿਆਦਾਤਰ ਅਕਾਦਮਿਕ ਜਾਂ ਨਿਸ਼ ਉਦਯੋਗਿਕ ਸੈਟਿੰਗਾਂ ਵਿੱਚ। ਦੂਜੇ ਪਾਸੇ, ਬਹੁਤ ਸਾਰੀਆਂ ਮੈਨਸਟਰੀਮ ਭਾਸ਼ਾਵਾਂ ਨੇ ਹੌਲੀ-ਹੌਲੀ ਕੁਝ ਹਿੱਸੇ ਉਧਾਰ ਲਏ: ਸਕ੍ਰਿਪਟਿੰਗ ਭਾਸ਼ਾਵਾਂ ਨੇ functions ਨੂੰ ਡੇਟਾ ਵਾਂਗ ਵਰਤਣਾ ਆਮ ਕੀਤਾ।
2000 ਅਤੇ 2010 ਦੇ ਦਹਾਕਿਆਂ ਵਿੱਚ ਫੰਕਸ਼ਨਲ ਵਿਚਾਰਾਂ ਨੂੰ ਅਣਦੇਖਾ ਕਰਨਾ ਔਖਾ ਹੋ ਗਿਆ:
ਹਾਲ ਹੀ ਵਿੱਚ Kotlin, Swift, ਅਤੇ Rust ਵਰਗੀਆਂ ਭਾਸ਼ਾਵਾਂ ਨੇ function-based collection ਟੂਲਾਂ ਅਤੇ ਸੁਰੱਖਿਅਤ ਡਿਫਾਲਟਸ ਨੂੰ ਅਪਣਾ ਕੇ ਇਹ ਰੁਝਾਨ ਅੱਗੇ ਵਧਾਇਆ, ਅਤੇ ਬਹੁਤ ਸਾਰੀਆਂ ਇਕੋ-ਇਕੋ ਐਕੋਸਿਸਟਮ ਵਿੱਚ ਫਰੇਮਵਰਕ pipelines ਅਤੇ ਡਿਕਲਰਟਿਵ ਤਬਦੀਲੀਆਂ ਨੂੰ ਉਤਸ਼ਾਹਿਤ ਕਰਦੇ ਹਨ।
ਇਹ ਵਿਚਾਰ ਵਾਪਸ ਆਉਂਦੇ ਹਨ ਕਿਉਂਕਿ ਸੰਦਰਭ ਮੁੜ-ਬਦਲਦਾ ਹੈ। ਜਦ ਪ੍ਰੋਗਰਾਮ ਛੋਟੇ ਅਤੇ ਅਕਸਰ single-threaded ਹੁੰਦੇ ਸਨ, ਤਾਂ ਇਕ ਵੈਰੀਏਬਲ ਮਿਊਟੇ ਕਰਨਾ ਠੀਕ ਸੀ। ਪਰ ਜਿਵੇਂ ਜਟਿਲਤਾ ਵਧੀ—distributation, concurrency, ਅਤੇ ਵੱਡੀਆਂ ਟੀਮਾਂ—ਚੁਪਚਾਪ coupling ਦਾ ਖ਼ਰਚ ਵਧ ਗਿਆ।
ਫੰਕਸ਼ਨਲ patterns (lambdas, collection pipelines, explicit async flows) ਆਮ ਤੌਰ 'ਤੇ ਨਿਰਭਰਤਾਵਾਂ ਨੂੰ ਵੇਖਣਯੋਗ ਬਣਾਉਂਦੇ ਹਨ ਅਤੇ ਵਿਵਹਾਰ ਨੂੰ ਅਣੁਮਾਨਯੋਗ ਘਟਨਾਂ ਤੋਂ ਬਚਾਉਂਦੇ ਹਨ। ਭਾਸ਼ਾ ਡਿਜ਼ਾਈਨਰ ਇਹਨਾਂ ਨੂੰ ਮੁੜ-ਪ੍ਰਵੇਸ਼ ਕਰਦੇ ਰਹਿੰਦੇ ਹਨ ਕਿਉਂਕਿ ਇਹ ਆਧੁਨਿਕ ਜਟਿਲਤਾ ਲਈ ਪ੍ਰਾਇਗਮੈਟਿਕ ਟੂਲ ਹਨ।
ਪੇਸ਼ਗੋਈਯੋਗ ਕੋਡ ਉਹ ਹੈ ਜੋ ਇੱਕੋ ਹਾਲਤ ਵਿੱਚ ਹਰ ਵਾਰੀ ਇਕੋ ਹੀ ਤਰੀਕੇ ਨਾਲ ਕੰਮ ਕਰਦਾ ਹੈ। ਇਹ ਉਹੀ ਚੀਜ਼ ਹੈ ਜੋ ਗੁੰਮ ਹੋ ਜਾਂਦੀ ਹੈ ਜਦੋਂ ਫੰਕਸ਼ਨ ਛੁਪੇ ਸਟੇਟ, ਵਰਤਮਾਨ ਸਮਾਂ, ਗਲੋਬਲ ਸੈਟਿੰਗਸ ਜਾਂ ਪਹਿਲਾਂ ਕੀ ਹੋਇਆ ਉਸ 'ਤੇ ਨਿਰਭਰ ਕਰਦੇ ਹਨ।
ਜਦ ਵਿਵਹਾਰ ਪੇਸ਼ਗੋਈਯੋਗ ਹੁੰਦਾ ਹੈ, ਡੀਬੱਗਿੰਗ ਡਿਟੈਕਟਿਵ ਵਰਗੀ ਨਹੀਂ ਰਹਿੰਦੀ—ਤੁਸੀਂ ਸਮੱਸਿਆ ਨੂੰ ਛੋਟੇ ਹਿੱਸੇ ਤੱਕ ਸੰਕੋਚਿਤ ਕਰ ਸਕਦੇ ਹੋ, ਉਸਨੂੰ ਦੁਹਰਾਉਂ ਅਤੇ ਸਹੀ ਕਰੋ ਬਿਨਾਂ ਡਰ ਦੇ ਕਿ ਸੱਚੀ ਵਜ੍ਹਾ ਕਿਤੇ ਹੋਰ ਹੈ।
ਜ਼ਿਆਦਾਤਰ ਡੀਬੱਗਿੰਗ ਸਮਾਂ ਟਾਈਪ ਕਰਨ ਲਈ ਨਹੀਂ, ਬਲਕਿ ਇਹ ਪਤਾ ਲਗਾਉਣ ਲਈ ਲਗਦਾ ਹੈ ਕਿ ਕੋਡ ਨੇ ਅਸਲ ਵਿੱਚ ਕਿਆ ਕੀਤਾ। ਫੰਕਸ਼ਨਲ ਵਿਚਾਰ ਤੁਹਾਨੂੰ ਉਸ ਤਰ੍ਹਾਂ ਧੱਕਦੇ ਹਨ ਜੋ ਲੋਕਲ ਤੌਰ 'ਤੇ ਤਰਕ-ਯੋਗ ਬਣੇ:
ਇਸਦਾ ਮਤਲਬ ਘੱਟ “ਇਹ ਸਿਰਫ਼ ਮੰਗਲਵਾਰ ਨੂੰ ਟੁੱਟਦਾ ਹੈ” ਬਗ, ਘੱਟ ਛਪਾਈ ਵਾਲੀਆਂ statements, ਅਤੇ ਘੱਟ ਐਸੇ fixes ਜੋ ਅਣਜਾਣੇ ਤੌਰ 'ਤੇ ਦੂਜੇ ਹਿੱਸੇ 'ਚ ਨਵਾਂ ਬਗ ਪੈਦਾ ਕਰਨ।
ਇੱਕ ਪਿਊਰ ਫੰਕਸ਼ਨ (ਇਕੋ ਇਨਪੁੱਟ → ਇਕੋ ਆਉਟਪੁੱਟ, ਕੋਈ side effects ਨਹੀਂ) unit tests ਲਈ ਦੋਸਤਾਨਾ ਹੁੰਦਾ ਹੈ। ਤੁਹਾਨੂੰ ਜ਼ਿਆਦਾ environment ਸੈਟ ਕਰਨ, ਅੱਧੇ ਐਪ ਨੂੰ mock ਕਰਨ ਜਾਂ ਟੈਸਟ ਰਨਾਂ ਵਿਚ ਗਲੋਬਲ ਸਟੇਟ ਰੀਸੈੱਟ ਕਰਨ ਦੀ ਲੋੜ ਨਹੀਂ। ਤੁਸੀਂ ਇਸਨੂੰ ਰੀਫੈਕਟਰ ਦੌਰਾਨ ਵੀ ਦੁਬਾਰਾ ਵਰਤ ਸਕਦੇ ਹੋ ਕਿਉਂਕਿ ਇਹ ਇਹ ਨਹੀਂ ਮੰਨੇ ਕਿ ਇਹ ਕਿੱਥੇ ਕਾਲ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ।
ਇਹ ਅਸਲ ਕੰਮ ਵਿੱਚ ਮਾਇਣਕ ਹੈ:
ਪਹਿਲਾਂ: calculateTotal() ਇੱਕ ਗਲੋਬਲ discountRate ਪੜ੍ਹਦਾ ਹੈ, ਇਕ ਗਲੋਬਲ “holiday mode” ਫਲੈਗ ਚੈੱਕ ਕਰਦਾ ਹੈ, ਅਤੇ ਗਲੋਬਲ lastTotal ਨੂੰ ਅਪਡੇਟ ਕਰਦਾ ਹੈ। ਇੱਕ ਬੱਗ ਰਿਪੋਰਟ ਦਿੰਦੀ ਹੈ ਕਿ totals “ਕਦੇ-ਕਦੇ ਗਲਤ” ਹੁੰਦੇ ਹਨ। ਹੁਣ ਤੁਸੀਂ state ਦਾ ਪਿੱਛਾ ਕਰ ਰਹੇ ਹੋ।
ਬਾਅਦ: calculateTotal(items, discountRate, isHoliday) ਇੱਕ ਨੰਬਰ ਵਾਪਸ ਕਰਦਾ ਹੈ ਅਤੇ ਹੋਰ ਕੁਝ ਨਹੀਂ ਬਦਲਦਾ। ਜੇ totals ਗਲਤ ਹਨ, ਤੁਸੀਂ ਇਨਪੁੱਟ ਲਾਗ ਕਰਕੇ ਤੁਰੰਤ ਸਮੱਸਿਆ ਦੁਹਰਾਉਂ ਸਕਦੇ ਹੋ।
ਪੇਸ਼ਗੋਈਯੋਗਤਾ ਇੱਕ ਮੁੱਖ ਕਾਰਨ ਹੈ ਜਿਸ ਕਰਕੇ ਫੰਕਸ਼ਨਲ ਫੀਚਰ ਮੈਨਸਟਰੀਮ ਭਾਸ਼ਾਵਾਂ ਵਿੱਚ ਜ਼ਿਆਦਾ ਜੋੜੇ ਜਾਂਦੇ ਹਨ: ਉਹ ਰੋਜ਼ਾਨਾ ਮੈਨਟੇਨੈਂਸ ਕੰਮ ਨੂੰ ਘੱਟ ਹੈਰਾਨੀਆਂ ਵਾਲਾ ਬਣਾਉਂਦੇ ਹਨ, ਅਤੇ ਹੈਰਾਨੀਆਂ ਹੀ ਸਾਫ਼ਟਵੇਅਰ ਨੂੰ ਮਹਿੰਗਾ ਬਣਾਉਂਦੇ ਹਨ।
“Side effect” ਉਹ ਕੁਝ ਵੀ ਹੈ ਜੋ ਕੋਡ ਇੱਕ ਕੀਮਤ ਕੈਲਕੁਲੇਟ ਕਰਨ ਅਤੇ ਵਾਪਸ ਕਰਨ ਤੋਂ ਇਲਾਵਾ ਕਰਦਾ ਹੈ। ਜੇਕਰ ਫੰਕਸ਼ਨ ਆਪਣੀਆਂ ਇਨਪੁੱਟ ਤੋਂ ਬਾਹਰ ਕੁਝ ਪੜ੍ਹਦਾ ਜਾਂ ਬਦਲਦਾ ਹੈ—ਫਾਈਲ, ਡੇਟਾਬੇਸ, ਵਰਤਮਾਨ ਸਮਾਂ, ਗਲੋਬਲ ਵੈਰੀਏਬਲ, ਨੈਟਵਰਕ ਕਾਲ—ਤਦ ਇਹ ਗਣਨਾ ਤੋਂ ਬਾਹਰ ਕੁਝ ਹੋਰ ਕਰ ਰਿਹਾ ਹੈ।
ਰੋਜ਼ਾਨਾ ਉਦਾਹਰਨ ਬਹੁਤ ਆਮ ਹਨ: ਲਾਗ ਲਿਖਣਾ, ਆਰਡਰ ਨੂੰ ਡੇਟਾਬੇਸ ਵਿੱਚ ਸੇਵ ਕਰਨਾ, ਈਮੇਲ ਭੇਜਣਾ, cache ਅਪਡੇਟ ਕਰਨਾ, environment variables ਪੜ੍ਹਨਾ, ਜਾਂ ਰੈਂਡਮ ਨੰਬਰ ਬਣਾਉਣਾ। ਇਹਨਾਂ ਵਿੱਚੋਂ ਕੋਈ ਵੀ “ਖਰਾਬ” ਨਹੀਂ, ਪਰ ਇਹ ਤੁਹਾਡੇ ਪ੍ਰੋਗ੍ਰਾਮ ਦੇ ਚਾਰਮ੍ਹਾਂ ਨੂੰ ਬਦਲ ਦਿੰਦੀਆਂ ਹਨ—ਅਤੇ ਓਥੇ ਹੀ ਹੈਰਾਨੀਆਂ ਸ਼ੁਰੂ ਹੁੰਦੀਆਂ ਹਨ।
ਜਦ ਪ੍ਰਭਾਵ ਆਮ ਲੋਜਿਕ ਵਿੱਚ ਮਿਲ ਜਾਂਦੇ ਹਨ ਤਾਂ ਵਿਵਹਾਰ “ਡੇਟਾ-ਇਨ, ਡੇਟਾ-ਆਉਟ” ਰਹਿਣਾ ਛੱਡ ਦਿੰਦਾ ਹੈ। ਇਕੋ ਇਨਪੁੱਟ ਵੱਖ-ਵੱਖ ਨਤੀਜੇ ਦੇ ਸਕਦਾ ਹੈ, ਇਹ ਇਸ ਗੱਲ 'ਤੇ ਨਿਰਭਰ ਕਰਦਾ ਹੈ ਕਿ ਡੇਟਾਬੇਸ ਵਿੱਚ پہلے ਕੀ ਹੈ, ਕਿਹੜਾ ਯੂਜ਼ਰ ਲੌਗ-ਇਨ ਹੈ, feature flag ਚਾਲੂ ਹੈ ਜਾਂ ਨਹੀਂ, ਜਾਂ ਕੋਈ ਨੈਟਵਰਕ ਕਾਲ fail ਹੋ ਗਈ। ਇਸ ਨਾਲ bugs ਦੁਹਰਾਉਣ ਅਤੇ fixes 'ਤੇ ਭਰੋਸਾ ਕਰਨ 'ਚ ਮੁਸੀਬਤ ਆਉਂਦੀ ਹੈ।
ਇਹ ਡੀਬੱਗਿੰਗ ਨੂੰ ਵੀ ਤੇਜ਼ੀ ਨਾਲ ਜਟਿਲ ਕਰਦਾ ਹੈ। ਜੇ ਇੱਕ ਫੰਕਸ਼ਨ ਇੱਕੋ-ਥਾਂ ਡਿਸਕਾਊਂਟ ਕੈਲਕੁਲੇਟ ਕਰਦਾ ਅਤੇ ਡੇਟਾਬੇਸ ਵਿੱਚ ਲਿਖਦਾ ਵੀ ਹੈ, ਤਾਂ ਇਹਨੂੰ ਜਾਂਚ ਲਈ ਦੋ ਵਾਰੀ ਕਾਲ ਕਰਨਾ ਸੁਰੱਖਿਅਤ ਨਹੀਂ—ਕਿਉਂਕਿ ਦੋ ਵਾਰੀ ਕਾਲ ਕਰਨ ਨਾਲ ਦੋ ਰਿਕਾਰਡ ਬਣ ਸਕਦੇ ਹਨ।
ਫੰਕਸ਼ਨਲ ਪ੍ਰਿੰਸਿਪਲ ਇੱਕ ਸਧਾਰਣ ਵੰਡ ਦਰਸਾਉਂਦੇ ਹਨ:
ਇਸ ਵੰਡ ਨਾਲ ਤੁਸੀਂ ਆਪਣਾ ਬਹੁਤ ਸਾਰਾ ਕੋਡ ਡੇਟਾਬੇਸ ਦੇ ਬਿਨਾਂ ਜਾਂ ਕਾਫ਼ੀ ਘੱਟ mocking ਦੇ ਨਾਲ ਟੈਸਟ ਕਰ ਸਕਦੇ ਹੋ ਅਤੇ ਇਹ ਡਰ ਨਹੀਂ ਕਿ ਇੱਕ “ਸਧਾਰਣ” ਕੈਲਕੁਲੇਸ਼ਨ ਕਿਸੇ ਲਿਖਤ ਨੂੰ ਚਾਲੂ ਕਰ ਦੇਵੇ।
ਸਭ ਤੋਂ ਆਮ ਨੁਕਸ “effect creep” ਹੈ: ਇੱਕ ਫੰਕਸ਼ਨ "ਸਿਰਫ਼ ਥੋੜ੍ਹਾ ਲਾਗ" ਕਰਦਾ, ਫਿਰ config ਪੜ੍ਹਦਾ, ਫਿਰ ਮੈਟਰਿਕ ਲਿਖਦਾ, ਫਿਰ ਕੋਈ ਸਰਵਿਸ ਕਾਲ ਕਰਦਾ। ਜਲਦੀ ਹੀ, ਬਹੁਤ ਸਾਰੇ ਹਿੱਸੇ ਛੁਪੇ ਵਿਵਹਾਰ 'ਤੇ ਨਿਰਭਰ ਕਰਨ ਲੱਗਦੇ ਹਨ।
ਇੱਕ ਚੰਗਾ rule of thumb: ਮੂਲ ਫੰਕਸ਼ਨਾਂ ਨੂੰ ਸਧਾਰਣ ਰੱਖੋ—ਇਨਪੁੱਟ ਲਓ, ਆਉਟਪੁੱਟ ਦਿਓ—ਅਤੇ side effects ਨੂੰ ਸਪਸ਼ਟ ਅਤੇ ਲੱਭਣਯੋਗ ਬਣਾਉ।
ਇਮੀਊਟੇਬਿਲਿਟੀ ਇੱਕ ਸਧਾਰਣ ਨਿਯਮ ਹੈ ਜਿਸਦੇ ਵੱਡੇ ਪ੍ਰਭਾਵ ਹਨ: ਕੀਮਤ ਨੂੰ ਬਦਲੋ ਨਾ—ਨਵਾਂ ਵਰਜਨ ਬਣਾਓ।
ਇਨ-ਪਲੇਸ ऑਬਜੈਕਟ ਨੂੰ ਸੋਧਣ ਦੀ ਥਾਂ, ਇਕ ਇਮੀਊਟੇਬਲ ਰੁਝਾਨ ਨਵੀ ਨਕਲ ਬਣਾਉਂਦਾ ਹੈ ਜੋ ਅਪਡੇਟ ਨੂੰ ਦਰਸਾਉਂਦਾ ਹੈ। ਪੁਰਾਣੀ ਵਰਜਨ ਬਿਲਕੁਲ ਉਸੇ ਤਰ੍ਹਾਂ ਰਹਿੰਦੀ ਹੈ, ਜੋ ਪ੍ਰੋਗ੍ਰਾਮ ਨੂੰ ਸੋਚਣ ਵਿੱਚ ਅਸਾਨ ਬਣਾਉਂਦਾ ਹੈ: ਇਕ ਵਾਰ ਕੋਈ ਮੁੱਲ ਬਣ ਗਿਆ, ਉਹ ਅਚਾਨਕ ਬਦਲਿਆ ਨਹੀਂ ਜਾਵੇਗਾ।
ਰੋਜ਼ਾਨਾ ਬੱਗ ਆਮ ਤੌਰ 'ਤੇ shared state ਤੋਂ ਆਉਂਦੇ ਹਨ—ਉਸੇ ਡੇਟਾ ਨੂੰ ਕਈ ਜਗ੍ਹਾਂ ਤੋਂ ਰੈਫਰੈਂਸ ਕੀਤਾ ਜਾਣਾ। ਜੇ ਕਿਸੇ ਹਿੱਸੇ ਨੇ ਉਸਨੂੰ ਸੋਧਿਆ, ਹੋਰ ਹਿੱਸੇ ਇੱਕ ਅਧ-ਅਪਡੇਟ ਵਿਚਲੀ ਕੀਮਤ ਦੇਖ ਸਕਦੇ ਹਨ ਜਾਂ ਉਹ ਬਦਲਾਅ ਜੋ ਉਨ੍ਹਾਂ ਨੇ ਉਮੀਦ ਨਹੀਂ ਕੀਤਾ।
ਇਮੀਊਟੇਬਿਲਿਟੀ ਨਾਲ:
ਇਹ ਖ਼ਾਸ ਕਰਕੇ ਉਹਨਾਂ ਹਾਲਤਾਂ ਵਿੱਚ ਮਦਦਗਾਰ ਹੁੰਦਾ ਹੈ ਜਦ ਡੇਟਾ ਵਿਸ਼ਾਲ ਤੌਰ 'ਤੇ ਪਾਸ ਹੋ ਰਿਹਾ ਹੈ (ਕੰਫਿਗ, ਯੂਜ਼ਰ ਸਟੇਟ, ਐਪ-ਵਿਆਪਕ ਸੈਟਿੰਗਸ) ਜਾਂ concurrent ਤੌਰ 'ਤੇ ਵਰਤਿਆ ਜਾ ਰਿਹਾ ਹੈ।
ਇਮੀਊਟੇਬਿਲਿਟੀ ਮੁਫ਼ਤ ਨਹੀਂ। ਅਗਰ ਗਲਤ ਤਰੀਕੇ ਨਾਲ ਲਾਗੂ ਕੀਤੀ ਜਾਵੇ ਤਾਂ ਤੁਸੀਂ ਮੈਮੋਰੀ, ਪ੍ਰਦਰਸ਼ਨ, ਜਾਂ ਵਾਧੂ ਕਾਪੀ ਦਾ ਭੁਗਤਾਨ ਕਰ ਸਕਦੇ ਹੋ—ਉਦਾਹਰਣ ਲਈ, ਘਣੇ ਲੂਪਾਂ ਵਿੱਚ ਵੱਡੀਆਂ ਐਰੇਜ਼ ਨੂੰ ਬਾਰ-ਬਾਰ ਕਲੋਨ ਕਰਨਾ।
ਆਧੁਨਿਕ ਭਾਸ਼ਾਵਾਂ ਅਤੇ ਲਾਇਬ੍ਰੇਰੀਆਂ structural sharing ਵਰਗੀਆਂ ਤਕਨੀਕਾਂ ਨਾਲ ਇਹ ਖ਼ਰਚ ਘਟਾਉਂਦੀਆਂ ਹਨ, ਪਰ ਫਿਰ ਵੀ ਸੋਚ-ਵਿਚਾਰ ਨਾਲ ਕੰਮ ਲੈਣਾ ਚਾਹੀਦਾ ਹੈ।
ਇਮੀਊਟੇਬਿਲਿਟੀ ਨੂੰ ਪ੍ਰਾਥਮਿਕਤਾ ਦਿਓ ਜਦ:
ਨਿਯੰਤ੍ਰਿਤ mutation 'ਤੇ ਵਿਚਾਰ ਕਰੋ ਜੇ:
ਇੱਕ ਲਾਭਦਾਇਕ ਸਮਝੌਤਾ ਇਹ ਹੈ: ਬਾਅਉਂਡਰੀਆਂ 'ਤੇ ਡੇਟਾ ਨੂੰ ਇਮੀਊਟੇਬਲ ਸਮਝੋ ਅਤੇ ਛੋਟੇ, ਵਧੀਆ-ਨਿਯੰਤਰਿਤ implementation ਵੇਖੋ ਜਿੱਥੇ ਸਥਾਨਕ mutation ਮਨਿਆ ਜਾਵੇ।
ਫੰਕਸ਼ਨਲ-ਸਟਾਈਲ ਕੋਡ ਵਿੱਚ ਇੱਕ ਵੱਡਾ ਬਦਲਾਅ ਫੰਕਸ਼ਨਾਂ ਨੂੰ ਮੁੱਲਾਂ ਵਾਂਗ ਵਰਤਣ ਦਾ ਹੈ। ਇਸਦਾ ਮਤਲਬ ਹੈ ਕਿ ਤੁਸੀਂ ਇੱਕ ਫੰਕਸ਼ਨ ਨੂੰ ਵੈਰੀਏਬਲ ਵਿੱਚ ਰੱਖ ਸਕਦੇ ਹੋ, ਦੂਜੇ ਫੰਕਸ਼ਨ ਨੂੰ ਪਾਸ ਕਰ ਸਕਦੇ ਹੋ, ਜਾਂ ਇੱਕ ਫੰਕਸ਼ਨ ਵਿੱਚੋਂ ਵਾਪਸ ਕਰ ਸਕਦੇ ਹੋ—ਬਿਲਕੁਲ ਡੇਟਾ ਵਰਗ।
ਇਹ ਲਚਕੀਲਾਪਣ higher-order functions ਨੂੰ ਪ੍ਰਾਇਗਮੈਟਿਕ ਬਣਾਉਂਦਾ ਹੈ: ਤਾਂ ਕਿ ਤੁਸੀਂ ਇੱਕੋ ਲੂਪ ਲਾਜ਼ਮੀ ਤੌਰ 'ਤੇ ਦੁਹਰਾਉਣ ਦੀ ਥਾਂ ਇੱਕ reusable helper ਲਿਖੋ, ਅਤੇ ਜਰੂਰਤ ਮੁਤਾਬਿਕ ਵਰਤਣ ਲਈ ਇਕ ਛੋਟੀ ਫੰਕਸ਼ਨ ਪਾਸ ਕਰੋ।
ਜੇ ਤੁਸੀਂ ਵਿਹਾਰ ਨੂੰ ਆਸਾਨੀ ਨਾਲ ਪਾਸ ਕਰ ਸਕਦੇ ਹੋ, ਤਾਂ ਕੋਡ ਜ਼ਿਆਦਾ ਮੋਡੀਊਲਰ ਹੋ ਜਾਂਦਾ ਹੈ। ਤੁਸੀਂ ਇੱਕ ਛੋਟੀ ਫੰਕਸ਼ਨ ਬਣਾਉਂਦੇ ਹੋ ਜੋ ਦੱਸਦੀ ਹੈ ਕਿ ਇੱਕ ਆਈਟਮ 'ਤੇ ਕੀ ਕਰਨਾ ਹੈ, ਫਿਰ ਉਸਨੂੰ ਉਸ ਟੂਲ ਨੂੰ ਦਿੰਦੇ ਹੋ ਜੋ ਜਾਣਦਾ ਹੈ ਕਿਵੇਂ ਹਰ ਆਈਟਮ 'ਤੇ ਲਾਗੂ ਕਰਨਾ ਹੈ।
const addTax = (price) => price * 1.2;
const pricesWithTax = prices.map(addTax);
ਇੱਥੇ, addTax ਇੱਕ loop ਵਿੱਚ ਸਿੱਧਾ ਨਹੀਂ ਬੁਲਾਈ ਜਾਂਦੀ। ਇਹ map ਵਿੱਚ ਪਾਸ ਕੀਤੀ ਜਾਂਦੀ ਹੈ ਜੋ iteration ਸੰਭਾਲਦਾ ਹੈ।
[a, b, c] → [f(a), f(b), f(c)]predicate(item) ਸੱਚ ਹੋਵੇconst total = orders
.filter(o => o.status === "paid")
.map(o => o.amount)
.reduce((sum, amount) => sum + amount, 0);
ਇਹ ਇੱਕ pipeline ਵਾਂਗ ਪੜ੍ਹਦਾ ਹੈ: paid orders ਚੁਣੋ, amounts ਕੱਡੋ, ਫਿਰ ਜੋੜੋ।
ਰਵਾਇਤੀ ਲੂਪ ਅਕਸਰ iteration, branching, ਅਤੇ ਬਿਜ਼ਨਸ ਰੂਲ ਇੱਕ ਹੀ ਜਗ੍ਹਾ ਮਿਲਾ ਦਿੰਦੇ ਹਨ। higher-order functions ਉਹਨਾਂ ਨੂੰ ਵੱਖ ਕਰ ਦਿੰਦੇ ਹਨ। ਲੂਪ ਅਤੇ accumulation ਲਈ ਆਮ ਤਰੀਕਿਆਂ ਦਾ ਇਸਤੇਮਾਲ ਹੁੰਦਾ ਹੈ, ਅਤੇ ਤੁਹਾਡਾ ਕੋਡ ਫੋਕਸ ਕਰਦਾ ਹੈ “ਨਿਯਮ” ਤੇ (ਛੋਟੀਆਂ ਫੰਕਸ਼ਨਾਂ ਜੋ ਤੁਸੀਂ ਪਾਸ ਕਰਦੇ ਹੋ)। ਇਹ copy-paste ਹੋਈਆਂ ਲੂਪਾਂ ਅਤੇ ਇਕ-ਵਾਰੀਆਂ variants ਨੂੰ ਘੱਟ ਕਰਨ ਦਾ ਰੁਝਾਨ ਰੱਖਦਾ ਹੈ।
Pipelines ਵਧੀਆ ਹਨ ਜਦ ਤੱਕ ਉਹ ਗੂੜ੍ਹੇ ਜਾਂ ਬਹੁਤ clever ਨਾ ਹੋਣ। ਜੇ ਤੁਸੀਂ ਬਹੁਤ ਸਾਰੀਆਂ ਤਬਦੀਲੀਆਂ ਸਤਰਾਂ 'ਤੇ ਸਟੈਕ ਕਰ ਰਹੇ ਹੋ ਜਾਂ ਲੰਬੇ inline callbacks ਲਿਖ ਰਹੇ ਹੋ, ਤਾਂ ਵਿਚਾਰ ਕਰੋ:
ਫੰਕਸ਼ਨਲ ਨਿਰਮਾਣੀ ਢੇਰ ਉਹਨਾਂ ਨੂੰ ਸਭ ਤੋਂ ਜ਼ਿਆਦਾ ਮਦਦ ਕਰਦਾ ਹੈ ਜਦ ਉਹ ਇਰਾਦਾ ਸਪਸ਼ਟ ਕਰਦੇ ਹਨ—ਨਾ ਕਿ ਜਦ ਉਹ ਸਧਾਰਣ ਤਰਕ ਨੂੰ ਇੱਕ ਪਹੇਲੀ ਬਣਾਉਣ।
ਆਧੁਨਿਕ ਸਾਫ਼ਟਵੇਅਰ ਬਹੁਤ ਵਾਰ ਇੱਕੱਲਾ thread 'ਤੇ ਨਹੀਂ ਚਲਦਾ। ਫੋਨ UI rendering, ਨੈੱਟਵਰਕ ਕਾਲ, ਅਤੇ background ਕੰਮ ਇਕੱਠੇ ਸੰਭਾਲਦੇ ਹਨ। ਸਰਵਰ ਹਜ਼ਾਰਾਂ ਬੇਨਤੀਆਂ ਇੱਕੱਠੇ ਸੰਭਾਲਦੇ ਹਨ। אפילו ਲੈਪਟਾਪ ਅਤੇ ਕਲਾਉਡ ਮਸ਼ੀਨਾਂ ਅਕਸਰ multiple CPU cores ਨਾਲ ਆਉਂਦੀਆਂ ਹਨ।
ਜਦ ਕਈ ਥਰੇਡ/ਟਾਸਕ ਇੱਕੋ ਡੇਟਾ ਨੂੰ ਬਦਲ ਸਕਦੇ ਹਨ, ਛੋਟੇ timing ਫਰਕ ਵੱਡੀਆਂ ਸਮੱਸਿਆਵਾਂ ਪੈਦਾ ਕਰਦੇ ਹਨ:
ਇਹ ਮੁੱਦੇ “ਬੁਰੇ ਡਿਵੈਲਪਰ” ਬਾਰੇ ਨਹੀਂ—ਇਹ shared mutable state ਦਾ ਕੁਦਰਤੀ ਨਤੀਜਾ ਹਨ। locks ਮਦਦ ਕਰਦੇ ਹਨ, ਪਰ ਉਹ complexity ਵਧਾਉਂਦੇ ਹਨ, deadlock ਦਾ ਖ਼ਤਰਾ ਲਿਆਉਂਦੇ ਹਨ, ਅਤੇ ਅਕਸਰ performance bottlenecks ਬਣ ਜਾਂਦੇ ਹਨ।
ਫੰਕਸ਼ਨਲ ਵਿਚਾਰ ਮੁੜ-ਉਭਰਦੇ ਰਹਿੰਦੇ ਹਨ ਕਿਉਂਕਿ ਉਹ parallel ਕੰਮ ਨੂੰ ਤਰਕ-ਯੋਗ ਬਣਾਉਂਦੇ ਹਨ।
ਜੇ ਡੇਟਾ ਇਮੀਊਟੇਬਲ ਹੈ, ਟਾਸਕ ਉਸਨੂੰ ਸੁਰੱਖਿਅਤ ਤਰੀਕੇ ਨਾਲ ਸਾਂਝਾ ਕਰ ਸਕਦੇ ਹਨ: ਕੋਈ ਵੀ ਉਸ ਨੂੰ ਬਦਲ ਨਹੀਂ ਸਕਦਾ। ਜੇ ਤੁਹਾਡੇ ਫੰਕਸ਼ਨ ਪਿਊਰ ਹਨ (ਇਕੋ ਇਨਪੁੱਟ → ਇਕੋ ਆਉਟਪੁੱਟ, کوئی ਛੁਪੇ side effects ਨਹੀਂ), ਤਾਂ ਤੁਸੀਂ ਉਹਨਾਂ ਨੂੰ parallel ਵਿੱਚ ਚਲਾ ਸਕਦੇ ਹੋ, ਨਤੀਜੇ cache ਕਰ ਸਕਦੇ ਹੋ, ਅਤੇ ਬਿਨਾਂ ਔਖੇ environment ਸੈਟਅਪ ਦੇ ਟੈਸਟ ਕਰ ਸਕਦੇ ਹੋ।
ਇਹ ਆਮ ਪੈਟਰਨਾਂ ਨਾਲ ਠੀਕ ਮਿਲਦਾ ਹੈ:
FP ਅਧਾਰਤ concurrency ਟੂਲ ਹਰ ਕੰਮ ਲਈ speedup ਦੀ ਗਾਰੰਟੀ ਨਹੀਂ ਦਿੰਦੇ। ਕੁਝ ਕੰਮ ਹੁੰਦੇ ਹਨ ਜੋ ਲੜੀਵਾਰ ਹੀ ਕਰਨੇ ਪੈਂਦੇ ਹਨ, ਅਤੇ ਵਾਧੂ ਕਾਪੀ ਜਾਂ ਕੋਆਰਡੀਨੇਸ਼ਨ overhead ਪੈ ਸਕਦਾ ਹੈ।
ਮੁੱਖ ਨਫ਼ਾ correctness ਹੈ: ਘੱਟ race conditions, ਪ੍ਰਭਾਵਾਂ ਦੇ ਆਸਪਾਸ ਸਪਸ਼ਟ ਹੱਦ, ਅਤੇ ਕੋਡ ਜੋ multi-core CPUs ਜਾਂ ਸਰਵਰ ਲੋਡ 'ਤੇ ਲਗਾਤਾਰ ਵਿਵਹਾਰ ਕਰਦਾ ਹੈ।
ਬਹੁਤ ਸਾਰਾ ਕੋਡ ਉਸ ਸਮੇਂ ਅਸਾਨ ਹੋ ਜਾਂਦਾ ਹੈ ਜਦ ਇਹ ਛੋਟੇ, ਨਾਮ-ਦිතੇ ਕਦਮਾਂ ਵਾਂਗ ਪੜ੍ਹਾਈਦਾ ਹੈ। ਇਹੀ ਕੰਪੋਜ਼ਿਸ਼ਨ ਅਤੇ ਪਾਈਪਲਾਈਨਾਂ ਦੀ ਮੂਲ ਧਾਰਣਾ ਹੈ: ਤੁਸੀਂ ਸਧਾਰਨ ਫੰਕਸ਼ਨਾਂ ਨੂੰ ਜੋੜਦੇ ਹੋ, ਤਾਂ ਕਿ ਡੇਟਾ ਉਨ੍ਹਾਂ ਕਦਮਾਂ ਵਿੱਚੋਂ “ਫਲੋ” ਹੋ ਕੇ ਆਵੇ।
ਪਾਈਪਲਾਈਨ ਨੂੰ ਇੱਕ ਅਸੈਂਬਲੀ ਲਾਈਨ ਵਾਂਗ ਸੋਚੋ:
ਹਰ ਕਦਮ ਨੂੰ ਅਲੱਗ-ਅਲੱਗ ਟੈਸਟ ਕੀਤਾ ਅਤੇ ਬਦਲਿਆ ਜਾ ਸਕਦਾ ਹੈ, ਅਤੇ ਪੂਰਾ ਪ੍ਰੋਗਰਾਮ ਇੱਕ ਪੜ੍ਹਨਯੋਗ ਕਹਾਣੀ ਬਣ ਜਾਂਦਾ ਹੈ: “ਇਹ ਲਓ, ਫਿਰ ਇਹ ਕਰੋ, ਫਿਰ ਇਹ ਕਰੋ।”
ਪਾਈਪਲਾਈਨਾਂ ਤੁਹਾਨੂੰ ਵਜ੍ਹਾ ਅਤੇ ਨਤੀਜੇ ਵਾਲੇ ਫੰਕਸ਼ਨਾਂ ਵੱਲ ਧੱਕਦੀਆਂ ਹਨ। ਇਸ ਨਾਲ ਆਮ ਤੌਰ 'ਤੇ:
ਕੰਪੋਜ਼ਿਸ਼ਨ ਬਸ ਇਹ ਵਿਚਾਰ ਹੈ ਕਿ “ਇੱਕ ਫੰਕਸ਼ਨ ਹੋਰ ਫੰਕਸ਼ਨਾਂ ਤੋਂ ਬਣਾਇਆ ਜਾ ਸਕਦਾ ਹੈ।” ਕੁਝ ਭਾਸ਼ਾਵ explicit helpers (ਜਿਵੇਂ compose) ਦਿੰਦੀਆਂ ਹਨ, ਹੋਰ chaining (.) ਜਾਂ operators 'ਤੇ ਨਿਰਭਰ ਕਰਦੀਆਂ ਹਨ।
ਇੱਥੇ ਇੱਕ ਛੋਟਾ, pipeline-ਸਟਾਈਲ ਉਦਾਹਰਨ ਹੈ ਜੋ orders ਨੂੰ ਲੈਂਦਾ, ਸਿਰਫ़ paid ਰੱਖਦਾ, totals ਜੋੜਦਾ, ਅਤੇ আਮਦਨ ਦਾ ਸਾਰਾਂਸ਼ ਬਣਾਉਂਦਾ:
const paid = o => o.status === 'paid';
const withTotal = o => ({ ...o, total: o.items.reduce((s, i) => s + i.price * i.qty, 0) });
const isLarge = o => o.total >= 100;
const revenue = orders
.filter(paid)
.map(withTotal)
.filter(isLarge)
.reduce((sum, o) => sum + o.total, 0);
ਭਾਵੇਂ ਤੁਸੀਂ JavaScript ਚੰਗੀ ਤਰ੍ਹਾਂ ਨਹੀਂ ਜਾਣਦੇ, ਫਿਰ ਵੀ ਇਹ ਆਮ ਤੌਰ 'ਤੇ ਪੜ੍ਹਿਆ ਜਾ ਸਕਦਾ ਹੈ: “paid orders → totals ਜੋੜੋ → ਵੱਡੇ ਰੱਖੋ → totals ਜੋੜੋ।” ਇਹ ਵੱਡੀ ਜਿੱਤ ਹੈ: ਕਦਮਾਂ ਦੀ ਬੰਨ੍ਹਤ ਹੀ ਕੋਡ ਨੂੰ ਸਮਝਾਉਂਦੀ ਹੈ।
ਬਹੁਤ ਸਾਰਾ “ਰਹੱਸਮਈ ਬੱਗ” ਕਿਸੇ ਚਤੁਰ algorithm ਬਾਰੇ ਨਹੀਂ ਹੋਂਦੇ—ਉਹ ਡੇਟਾ ਬਾਰੇ ਹੁੰਦੇ ਹਨ ਜੋ ਚੁਪਚਾਪ ਗਲਤ ਹੋ ਸਕਦਾ ਹੈ। ਫੰਕਸ਼ਨਲ ਵਿਚਾਰ ਤੁਹਾਨੂੰ ਐਸਾ ਡੇਟਾ ਮਾਡਲ ਕਰਨ ਲਈ ਧੱਕਦੇ ਹਨ ਜਿਸਨੂੰ ਗਲਤ ਬਣਾਉਣਾ ਮুশਕਿਲ (ਯਾ ਅਸੰਭਵ) ਹੋਵੇ, ਜਿਸ ਨਾਲ APIs ਸੁਰੱਖਿਅਤ ਅਤੇ ਵਿਵਹਾਰ ਅਨੁਮਾਨਯੋਗ ਬਣਦਾ ਹੈ।
ਧੀਲੇ-ਧਾਰੇ ਬਲਾਬ (strings, dictionaries, nullable fields) ਦੀ ਥਾਂ, ਫੰਕਸ਼ਨਲ-ਸਟਾਈਲ ਮਾਡਲਿੰਗ ਸੰਪੂਰਨ ਕਿਸਮਾਂ ਨੂੰ ਉਤਸ਼ਾਹਿਤ ਕਰਦੀ ਹੈ। ਉਦਾਹਰਣ ਲਈ, “EmailAddress” ਅਤੇ “UserId” ਨੂੰ ਵੱਖ-ਵੱਖ ਧਾਰਣਾ ਬਣਾਉਣਾ ਉਹਨਾਂ ਨੂੰ ਗਲਤ ਤਰੀਕੇ ਨਾਲ ਮਿਲਾਣ ਤੋਂ ਰੋਕਦਾ ਹੈ, ਅਤੇ ਵੈਰੀਫਿਕੇਸ਼ਨ ਬਾਊਂਡਰੀ 'ਤੇ (ਜਦ ਡੇਟਾ ਸਿਸਟਮ ਵਿੱਚ ਦਾਖਲ ਹੁੰਦਾ ਹੈ) ਹੋ ਸਕਦੀ ਹੈ।
ਇਸ ਦਾ ਤੁਰੰਤ ਪ੍ਰਭਾਵ APIs 'ਤੇ ਇਹ ਹੈ ਕਿ ਫੰਕਸ਼ਨ ਪਹਿਲਾਂ ਹੀ-ਵੈਰੀਫਾਇਡ ਮੁੱਲਾਂ ਨੂੰ ਸਵੀਕਾਰ ਕਰ ਸਕਦੇ ਹਨ, ਤਾਂ ਕਿ ਕਾਲਰ "ਚੈੱਕ ਭੁੱਲ" ਨਾ ਸਕੇ। ਇਹ defensive programming ਘਟਾਉਂਦਾ ਹੈ ਅਤੇ failure modes ਨੂੰ ਸਪਸ਼ਟ ਬਣਾਉਂਦਾ ਹੈ।
ਫੰਕਸ਼ਨਲ ਭਾਸ਼ਾਵਾਂ ਵਿੱਚ, algebraic data types (ADTs) ਤੁਹਾਨੂੰ ਇੱਕ ਮੁੱਲ ਨੂੰ ਕੁਝ ਵਿਸ਼ੇਸ਼ ਕੇਸਾਂ ਵਿੱਚ ਪਰਿਭਾਸ਼ਤ ਕਰਨ ਦਿੰਦੇ ਹਨ। ਸੋਚੋ: “ਇੱਕ payment ਜਾਂ Card, BankTransfer, ਜਾਂ Cash ਹੈ,” ਹਰ ਇੱਕ ਕੋਲ ਸਿਰਫ਼ ਓਹੇ ਫੀਲਡ ਹੁੰਦੇ ਹਨ ਜੋ ਲੋੜੀਦੇ ਹਨ। Pattern matching ਫਿਰ ਹਰ ਕੇਸ ਨੂੰ ਵੱਖ-ਵੱਖ ਸੰਭਾਲਣ ਦਾ ਢੰਗ ਦਿੰਦੀ ਹੈ।
ਇਸ ਨਾਲ ਮੂਲ ਸਿਧਾਂਤ ਬਣਦਾ ਹੈ: invalid states ਨੂੰ ਅਣਅਭਿਵਿਰਤੀ ਬਣਾਓ। ਜੇ “Guest users” ਨੂੰ ਕਦਾਚਿਤ password ਨਹੀਂ ਹੁੰਦੀ, ਤਾਂ ਉਸਨੂੰ password: string | null ਵਾਂਗ ਨਹੀਂ ਮਾਡਲ ਕਰੋ; “Guest” ਨੂੰ ਵੱਖ ਕੇਸ ਬਣਾਓ ਜਿਸ ਵਿੱਚ ਸਿੱਧਾ password ਫੀਲਡ ਹੀ ਨਹੀਂ ਹੋਵੇ। ਬਹੁਤ ਸਾਰੇ ਐਜ ਕੇਸ ਦਫ਼ਤਰੀ ਤੌਰ 'ਤੇ ਗੈਰ-ਹਾਜ਼ਰ ਹੋ ਜਾਂਦੇ ਹਨ ਕਿਉਂਕਿ ਅਸੰਭਵ ਨੂੰ ਪ੍ਰਗਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।
ਪੂਰੇ ADTs ਤੋਂ ਬਿਨਾਂ ਵੀ, ਆਧੁਨਿਕ ਭਾਸ਼ਾਵਾਂ ਕਈ ਸਮਾਨ ਟੂਲ ਦਿੰਦੀਆਂ ਹਨ:
ਜੇ pattern matching ਉਪਲਬਧ ਹੋਵੇ, ਤਾਂ ਇਹ ਫੀਚਰ ਹਰ ਕੇਸ ਨੂੰ ਜ਼ਰੂਰੀ ਤੌਰ 'ਤੇ ਹੰਢਾਉਣ ਵਿੱਚ ਮਦਦ ਕਰਦੇ ਹਨ—ਤਾਂ ਕਿ ਨਵੇਂ ਵਰਿਅੰਟ ਛੁਪੇ ਬੱਗ ਨਾ ਬਣ ਜਾਣ।
ਮੈਨਸਟਰੀਮ ਭਾਸ਼ਾਵਾਂ ਅਕਸਰ ਫੰਕਸ਼ਨਲ ਫੀਚਰ ਨੂੰ ਆਈਡੀਓਲੋਜੀ ਕਰਕੇ ਨਹੀਂ ਲਾਉਂਦੀਆਂ। ਉਹ ਇਹ ਫੀਚਰ ਇਸਲਈ ਲਾਉਂਦੀਆਂ ਹਨ ਕਿਉਂਕਿ ਵਿਕਾਸਕਾਰਾਂ ਜਿਹੇ ਹੀ ਤਕਨੀਕਾਂ ਤੇ ਲਗਾਤਾਰ ਨਿਰਭਰ ਹੋ ਰਹੇ ਹਨ—ਅਤੇ ecosystem ਵੱਲੋਂ ਉਹਨਾਂ ਨੂੰ ਇਨਾਮ ਮਿਲਦਾ ਹੈ।
ਟੀਮਾਂ ਐਸਾ ਕੋਡ ਚਾਹੁੰਦੀਆਂ ਹਨ ਜੋ ਪੜ੍ਹਨ, ਟੈਸਟ ਅਤੇ ਬਦਲਣ ਵਿੱਚ ਆਸਾਨ ਹੋਵੇ ਬਿਨਾਂ ਅਣਪਛਾਤੇ ripple effects ਦੇ। ਜਿਵੇਂ ਜ਼ਿਆਦਾਤਰ ਵਿਕਾਸਕਾਰ ਅਨੁਭਵ ਕਰਦੇ ਹਨ ਕਿ ਡੇਟਾ ਤਬਦੀਲੀਆਂ ਸਾਫ਼ ਹੋਣ ਅਤੇ ਛੁਪੇ ਨਿਰਭਰਤਾ ਘਟਣ ਨਾਲ ਫਾਇਦਾ ਹੁੰਦਾ ਹੈ, ਉਹ ਉਨ੍ਹਾਂ ਟੂਲਾਂ ਦੀ ਉਮੀਦ ਹਰ ਥਾਂ ਕਰਦੇ ਹਨ।
ਭਾਸ਼ਾ communities ਵੀ ਮੁਕਾਬਲਾ ਕਰਦੀਆਂ ਹਨ। ਜੇ ਇਕ ਐਕੋਸਿਸਟਮ ਸਧਾਰਨ ਕੰਮਾਂ ਨੂੰ ਸੁਗਮ ਬਣਾ ਦਿੰਦਾ ਹੈ—ਜਿਵੇਂ collection ਤਬਦੀਲੀਆਂ ਜਾਂ ਕੰਪੋਜ਼ਿਸ਼ਨ—ਤਾਂ ਹੋਰ ecosystem ਉਨ੍ਹਾਂ ਢੰਗਾਂ ਨੂੰ ਘੱਟ ਰਕੇ ਲਾਉਣ ਲਈ ਪ੍ਰੇਰਿਤ ਹੁੰਦੇ ਹਨ।
ਬਹੁਤ ਸਾਰਾ “ਫੰਕਸ਼ਨਲ-ਸਟਾਈਲ” ਲਾਇਬ੍ਰੇਰੀਆਂ ਦੁਆਰਾ ਚਲਾਇਆ ਜਾਂਦਾ ਹੈ ਨਾ ਕਿ ਕੇਵਲ ਪਾਠਕਰਮਾਂ ਦੁਆਰਾ:
ਜਦ ਇਹ ਲਾਇਬ੍ਰੇਰੀਆਂ ਲੋਕਪ੍ਰਿਯ ਹੋ ਜਾਂਦੀਆਂ ਹਨ, ਵਿਕਾਸਕਾਰ ਚਾਹੁੰਦੇ ਹਨ ਕਿ ਭਾਸ਼ਾ ਉਹਨਾਂ ਨੂੰ ਸਿੱਧਾ ਸਹਾਰਾ ਦੇਵੇ: concise lambdas, ਚੰਗੀ type inference, pattern matching, ਜਾਂ map, filter, reduce ਵਰਗੇ ਸਥੰਭ।
ਭਾਸ਼ਾ ਫੀਚਰ ਅਕਸਰ ਸਮਾਜਿਕ ਪ੍ਰਯੋਗਾਂ ਦੇ ਬਾਅਦ ਦਿਖਾਈ ਦਿੰਦੇ ਹਨ। ਜਦ ਕੋਈ ਨਿਰਧਾਰਿਤ ਪੈਟਰਨ ਆਮ ਹੋ ਜਾਂਦਾ—ਜਿਵੇਂ ਛੋਟੀਆਂ ਫੰਕਸ਼ਨਾਂ ਨੂੰ ਪਾਸ ਕਰਨਾ—ਤਾਂ ਭਾਸ਼ਾਵਾਂ ਉਸ ਪੈਟਰਨ ਨੂੰ ਘੱਟ ਸ਼ੋਰ ਵਾਲਾ ਬਣਾਉਂਦੀਆਂ ਹਨ।
ਇਸ ਕਰਕੇ ਤੁਸੀਂ ਆਮ ਤੌਰ 'ਤੇ ਥੋੜੇ-ਥੋੜੇ ਅਪਗਰੇਡ ਵੇਖਦੇ ਹੋ ਨਾ ਕਿ ਅਚਾਨਕ “ਸਾਰੇ FP” ਬਦਲਾਅ: ਪਹਿਲਾਂ lambdas, ਫਿਰ ਚੰਗੇ generics, ਫਿਰ ਬਿਹਤਰ immutability ਟੂਲ, ਫਿਰ ਸੁਧਾਰੇ ਕੰਪੋਜ਼ਿਸ਼ਨ ਯੂਟਿਲਿਟੀ।
ਜ਼ਿਆਦਾਤਰ ਭਾਸ਼ਾ ਡਿਜ਼ਾਈਨਰ ਅਸਲੀ ਜ਼ਿੰਦਗੀ ਦੇ ਕੋਡਬੇਸ ਹੋਰ-ਹੋਰ ਸ਼ੈਲੀਆਂ ਦੇ ਹਾਈਬ੍ਰਿਡ ਹੋਣ ਦੀ ਉਮੀਦ ਕਰਦੇ ਹਨ। ਮਕਸਦ ਹਰ ਚੀਜ਼ ਨੂੰ ਪਿਊਰ FP ਵਿੱਚ ਦਬੋ ਨਾ ਕਰਨਾ—ਬਲਕਿ ਟੀਮਾਂ ਨੂੰ ਉਹਨਾਂ ਧਾਰਣਾਵਾਂ ਨੂੰ ਵਰਤਣ ਦੀ ਆਜ਼ਾਦੀ ਦੇਣਾ ਹੈ ਜਿੱਥੇ ਉਹ ਮਦਦ ਕਰਦੀਆਂ ਹਨ:
ਉਹ ਮੱਧ ਰਸਤਾ ਹੀ ਇੱਕ ਵਜ੍ਹਾ ਹੈ ਕਿ FP ਫੀਚਰ ਮੁੜ-ਮੁੜ ਆ ਰਹੇ ਹਨ: ਉਹ ਆਮ ਸਮੱਸਿਆਵਾਂ ਨੂੰ ਹੱਲ ਕਰਦੇ ਹਨ ਬਿਨਾਂ ਲੋਕਾਂ ਨੂੰ ਆਪਣੇ ਸਾਰੇ ਤਰੀਕੇ ਬਦਲਣ ਲਈ ਮਜ਼ਬੂਰ ਕੀਤੇ।
ਫੰਕਸ਼ਨਲ ਧਾਰਣਾਵਾਂ ਸਭ ਤੋਂ ਜ਼ਿਆਦਾ ਲਾਹੇਮੰਦ ਹੁੰਦੀਆਂ ਹਨ ਜਦ ਉਹ ਗੁੰਝਲਦਾਰੀਆਂ ਘਟਾਉਂਦੀਆਂ ਹਨ, ਨਾ ਕਿ ਜਦ ਉਹ ਇਕ ਨਵਾਂ style ਮੁਕਾਬਲਾ ਬਣ ਜਾਂਦੀਆਂ। ਤੁਹਾਨੂੰ ਪੂਰੇ ਕੋਡਬੇਸ ਨੂੰ ਦੁਬਾਰਾ ਲਿਖਣ ਜਾਂ “ਸਾਰਾ ਕੁਝ ਪਿਊਰ” ਨਿਯਮ ਅਪਣਾਉਣ ਦੀ ਲੋੜ ਨਹੀਂ ਹੈ ਤਾਂ ਕਿ ਲਾਭ ਮਿਲ ਸਕਣ।
ਘੱਟ-ਖ਼ਤਰੇ ਵਾਲੀਆਂ ਥਾਵਾਂ ਤੋਂ ਸ਼ੁਰੂ ਕਰੋ ਜਿੱਥੇ ਫੰਕਸ਼ਨਲ ਆਦਤਾਂ ਫਾਇਦਾ ਸਿੱਧਾ ਦਿੰਦੀਆਂ ਹਨ:
ਜੇ ਤੁਸੀਂ ਤੇਜ਼ੀ ਨਾਲ AI-ਸਹਾਇਤਾ ਵਾਲੇ workflow ਨਾਲ ਬਣਾਉਂਦੇ ਹੋ, ਤਾਂ ਇਹ ਬਾਊਂਡਰੀ ਹੋਰ ਵੀ ਜ਼ਰੂਰੀ ਬਣ ਜਾਂਦੀਆਂ ਹਨ। ਉਦਾਹਰਣ ਲਈ, Koder.ai (ਇੱਕ vibe-coding ਫਲੈਟਫਾਰਮ ਜੋ React apps, Go/PostgreSQL backend, ਅਤੇ Flutter mobile apps ਨੂੰ chat ਰਾਹੀਂ ਜਨਰੇਟ ਕਰਦਾ ਹੈ) 'ਤੇ ਤੁਸੀਂ ਸਿਸਟਮ ਨੂੰ ਕਹਿ ਸਕਦੇ ਹੋ ਕਿ business logic ਨੂੰ pure functions/modules ਵਿੱਚ ਰੱਖੇ ਅਤੇ I/O ਨੂੰ patlined "edge" layers ਵਿੱਚ ਮਹਦੂਦ ਰੱਖੇ। snapshots ਅਤੇ rollback ਨਾਲ ਜੋੜ ਕੇ, ਤੁਸੀਂ ਰੀਫੈਕਟਰਾਂ 'ਤੇ ਇਸਤੇਮਾਲੀ ਤੌਰ 'ਤੇ ਦੇਖ-ਭਾਲ ਕਰ ਸਕਦੇ ਹੋ ਬਿਨਾਂ ਪੂਰੇ ਕੋਡਬੇਸ 'ਤੇ ਇਕ ਵੱਡੇ ਦਾਅ ਕਾਰਨਾ।
ਕੁਝ ਹਾਲਤਾਂ ਵਿੱਚ functional ਤਕਨੀਕਾਂ ਗਲਤ ਟੂਲ ਹੋ ਸਕਦੀਆਂ ਹਨ:
ਸੰਮੇਤ ਨਿਯਮਾਂ 'ਤੇ ਸਹਿਮਤ ਹੋਵੋ:.side effects ਕਿੱਥੇ ਮਨਜ਼ੂਰ ਹਨ, pure helpers ਨੂੰ ਕਿਵੇਂ ਨਾਮ ਦਿਓ, ਅਤੇ ਤੁਹਾਡੇ ਭਾਸ਼ਾ ਵਿੱਚ “ਕਿੰਨੀ ਇਮੀਊਟੇਬਲ ਕਾਫ਼ੀ” ਹੈ। ਕੋਡ ਰਿਵਿਊਜ਼ ਨੂੰ ਪੜ੍ਹਨਯੋਗਤਾ ਨੂੰ ਇਨਾਮ ਦਿਓ: ਸਧਾਰਣ pipelines ਅਤੇ ਵੇਖਣ-ਯੋਗ ਨਾਮ dense compositions ਦੀ ਥਾਂ।
ਸ਼ਿਪ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਪੁੱਛੋ:
ਇਸ ਤਰੀਕੇ ਨਾਲ ਵਰਤੀ ਜਾਣ 'ਤੇ, ਫੰਕਸ਼ਨਲ ਵਿਚਾਰ ਰਹੀਲ ਕੇ ਰਾਹ-ਨਿਰਦੇਸ਼ ਬਣ ਜਾਂਦੇ ਹਨ—ਤੁਹਾਨੂੰ ਕਲਮ ਤੋਂ ਬਿਨਾਂ ਜ਼ਿਆਦਾ ਸਹੀ ਅਤੇ ਰੱਖ-ਰਖਾਅਯੋਗ ਕੋਡ ਲਿਖਣ ਵਿੱਚ ਮਦਦ ਕਰਨਗੇ।
ਫੰਕਸ਼ਨਲ ਕਾਂਸੈਪਟ ਉਹ practical ਆਦਤਾਂ ਅਤੇ language features ਹਨ ਜੋ ਕੋਡ ਨੂੰ “ਇਨਪੁੱਟ → ਆਉਟਪੁੱਟ” ਤਬਦੀਲੀਆਂ ਵਾਂਗ ਵਿਵਹਾਰ ਕਰਨ ਤੇ ਧਿਆਨ ਦਿਣਦੀਆਂ ਹਨ।
ਸਧਾਰਨ ਭਾਸ਼ਾ ਵਿੱਚ, ਇਹ ਜ਼ੋਰ ਦਿੰਦੇ ਹਨ:
map, filter, ਅਤੇ reduce ਵਰਗੇ ਟੂਲਾਂ ਨਾਲ ਡੇਟਾ ਨੂੰ ਸਾਫ਼ ਤਰੀਕੇ ਨਾਲ ਬਦਲਣਾਨਹੀਂ। ਮਕਸਦ ਪ੍ਰੈਕਟਿਕਲ ਅਪਣਾਵਟ ਹੈ, ਨਾ ਕਿ ਕੋਈ ਆਈਡੀਓਲੋਜੀ।
ਮੁੱਖ ਭਾਸ਼ਾਵਾਂ ਕੁਝ ਫੀਚਰ (lambdas, streams/sequences, pattern matching, immutability helpers) ਲੈ ਰਹੀਆਂ ਹਨ ਤਾਂ ਕਿ ਜਤੋਂ ਜ਼ਰੂਰੀ ਹੋਵੇ functional ਅੰਦਾਜ਼ ਵਰਤ ਸਕੀਏ ਅਤੇ ਜਦੋਂ imperative ਜਾਂ OO ਸਪੱਸ਼ਟ ਹੋਵੇ ਉਹ ਵੀ ਵਰਤ ਸਕੀਏ।
ਕਿਉਂਕਿ ਇਹ ਅਚਾਨਕ ਤਰ੍ਹਾਂ ਦੇ ਸਰਪ੍ਰਾਈਜ਼ ਘਟਾਉਂਦੇ ਹਨ।
ਜਦ ਫੰਕਸ਼ਨ ਛੁਪੇ ਸਟੇਟ (ਗਲੋਬਲ, ਸਮਾਂ, ਮਿਊਟੇਬਲ ਆਬਜੈਕਟ) 'ਤੇ ਨਿਰਭਰ ਨਹੀਂ ਰਹਿੰਦੇ, ਤਦ ਇਹਨਾਂ ਦਾ ਵਿਵਹਾਰ ਮੁੜ-ਪੈਦਾ ਕਰਨ ਯੋਗ ਹੋ ਜਾਂਦਾ ਹੈ ਅਤੇ ਤਰਕ-ਵਿਵੇਚਨਾ ਆਸਾਨ ਹੁੰਦੀ ਹੈ। ਇਸ ਦਾ ਨਤੀਜਾ ਆਮ ਤੌਰ 'ਤੇ:
ਇੱਕ pure ਫੰਕਸ਼ਨ ਉਹ ਹੈ ਜੋ ਇਕੋ ਇਨਪੁੱਟ 'ਤੇ ਹਮੇਸ਼ਾ ਇਕੋ ਆਉਟਪੁੱਟ ਦਿੰਦਾ ਅਤੇ side effects ਤੋਂ ਬਚਦਾ ਹੈ।
ਇਸਦਾ ਫਾਇਦਾ ਇਹ ਹੈ ਕਿ ਤੁਸੀਂ ਇਸਨੂੰ ਆਸਾਨੀ ਨਾਲ ਟੈਸਟ ਕਰ ਸਕਦੇ ਹੋ: ਜਾਣੇ ਹੋਏ ਇਨਪੁੱਟ ਦੇ ਨਾਲ ਬੁਲਾਓ ਅਤੇ ਨਤੀਜੇ ਦੀ ਜਾਂਚ ਕਰੋ, ਬਿਨਾਂ ਡੇਟਾਬੇਸ, ਕੁਝ ਘੜੀਆਂ ਜਾਂ ਗਲੋਬਲ ਫਲੈਗ ਸੈਟ ਕਰਨ ਦੀ ਲੋੜ। Pure ਫੰਕਸ਼ਨ ਰੀਫੈਕਟਰ ਦੌਰਾਨ ਵੀ ਆਸਾਨੀ ਨਾਲ ਦੁਬਾਰਾ ਵਰਤੇ ਜਾ ਸਕਦੇ ਹਨ ਕਿਉਂਕਿ ਉਹ ਘੁਪਦੇ ਸੰਦਰਭ ਨਹੀਂ ਲੈਂਦੇ।
Side effect ਉਹ ਹੈ ਜੋ ਫੰਕਸ਼ਨ ਕੀਮਤ ਵਾਪਸ ਕਰਨ ਤੋਂ ਇਲਾਵਾ ਕੁਝ ਹੋਰ ਕਰਦਾ ਹੈ—ਫਾਈਲ ਪੜ੍ਹਨਾ/ਲਿਖਣਾ, API ਕਾਲ, ਲੌਗ, cache ਅਪਡੇਟ, ਗਲੋਬਲ ਛੇੜਛਾੜ, ਸਮਾਂ ਵਰਤਣਾ, ਰੈਂਡਮ ਨੰਬਰ ਬਣਾਉਣਾ ਆਦਿ।
ਪਰਭਾਵਾਂ (effects) ਨੂੰ ਮਿਲਾ ਕੇ ਲਾਜ਼ਮੀ ਹੋ ਜਾਂਦਾ ਹੈ ਕਿ ਵਿਵਹਾਰ ਮੁੜ-ਪੈਦਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ। ਇੱਕ ਅਸਰਕਾਰੀ ਤਰੀਕਾ ਇਹ ਹੈ:
ਇਮੀਊਟੇਬਿਲਿਟੀ ਦਾ ਮਤਲਬ ਹੈ ਕਿ ਤੁਸੀਂ ਕਿਸੇ ਵੀ ਕੀਮਤ ਨੂੰ ਜਗ੍ਹਾ 'ਤੇ ਬਦਲਦੇ ਨਹੀਂ—ਇੱਕ ਨਵੀਂ ਵਰਜਨ ਬਣਾਉ।
ਇਸ ਨਾਲ shared mutable state ਕਾਰਨ ਆਉਣ ਵਾਲੀਆਂ ਗਲਤੀਆਂ ਘਟਦੀਆਂ ਹਨ, ਖ਼ਾਸ ਕਰਕੇ ਜਦ ਜ਼ਿਆਦਾ ਥਾਵਾਂ ਤੇ ਡੇਟਾ ਸਾਂਝਾ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਇਹ undo/redo, caching ਅਤੇ time-travel debugging ਵਰਗੀਆਂ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਨੂੰ ਆਸਾਨ ਬਣਾ ਦਿੰਦਾ ਕਿਉਂਕਿ ਪੁਰਾਣੀਆਂ ਵਰਜਨ ਹਜੇ ਵੀ ਉਪਲੱਬਧ ਰਹਿੰਦੀਆਂ ਹਨ।
ਕਦੇ-ਕਦੇ ਹਾਂ—ਕਈ ਵਾਰੀ।
ਖ਼ਰਚ ਆਮ ਤੌਰ 'ਤੇ ਉਸ ਵੇਲੇ ਆਉਂਦਾ ਹੈ ਜਦ ਤੁਸੀਂ ਵੱਡੀਆਂ ਸਟ੍ਰਕਚਰਾਂ ਨੂੰ ਬਾਰ-ਬਾਰ ਕਾਪੀ ਕਰ ਰਹੇ ਹੋ। ਪ੍ਰਯੋਗਸ਼ੀਲ ਸਮਝੌਤੇ:
ਇਹ ਲੂਪ boilerplate ਨੂੰ ਪੜ੍ਹਨ ਯੋਗ, ਦੁਹਰਾਈਯੋਗ ਤਬਦੀਲੀਆਂ ਨਾਲ ਬਦਲ ਦਿੰਦੇ ਹਨ।
map: ਹਰ ਐਲਿਮੈਂਟ ਨੂੰ ਬਦਲੋfilter: ਜੋ ਨਿਯਮ ਮਿਲਦਾ ਹੈ, ਉਹ ਰੱਖੋreduce: ਲਿਸਟ ਨੂੰ ਇੱਕ ਕੀਮਤ ਵਿੱਚ ਮਿਲਾਓਚੰਗੀ ਤਰ੍ਹਾਂ ਵਰਤਣ 'ਤੇ ਇਹ pipeline ਇਰਾਦਾ ਸਾਫ਼ ਦਿਖਾਉਂਦੀ ਹੈ (ਜਿਵੇਂ “paid orders → amounts → sum”) ਅਤੇ ਕਾਪੀ-ਪੇਸਟ ਕੀਤੇ ਲੂਪਾਂ ਨੂੰ ਘਟਾਉਂਦੀ ਹੈ।
ਕੰਕਰਨਸੀ ਦਾ ਸਭ ਤੋਂ ਵੱਡਾ ਮੁੱਦਾ shared mutable state ਹੁੰਦਾ ਹੈ।
ਜੇ ਡੇਟਾ ਇਮੀਊਟੇਬਲ ਹੈ ਅਤੇ ਫੰਕਸ਼ਨ ਪਿਊਰ ਹਨ, ਤਾਂ ਟਾਸਕ ਉਹਨੂੰ ਸਾਂਝਾ ਰੂਪ ਵਿੱਚ ਸੁਰੱਖਿਅਤ ਤਰੀਕੇ ਨਾਲ ਚਲਾ ਸਕਦੇ ਹਨ—ਘੱਟ ਲਾਕਿੰਗ, ਘੱਟ race conditions। ਇਹ ਹਰ ਹਾਲਤ ਵਿੱਚ ਤੇਜ਼ੀ ਦੀ ਗਾਰੰਟੀ ਨਹੀਂ ਦਿੰਦਾ, ਪਰ correctness ਤੇ ਭਰੋਸਾ ਵਧਾਉਂਦਾ ਹੈ।
ਛੋਟੇ, ਘੱਟ-ਖ਼ਤਰੇ ਵਾਲੇ ਤਬਦੀਲੀਆਂ ਨਾਲ ਸ਼ੁਰੂ ਕਰੋ:
ਜੇ ਕੋਡ ਬਹੁਤ clever ਹੋ ਜਾਵੇ ਤਾਂ ਰੋਕੋ—ਇੰਟਰਮੀਡੀਏਟ ਕਦਮਾਂ ਨੂੰ ਨਾਮ ਦਿਓ, ਹੈਲਪਰ ਕੱਢੋ, ਅਤੇ ਪੜ੍ਹਨਯੋਗਤਾ ਨੂੰ ਤਰਜੀਹ ਦਿਓ।