技术控

    今日:61| 主题:49471
收藏本版 (1)
最新软件应用技术尽在掌握

[其他] Pure versus impure functions

[复制链接]
国产姑娘不矫情 发表于 2016-10-12 17:00:41
121 0

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
Understanding pure and impure functions is a simple transition into cleaner, more role-based and testable code. In this post we’ll explore pure and impure functions by looking at a simple Body Mass Index (BMI) calculator that estimates your “healthy weight” by some simple input factors of height and weight. BMI isn’t considered the most reliable tool for checking your weight, but that’s not the point of this article ;)
  Note: if you’re not familiar with    kgand    cmunits, use something like    70for    kgand    180for    cmto try it out.  
  Table of contents

  
       
  • HTML and submit event   
  • Impure implementation  
  Terminology

  Before we begin, let’s clarify what “impure” and “pure” functions really mean in programming terms.
  Impure functions

  An impure function is a function that mutates variables/state/data outside of it’s lexical scope, thus deeming it “impure” for this reason. There are many ways to write JavaScript, and thinking in terms of impure/pure functions we can write code that is much easier to reason with.
  Pure functions

  A pure function is much easier to comprehend, especially as our codebase may scale, as well as role-based functions that do one job and do it well. Pure functions don’t modify external variables/state/data outside of the scope, and returns the same output given the same input. Therefore it is deemed “pure”.
  Let’s refactor our BMI calculator that I’ve created in a fully impure fashion, into multiple functions that make use of pure functions.
  HTML and submit event

  Here’s the markup I’ve created to use for capturing the user’s input data:
     
  1. <form name="bmi">
  2.   <h1>BMI Calculator</h1>
  3.   <label>
  4.     <input type="text" name="weight" placeholder="Weight (kg)">
  5.   </label>
  6.   <label>
  7.     <input type="text" name="height" placeholder="Height (cm)">
  8.   </label>
  9.   <button type="submit">
  10.     Calculate BMI
  11.   </button>
  12.   <div class="calculation">
  13.     <div>
  14.       BMI calculation: <span class="result"></span>
  15.     </div>
  16.     <div>
  17.       This means you are: <span class="health"></span>
  18.     </div>
  19.   </div>
  20. </form>
复制代码
   And as a base, we’ll attach an event listener as a base and    preventDefaulton the    submitevent:  
     
  1. (() => {
  2.   const form = document.querySelector('form[name=bmi]');
  3.   const onSubmit = event => {
  4.     event.preventDefault();
  5.   };
  6.   form.addEventListener('submit', onSubmit, false);
  7. })();
复制代码
   The live output (which doesn’t work yet) here:
  Impure implementation

  We’ll cut out the IIFE and event handler fluff for now and focus on the    onSubmitfunction:  
     
  1. const onSubmit = event => {
  2.   event.preventDefault();
  3.   let healthMessage;
  4.   const result = form.querySelector('.result');
  5.   const health = form.querySelector('.health');
  6.   const weight = parseInt(form.querySelector('input[name=weight]').value, 10);
  7.   const height = parseInt(form.querySelector('input[name=height]').value, 10);
  8.   const bmi = (weight / (height /100 * height / 100)).toFixed(1);
  9.   if (bmi < 18.5) {
  10.     healthMessage = 'considered underweight';
  11.   } else if (bmi > 18.5 && bmi < 25) {
  12.     healthMessage = 'a healthy weight';
  13.   } else if (bmi > 25) {
  14.     healthMessage = 'considered overweight';
  15.   }
  16.   result.innerHTML = bmi;
  17.   health.innerHTML = healthMessage;
  18. };
复制代码
   That’s all our function contains, and once you enter your height/weight it’ll update the DOM with those results. Now, this is what I would personally consider a bit of a mess, and extremely difficult to debug and understand the role of the function. Let’s explain what’s happening here with some code comments:
     
  1. const onSubmit = event => {
  2.   // prevent the form actually submitting
  3.   event.preventDefault();
  4.   // create a variable to wait and hold for our "health message"
  5.   // which will be mutated and bound a new String with the correct message later
  6.   let healthMessage;
  7.   // grabbing both the result and health <span> tags to inject the results into
  8.   const result = form.querySelector('.result');
  9.   const health = form.querySelector('.health');
  10.   // parsing to Integers with base 10, based on the weight and height <input> values
  11.   const weight = parseInt(form.querySelector('input[name=weight]').value, 10);
  12.   const height = parseInt(form.querySelector('input[name=height]').value, 10);
  13.   // run the formula to obtain the BMI result
  14.   // finally, use toFixed(1) for 1 decimal place
  15.   const bmi = (weight / (height /100 * height / 100)).toFixed(1);
  16.   // run the logic to see "how healthy" the person's weight is considered
  17.   // this overrides the "healthMessage" variable based on the expression that passes
  18.   if (bmi < 18.5) {
  19.     healthMessage = 'considered underweight';
  20.   } else if (bmi > 18.5 && bmi < 25) {
  21.     healthMessage = 'a healthy weight';
  22.   } else if (bmi > 25) {
  23.     healthMessage = 'considered overweight';
  24.   }
  25.   // bind results to DOM
  26.   result.innerHTML = bmi;
  27.   health.innerHTML = healthMessage;
  28. };
