# CH 10 First-class function: part 1 ## First class function ### New skill • Learn how to make first-class functions and values. • Learn to wrap syntax using higher-order functions. - Build complex calculations using chains of operations. - Discover a way to operate on deeply nested data. - Fontrol the ordering and repetition of actions to eliminate timing bugs. • Apply two refactorings that use first-class and higher-order functions. <!-- - 学习如何制作一流函数版本的语法。 - 学习使用高阶函数包装语法。 - 应用两个使用一流函数和高阶函数的重构方法。 --> > These skills will be available once we learn about first-class values. <!-- 我们就可以学习一种功能性的迭代方式。我们可以利用运算链建立复杂的计算。我们可以发现一种对深度嵌套数据进行操作的方法。我们还可以学习控制动作的顺序和重复,以消除时间上的错误。我们将用两个架构来结束这个游戏,这两个架构可以让我们构造我们的服务。一旦我们学习了第一类值,这些技能就可以使用了。 --> ### First-class value First-class values are anything that can be stored in a variable, passed as an argument, and returned from functions. A first-class value can be manipulated by code. <!-- 一级值是任何可以存储在变量中、作为参数传递、并从函数中返回的东西。一级值可以被代码所操纵。 --> - Many parts of a language are not first-class. We can wrap those parts in functions that do the same thing to make them first-class. - Some languages have first-class functions that let you treat functions as first-class values. First-class functions are necessary for doing this level of functional programming. - Higher-order functions are functions that take other functions as arguments (or that return a function). <!-- - 一种语言的许多部分都不是一流的。我们可以将这些部分包裹在做同样事情的函数中,使它们成为一流的。 - 有些语言有一流的函数,让你把函数当作一流的值。一级函数对于进行这种层次的函数式编程是必要的。 - 高阶函数是以其他函数为参数的函数(或返回一个函数)。高阶函数让我们对不同的行为进行抽象。 --> <!-- 在这一章中,我们将学习一种代码气味和两种重构方法,它们可以帮助我们消除重复,找到更好的抽象。我们将在本章和本书的整个第二部分中应用这些新技能。 --> <!-- - 函数名中的隐式参数是一种代码气味,其中函数之间的差异在函数名中被命名。我们可以应用表达式隐式参数来使参数成为第一类,而不是函数名中不可访问的部分。 - 我们可以应用称为用回调替换主体的重构来抽象出行为。它创建了一个一流的函数参数,代表两个函数之间的行为差异。 --> <!-- 代码气味:函数名称中的隐性参数 --> - Code smell: Implicit argument in function name - Implicit argument in function name is a code smell where the difference between functions is named in the function name. We can apply express implicit argument to make the argument first-class instead of an inaccessible part of the function name. <!-- 重构。表达隐性参数 --> - Refactoring: Express implicit argument <!-- 重构。用回调取代主体 --> - Refactoring: Replace body with callback - Apply the refactoring called replace body with callback to abstract over behavior. It creates a first-class function argument that represents the behavioral difference between two functions. ## Code smell: Implicit argument in function name There are two characteristics to the implicit argument in function name smell: 1. Very similar function implementations 2. Name of function indicates the difference in implementation The function name difference is an implicit argument <!-- 隐含参数在函数名称中的气味有两个特点。 1. 非常相似的函数实现 2. 函数的名称表明了实现的差异 函数名称差异是一个隐性参数 --> ## Refactoring: Express implicit argument ### Steps: 1. Identify the implicit argument in the name of the function. 2. Add explicit argument. 3. Use new argument in body in place of hard-coded value. 4. Update the calling code. <!-- 这就是express这个词的意思。使隐式参数显性化。以下是具体步骤。 --> <!-- 1. 确定函数名称中的隐性参数。 2. 添加显性参数。 3. 在主体中使用新的参数来代替硬编码的值。 4. 更新调用代码。 让我们看看我们如何重构setPriceByName(),它只能设置价格,变成setFieldByName(),它可以设置项目的任何字段。 --> <!-- 这个重构应用于这段代码,用一个通用的函数取代了四个现有的函数,谁知道因为有了通用的setFieldByName(),有多少个函数不用写了。 我们所做的是使字段名成为一个一流的值。以前,字段名除了作为函数名的一部分隐含地暴露给API客户外,从未暴露给客户。现在,它是一个值(在本例中是一个字符串),可以作为一个参数传递,也可以存储在一个变量或数组中。这就是我们所说的第一类。我们可以使用整个语言来处理它。而使事物成为第一类是本章的主题。 你可能认为像这样使用字符串是不安全的。我们将在几页中讨论这个问题。现在,请随它去吧! --> ![](https://i.imgur.com/ZIS8mmo.png) ![](https://i.imgur.com/IlbhOXG.png) ```ruby # Usage example employee = { id: 1, name: "John Doe", salary: 5000 } department = { id: 2, name: "IT", multiplier: 1.2 } # Before refactoring def calculate_salary_for_employee_in_department(employee_id, department_id) # Calculate salary based on employee and department information # ... end calculate_salary_for_employee_in_department(employee[:id], department[:id]) # After refactoring def calculate_salary(employee, department) # Calculate salary based on employee and department information # ... end # After refactoring calculate_salary(employee, department) ``` ### Examples of things you can do with a first-class value 1. Assign it to a variable. 2. Pass it as an argument to a function. 3. Return it from a function. 4. Store it in an array or object. ### Will field names as strings lead to more bugs? ```javascript var validItemFields = ['price', 'quantity', 'shipping', 'tax']; function setFieldByName(cart, name, field, value { if(!validItemFields.includes(field)) throw `Not a valid item field: '${field}'.`; var item = cart[name]; var newItem = objectSet(item, field, value); var newCart = objectSet(cart, name, newItem); return newCart; } ``` ### Will first-class fields make the API hard to change? ```javascript var validItemFields = ['price', 'quantity', 'shipping', 'tax', 'number'] var translations = { 'quantity': 'number' }; function setFieldByName(cart, name, field, value) { if(!validItemFields.includes(field)) throw "Not a valid item field: '" + field + "'."; if(translations.hasOwnProperty(field)) field = translations[field]; var item = cart[name]; var newItem = objectSet(item, field, value); var newCart = objectSet(cart, name, newItem); return newCart; } ``` <!-- before Replace body with callback --> ### For loop example: Eating and cleaning up 1. Wrap code in functions. 2. Rename to be more generic. 3. Express implicit argument. 4. Extract function. 5. Express implicit argument. ![](https://i.imgur.com/8gW3oIq.png) ![](https://i.imgur.com/7prkjd4.png) ## Refactoring: Replace body with callback 1. Identify the before, body, and after sections. 2. Extract the whole thing into a function. 3. Extract the body section into a function passed as an argument to that function. ![](https://i.imgur.com/09ukdZo.png) ![](https://i.imgur.com/IqhWYCd.png) ![](https://i.imgur.com/lRG6CCU.png) ```ruby # Usage example employee = { id: 1, name: "John Doe", salary: 5000 } department = { id: 2, name: "IT", multiplier: 1.2 } # Before refactoring def calculate_salary_for_employee_in_department(employee_id, department_id) # Calculate salary based on employee and department information employee = get_employee(employee_id) department = get_department(department_id) salary = employee[:salary] * department[:multiplier] salary end calculate_salary_for_employee_in_department(employee[:id], department[:id]) # After refactoring def calculate_salary(employee, department, salary_calculator) # Before # ... # Body salary_calculator.call(employee, department) # After # ... end # Proc salary_calculator = Proc.new { |employee, department| employee[:salary] * department[:multiplier] } # lambda salary_calculator = ->(employee, department) { employee[:salary] * department[:multiplier] } calculate_salary(employee, department, salary_calculator) # Another refactoring def salary_calculator(employee, department) employee[:salary] * department[:multiplier] end calculate_salary(employee, department, method(:salary_calculator)) ``` ## Conclusion This chapter introduced us to the ideas - First-class values - First-class functions - Higher-order functions > We are going to be exploring the potential of these ideas in the next chapters. After the distinction between actions, calculations, and data, the idea of higher-order functions opens up a new level of functional programming power. The second part of this book is all about that power.