复制代码
   Upon first look, this is absolutely fine in terms of the fact that “it works”. However if we began to scale this, we would end up with a monstrosity codebase with a bible of procedural code that is very easily broken.
  We can do better, but here’s the live demo for this implementation:
  Pure implementation

  Before we can start using pure functions, we need to decide what functions will be pure. In the above and 100% impure implementation we did    way too manythings in a single function:  
  
       
  • Read values from the DOM   
  • Parsed values to numbers   
  • Calculated the BMI from the parsed values   
  • Conditionally checked the BMI result and assigned the correct message to an undefined variable      healthMessage   
  • Wrote values to the DOM  
  To “go pure”, we’ll implement functions that handle these actions:
  
       
  • Parse values to numbers and calculate the BMI   
  • Return us the correct message for binding to the DOM  
  Going pure

  Let’s start with the input value parsing and calculating the BMI, specifically addressing this section of code:
     
  1. const weight = parseInt(form.querySelector('input[name=weight]').value, 10);
  2. const height = parseInt(form.querySelector('input[name=height]').value, 10);
  3. const bmi = (weight / (height /100 * height / 100)).toFixed(1);
复制代码
   This deals with    parseInt()and the formula to calculate the BMI. This is not very flexible and likely very error prone when at some point in an application we’d come to refactoring or adding more features.  
  To refactor, we’re only going obtain each input’s value property alone, and delegate those into a    getBMIfunction:  
     
  1. const weight = form.querySelector('input[name=weight]').value;
  2. const height = form.querySelector('input[name=height]').value;
  3. const bmi = getBMI(weight, height);
复制代码
   This    getBMIfunction would be 100% pure in the fact that it accepts arguments and returns a new piece of data based on those arguments. Given the same input, you’ll get the same output.  
  Here’s how I’d implement the    getBMIfunction:  
     
  1. const getBMI = (weight, height) => {
  2.   let newWeight = parseInt(weight, 10);
  3.   let newHeight = parseInt(height, 10);
  4.   return (newWeight / (newHeight /100 * newHeight / 100)).toFixed(1);
  5. };
复制代码
   This function takes the    weightand    heightas arguments, converts them to Numbers through    parseIntand then performs the calculation for the BMI. Whether we pass a String or Number as each argument, we can safety check and    parseIntregardless here.  
  Onto the next function. Instead of    ifand    else iflogic to assign the    healthMessage, we’ll create the expected result to look like this:  
     
  1. health.innerHTML = getHealthMessage(bmi);
复制代码
   Again, this is much easier to reason with. The implementation of    getHealthMessagewould look like this:  
     
  1. const getHealthMessage = unit => {
  2.   let healthMessage;
  3.   if (unit < 18.5) {
  4.     healthMessage = 'considered underweight';
  5.   } else if (unit > 18.5 && unit < 25) {
  6.     healthMessage = 'a healthy weight';
  7.   } else if (unit > 25) {
  8.     healthMessage = 'considered overweight';
  9.   }
  10.   return healthMessage;
  11. };
复制代码
   Putting everything together, we have this:
     
  1. (() => {
  2.   const form = document.querySelector('form[name=bmi]');
  3.   const getHealthMessage = unit => {
  4.     let healthMessage;
  5.     if (unit < 18.5) {
  6.       healthMessage = 'considered underweight';
  7.     } else if (unit > 18.5 && unit < 25) {
  8.       healthMessage = 'a healthy weight';
  9.     } else if (unit > 25) {
  10.       healthMessage = 'considered overweight';
  11.     }
  12.     return healthMessage;
  13.   };
  14.   const getBMI = (weight, height) => {
  15.     let newWeight = parseInt(weight, 10);
  16.     let newHeight = parseInt(height, 10);
  17.     return (newWeight / (newHeight /100 * newHeight / 100)).toFixed(1);
  18.   };
  19.   const onSubmit = event => {
  20.     event.preventDefault();
  21.     const result = form.querySelector('.result');
  22.     const health = form.querySelector('.health');
  23.     const weight = form.querySelector('input[name=weight]').value;
  24.     const height = form.querySelector('input[name=height]').value;
  25.     const bmi = getBMI(weight, height);
  26.     result.innerHTML = bmi;
  27.     health.innerHTML = getHealthMessage(bmi);
  28.   };
  29.   form.addEventListener('submit', onSubmit, false);
  30. })();
复制代码
   You can see how much clearer this becomes. It also means we can test the    getBMIand    getHealthMessagefunctions on their own, without any external variables being needed. This means our “impure”    onSubmitfunction becomes much clearer and easier to extend, refactor without breaking any isolated pieces of logic that may have before relied on variables in the lexical scope(s).  
  Final solution

  The final output with a mix of impure and pure functions:
友荐云推荐




上一篇:Uncovering Secrets in the Offshore Leaks Database with Tom Sawyer Perspectives
下一篇:ResizeObserver – It’s like document.onresize for elements
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表