การเขียนโปรแกรมเชิงฟังก์ชันสำหรับนักพัฒนา Java ตอนที่ 1

Java 8 แนะนำนักพัฒนา Java ในการเขียนโปรแกรมเชิงฟังก์ชันด้วยนิพจน์แลมด้า Java รุ่นนี้แจ้งให้นักพัฒนาทราบอย่างมีประสิทธิภาพว่าไม่เพียงพอที่จะคิดถึงการเขียนโปรแกรม Java จากมุมมองเชิงวัตถุที่จำเป็นอีกต่อไป นักพัฒนา Java ต้องสามารถคิดและเขียนโค้ดโดยใช้กระบวนทัศน์เชิงฟังก์ชันที่เปิดเผยได้

บทช่วยสอนนี้นำเสนอพื้นฐานของการเขียนโปรแกรมเชิงฟังก์ชัน ฉันจะเริ่มต้นด้วยคำศัพท์จากนั้นเราจะเจาะลึกถึงแนวคิดการเขียนโปรแกรมเชิงฟังก์ชัน ฉันจะสรุปโดยแนะนำคุณเกี่ยวกับเทคนิคการเขียนโปรแกรมเชิงฟังก์ชันห้าประการ ตัวอย่างโค้ดในส่วนเหล่านี้จะช่วยให้คุณเริ่มต้นด้วยฟังก์ชันที่แท้จริงฟังก์ชันลำดับที่สูงขึ้นการประเมินแบบขี้เกียจการปิดและการแกง

การเขียนโปรแกรมเชิงฟังก์ชันกำลังเพิ่มขึ้น

Institute of Electrical and Electronics Engineers (IEEE) ได้รวมภาษาการเขียนโปรแกรมเชิงฟังก์ชันไว้ในภาษาโปรแกรม 25 อันดับแรกสำหรับปี 2018 และปัจจุบัน Google Trends จัดอันดับการเขียนโปรแกรมเชิงฟังก์ชันได้รับความนิยมมากกว่าการเขียนโปรแกรมเชิงวัตถุ

เห็นได้ชัดว่าการเขียนโปรแกรมเชิงฟังก์ชันไม่สามารถละเลยได้ แต่เหตุใดจึงเป็นที่นิยมมากขึ้น? เหนือสิ่งอื่นใดการเขียนโปรแกรมเชิงฟังก์ชันช่วยให้ตรวจสอบความถูกต้องของโปรแกรมได้ง่ายขึ้น นอกจากนี้ยังลดความยุ่งยากในการสร้างโปรแกรมพร้อมกัน การทำงานพร้อมกัน (หรือการประมวลผลแบบขนาน) มีความสำคัญต่อการปรับปรุงประสิทธิภาพของแอปพลิเคชัน

ดาวน์โหลดรับโค้ดดาวน์โหลดซอร์สโค้ดสำหรับแอปพลิเคชันตัวอย่างในบทช่วยสอนนี้ สร้างโดย Jeff Friesen สำหรับ JavaWorld

การเขียนโปรแกรมเชิงฟังก์ชันคืออะไร?

โดยทั่วไปคอมพิวเตอร์จะใช้สถาปัตยกรรม Von Neumann ซึ่งเป็นสถาปัตยกรรมคอมพิวเตอร์ที่ใช้กันอย่างแพร่หลายตามคำอธิบายของนักคณิตศาสตร์และนักฟิสิกส์ John von Neumann (และคนอื่น ๆ ) ในปี 1945 สถาปัตยกรรมนี้มีความเอนเอียงไปสู่การเขียนโปรแกรมที่จำเป็นซึ่งเป็นกระบวนทัศน์การเขียนโปรแกรมที่ใช้คำสั่งเพื่อเปลี่ยนสถานะของโปรแกรม C, C ++ และ Java เป็นภาษาโปรแกรมที่จำเป็นทั้งหมด

ในปีพ. ศ. 2520 จอห์นแบ็คคุส (John Backus) นักวิทยาศาสตร์คอมพิวเตอร์ผู้มีชื่อเสียงโด่งดังจากผลงานเรื่อง FORTRAN ได้บรรยายหัวข้อ "การเขียนโปรแกรมจะหลุดพ้นจากสไตล์ฟอนนอยมันน์ได้หรือไม่ Backus ยืนยันว่าสถาปัตยกรรม Von Neumann และภาษาจำเป็นที่เกี่ยวข้องมีข้อบกพร่องโดยพื้นฐานและนำเสนอภาษาการเขียนโปรแกรมระดับฟังก์ชัน (FP) เป็นโซลูชัน

การชี้แจง Backus

เนื่องจากมีการนำเสนอการบรรยายของ Backus เมื่อหลายสิบปีก่อนแนวคิดบางอย่างอาจเข้าใจยาก Blogger Tomasz Jaskułaเพิ่มความชัดเจนและเชิงอรรถในบล็อกโพสต์ของเขาตั้งแต่เดือนมกราคม 2018

แนวคิดและคำศัพท์เกี่ยวกับการเขียนโปรแกรมเชิงฟังก์ชัน

โปรแกรมการทำงานเป็นรูปแบบการเขียนโปรแกรมในการคำนวณซึ่งมีการประมวลผลเป็นฟังก์ชั่นโปรแกรมการทำงาน สิ่งเหล่านี้คือโครงสร้างที่เหมือนฟังก์ชันทางคณิตศาสตร์ (เช่นฟังก์ชันแลมบ์ดา) ที่ประเมินในบริบทของนิพจน์

ภาษาการเขียนโปรแกรมเชิงฟังก์ชันเป็นภาษาที่เปิดเผยซึ่งหมายความว่าตรรกะของการคำนวณจะแสดงโดยไม่ได้อธิบายขั้นตอนการควบคุม ในการเขียนโปรแกรมเชิงประกาศไม่มีคำสั่งใด ๆ โปรแกรมเมอร์จะใช้นิพจน์เพื่อบอกคอมพิวเตอร์ว่าต้องทำอะไร แต่ไม่ใช่วิธีการทำงานให้สำเร็จ หากคุณคุ้นเคยกับ SQL หรือนิพจน์ทั่วไปคุณมีประสบการณ์เกี่ยวกับรูปแบบการประกาศ ทั้งสองใช้นิพจน์เพื่ออธิบายสิ่งที่ต้องทำแทนที่จะใช้คำสั่งเพื่ออธิบายวิธีการทำ

การคำนวณในการเขียนโปรแกรมเชิงฟังก์ชันอธิบายโดยฟังก์ชันที่ประเมินในบริบทนิพจน์ ฟังก์ชันเหล่านี้ไม่เหมือนกับฟังก์ชันที่ใช้ในการเขียนโปรแกรมที่จำเป็นเช่นวิธี Java ที่ส่งคืนค่า แทนการเขียนโปรแกรมการทำงานฟังก์ชั่นเป็นเหมือนฟังก์ชั่นทางคณิตศาสตร์ซึ่งเป็นผู้ผลิตส่งออกที่มักจะขึ้นอยู่กับข้อโต้แย้งของ ทุกครั้งที่เรียกใช้ฟังก์ชันการเขียนโปรแกรมเชิงฟังก์ชันด้วยอาร์กิวเมนต์เดียวกันจะได้ผลลัพธ์เดียวกัน ฟังก์ชั่นในการเขียนโปรแกรมการทำงานจะกล่าวแสดงความโปร่งใสอ้างอิงซึ่งหมายความว่าคุณสามารถแทนที่การเรียกฟังก์ชันด้วยค่าผลลัพธ์โดยไม่ต้องเปลี่ยนความหมายของการคำนวณ

การเขียนโปรแกรมเชิงฟังก์ชันสนับสนุนความไม่เปลี่ยนรูปซึ่งหมายความว่าสถานะไม่สามารถเปลี่ยนแปลงได้ โดยทั่วไปจะไม่เป็นเช่นนั้นในการเขียนโปรแกรมที่จำเป็นซึ่งฟังก์ชันที่จำเป็นอาจเชื่อมโยงกับสถานะ (เช่นตัวแปรอินสแตนซ์ Java) การเรียกใช้ฟังก์ชันนี้ในเวลาที่ต่างกันโดยใช้อาร์กิวเมนต์เดียวกันอาจส่งผลให้ค่าส่งกลับต่างกันเนื่องจากในกรณีนี้สถานะไม่แน่นอนหมายความว่าจะเปลี่ยนไป

ผลข้างเคียงในการเขียนโปรแกรมที่จำเป็นและการทำงาน

การเปลี่ยนแปลงสถานะเป็นผลข้างเคียงของการเขียนโปรแกรมที่จำเป็นซึ่งป้องกันความโปร่งใสในการอ้างอิง มีผลข้างเคียงอื่น ๆ อีกมากมายที่ควรทราบโดยเฉพาะอย่างยิ่งเมื่อคุณประเมินว่าจะใช้รูปแบบที่จำเป็นหรือใช้งานได้ในโปรแกรมของคุณ

ผลข้างเคียงที่พบบ่อยอย่างหนึ่งในการเขียนโปรแกรมที่จำเป็นคือเมื่อคำสั่งมอบหมายกลายพันธุ์ตัวแปรโดยเปลี่ยนค่าที่เก็บไว้ ฟังก์ชันในการเขียนโปรแกรมเชิงฟังก์ชันไม่รองรับการกำหนดตัวแปร เนื่องจากค่าเริ่มต้นของตัวแปรไม่เคยเปลี่ยนแปลงการเขียนโปรแกรมเชิงฟังก์ชันจึงกำจัดผลข้างเคียงนี้

ผลข้างเคียงที่พบบ่อยอีกประการหนึ่งเกิดขึ้นเมื่อปรับเปลี่ยนพฤติกรรมของฟังก์ชันที่จำเป็นโดยอาศัยข้อยกเว้นที่ถูกโยนทิ้งซึ่งเป็นปฏิสัมพันธ์ที่สังเกตได้กับผู้โทร สำหรับข้อมูลเพิ่มเติมโปรดดูที่การอภิปราย Stack Overflow "เหตุใดการเพิ่มข้อยกเว้นจึงเป็นผลข้างเคียง"

ผลข้างเคียงทั่วไปที่สามเกิดขึ้นเมื่อการดำเนินการ I / O ป้อนข้อความที่ไม่สามารถยังไม่ได้อ่านหรือแสดงข้อความที่ไม่สามารถยกเลิกการเขียนได้ ดูการอภิปรายของ Stack Exchange "IO จะทำให้เกิดผลข้างเคียงในการเขียนโปรแกรมเชิงฟังก์ชันได้อย่างไร" เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับผลข้างเคียงนี้

การขจัดผลข้างเคียงทำให้เข้าใจและทำนายพฤติกรรมการคำนวณได้ง่ายขึ้นมาก นอกจากนี้ยังช่วยให้โค้ดเหมาะสมกับการประมวลผลแบบขนานมากขึ้นซึ่งมักจะช่วยเพิ่มประสิทธิภาพของแอปพลิเคชัน แม้ว่าจะมีผลข้างเคียงในการเขียนโปรแกรมเชิงฟังก์ชัน แต่โดยทั่วไปแล้วจะมีน้อยกว่าในการเขียนโปรแกรมที่จำเป็น การใช้การเขียนโปรแกรมเชิงฟังก์ชันสามารถช่วยให้คุณเขียนโค้ดที่เข้าใจดูแลรักษาและทดสอบได้ง่ายขึ้นและยังสามารถใช้ซ้ำได้อีกด้วย

ต้นกำเนิด (และผู้ริเริ่ม) ของการเขียนโปรแกรมเชิงฟังก์ชัน

การเขียนโปรแกรมเชิงฟังก์ชันมีต้นกำเนิดจากแคลคูลัสแลมบ์ดาซึ่งได้รับการแนะนำโดย Alonzo Church แหล่งกำเนิดอื่นคือตรรกะแบบผสมผสานซึ่งได้รับการแนะนำโดย Moses Schönfinkelและพัฒนาต่อมาโดย Haskell Curry

การเขียนโปรแกรมเชิงวัตถุกับฟังก์ชัน

ฉันได้สร้างแอปพลิเคชัน Java ที่เปรียบเทียบวิธีการเขียนโค้ดที่จำเป็นเชิงวัตถุและเชิงประกาศและใช้งานได้ในการเขียนโค้ด ศึกษาโค้ดด้านล่างแล้วฉันจะชี้ให้เห็นความแตกต่างระหว่างสองตัวอย่าง

รายชื่อ 1. พนักงาน java

import java.util.ArrayList; import java.util.List; public class Employees { static class Employee { private String name; private int age; Employee(String name, int age) { this.name = name; this.age = age; } int getAge() { return age; } @Override public String toString() { return name + ": " + age; } } public static void main(String[] args) { List employees = new ArrayList(); employees.add(new Employee("John Doe", 63)); employees.add(new Employee("Sally Smith", 29)); employees.add(new Employee("Bob Jone", 36)); employees.add(new Employee("Margaret Foster", 53)); printEmployee1(employees, 50); System.out.println(); printEmployee2(employees, 50); } public static void printEmployee1(List employees, int age) { for (Employee emp: employees) if (emp.getAge() < age) System.out.println(emp); } public static void printEmployee2(List employees, int age) { employees.stream() .filter(emp -> emp.age  System.out.println(emp)); } }

รายการที่ 1 แสดงEmployeesแอปพลิเคชันที่สร้างอEmployeeอบเจ็กต์สองสามชิ้นจากนั้นพิมพ์รายชื่อพนักงานทั้งหมดที่อายุน้อยกว่า 50 ปีรหัสนี้แสดงให้เห็นทั้งรูปแบบการเขียนโปรแกรมเชิงวัตถุและเชิงฟังก์ชัน

printEmployee1()วิธีการแสดงให้เห็นถึงความจำเป็นวิธีการคำสั่งที่มุ่งเน้น ตามที่ระบุไว้วิธีนี้จะวนซ้ำรายชื่อพนักงานเปรียบเทียบอายุของพนักงานแต่ละคนกับค่าอาร์กิวเมนต์และ (หากอายุน้อยกว่าอาร์กิวเมนต์) ให้พิมพ์รายละเอียดของพนักงาน

printEmployee2()วิธีการเผยเปิดเผยวิธีการแสดงออกที่มุ่งเน้นในกรณีนี้การดำเนินการกับลำธาร API แทนที่จะระบุวิธีการพิมพ์พนักงานอย่างจำเป็น (ทีละขั้นตอน) นิพจน์จะระบุผลลัพธ์ที่ต้องการและทิ้งรายละเอียดวิธีการพิมพ์ไปยัง Java คิดว่าfilter()เทียบเท่ากับฟังก์ชันของifคำสั่งและforEach()เทียบเท่ากับforคำสั่ง

คุณสามารถรวบรวม Listing 1 ได้ดังนี้:

javac Employees.java

ใช้คำสั่งต่อไปนี้เพื่อรันแอ็พพลิเคชันผลลัพธ์:

java Employees

ผลลัพธ์ควรมีลักษณะดังนี้:

Sally Smith: 29 Bob Jone: 36 Sally Smith: 29 Bob Jone: 36

ตัวอย่างการเขียนโปรแกรมเชิงฟังก์ชัน

ในส่วนถัดไปเราจะสำรวจเทคนิคหลัก 5 ประการที่ใช้ในการเขียนโปรแกรมเชิงฟังก์ชัน ได้แก่ ฟังก์ชันบริสุทธิ์ฟังก์ชันลำดับที่สูงขึ้นการประเมินผลแบบขี้เกียจการปิดและการแกง ตัวอย่างในส่วนนี้เขียนโค้ดใน JavaScript เนื่องจากความเรียบง่ายเมื่อเทียบกับ Java จะช่วยให้เราสามารถมุ่งเน้นไปที่เทคนิคได้ ในส่วนที่ 2 เราจะทบทวนเทคนิคเดียวกันนี้อีกครั้งโดยใช้โค้ด Java

รายการ 2 นำเสนอซอร์สโค้ดให้กับRunScriptแอปพลิเคชัน Java ที่ใช้ Scripting API ของ Java เพื่ออำนวยความสะดวกในการรันโค้ด JavaScript RunScriptจะเป็นโปรแกรมพื้นฐานสำหรับตัวอย่างที่กำลังจะมีขึ้นทั้งหมด

รายการ 2. RunScript.java

import java.io.FileReader; import java.io.IOException; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import static java.lang.System.*; public class RunScript { public static void main(String[] args) { if (args.length != 1) { err.println("usage: java RunScript script"); return; } ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("nashorn"); try { engine.eval(new FileReader(args[0])); } catch (ScriptException se) { err.println(se.getMessage()); } catch (IOException ioe) { err.println(ioe.getMessage()); } } }

The main() method in this example first verifies that a single command-line argument (the name of a script file) has been specified. Otherwise, it displays usage information and terminates the application.

Assuming the presence of this argument, main() instantiates the javax.script.ScriptEngineManager class. ScriptEngineManager is the entry-point into Java's Scripting API.

Next, the ScriptEngineManager object's ScriptEngine getEngineByName(String shortName) method is called to obtain a script engine corresponding to the desired shortName value. Java 10 supports the Nashorn script engine, which is obtained by passing "nashorn" to getEngineByName(). The returned object's class implements the javax.script.ScriptEngine interface.

ScriptEngine declares several eval() methods for evaluating a script. main() invokes the Object eval(Reader reader) method to read the script from its java.io.FileReader object argument and (assuming that java.io.IOException isn't thrown) then evaluate the script. This method returns any script return value, which I ignore. Also, this method throws javax.script.ScriptException when an error occurs in the script.

Compile Listing 2 as follows:

javac RunScript.java

I'll show you how to run this application after I have presented the first script.

Functional programming with pure functions

A pure function is a functional programming function that depends only on its input arguments and no external state. An impure function is a functional programming function that violates either of these requirements. Because pure functions have no interaction with the outside world (apart from calling other pure functions), a pure function always returns the same result for the same arguments. Pure functions also have no observable side effects.

Can a pure function perform I/O?

If I/O is a side effect, can a pure function perform I/O? The answer is yes. Haskell uses monads to address this problem. See "Pure Functions and I/O" for more about pure functions and I/O.

Pure functions versus impure functions

The JavaScript in Listing 3 contrasts an impure calculatebonus() function with a pure calculatebonus2() function.

Listing 3. Comparing pure vs impure functions (script1.js)

// impure bonus calculation var limit = 100; function calculatebonus(numSales) { return(numSales > limit) ? 0.10 * numSales : 0 } print(calculatebonus(174)) // pure bonus calculation function calculatebonus2(numSales) { return (numSales > 100) ? 0.10 * numSales : 0 } print(calculatebonus2(174))

calculatebonus() is impure because it accesses the external limit variable. In contrast, calculatebonus2() is pure because it obeys both requirements for purity. Run script1.js as follows:

java RunScript script1.js

Here's the output you should observe:

17.400000000000002 17.400000000000002

Suppose calculatebonus2() was refactored to return calculatebonus(numSales). Would calculatebonus2() still be pure? The answer is no: when a pure function invokes an impure function, the "pure function" becomes impure.

When no data dependency exists between pure functions, they can be evaluated in any order without affecting the outcome, making them suitable for parallel execution. This is one of functional programming's benefits.

More about impure functions

Not all functional programming functions need to be pure. As Functional Programming: Pure Functions explains, it is possible (and sometimes desirable) to "separate the pure, functional, value based core of your application from an outer, imperative shell."

Functional programming with higher-order functions

A higher-order function is a mathematical function that receives functions as arguments, returns a function to its caller, or both. One example is calculus's differential operator, d/dx, which returns the derivative of function f.

First-class functions are first-class citizens

Closely related to the mathematical higher-order function concept is the first-class function, which is a functional programming function that takes other functional programming functions as arguments and/or returns a functional programming function. First-class functions are first-class citizens because they can appear wherever other first-class program entities (e.g., numbers) can, including being assigned to a variable or being passed as an argument to or returned from a function.

The JavaScript in Listing 4 demonstrates passing anonymous comparison functions to a first-class sorting function.

Listing 4. Passing anonymous comparison functions (script2.js)

function sort(a, cmp) { for (var pass = 0; pass 
    
      pass; i--) if (cmp(a[i], a[pass]) < 0) { var temp = a[i] a[i] = a[pass] a[pass] = temp } } var a = [22, 91, 3, 45, 64, 67, -1] sort(a, function(i, j) { return i - j; }) a.forEach(function(entry) { print(entry) }) print('\n') sort(a, function(i, j) { return j - i; }) a.forEach(function(entry) { print(entry) }) print('\n') a = ["X", "E", "Q", "A", "P"] sort(a, function(i, j) { return i 
     
       j; }) a.forEach(function(entry) { print(entry) }) print('\n') sort(a, function(i, j) { return i > j ? -1 : i < j; }) a.forEach(function(entry) { print(entry) })
     
    

In this example, the initial sort() call receives an array as its first argument, followed by an anonymous comparison function. When called, the anonymous comparison function executes return i - j; to achieve an ascending sort. By reversing i and j, the second comparison function achieves a descending sort. The third and fourth sort() calls receive anonymous comparison functions that are slightly different in order to properly compare string values.

Run the script2.js example as follows:

java RunScript script2.js

Here's the expected output:

-1 3 22 45 64 67 91 91 67 64 45 22 3 -1 A E P Q X X Q P E A

Filter and map

Functional programming languages typically provide several useful higher-order functions. Two common examples are filter and map.

  • A filter processes a list in some order to produce a new list containing exactly those elements of the original list for which a given predicate (think Boolean expression) returns true.
  • A map applies a given function to each element of a list, returning a list of results in the same order.

JavaScript supports filtering and mapping functionality via the filter() and map() higher-order functions. Listing 5 demonstrates these functions for filtering out odd numbers and mapping numbers to their cubes.

Listing 5. Filtering and mapping (script3.js)

print([1, 2, 3, 4, 5, 6].filter(function(num) { return num % 2 == 0 })) print('\n') print([3, 13, 22].map(function(num) { return num * 3 }))

Run the script3.js example as follows:

java RunScript script3.js

You should observe the following output:

2,4,6 9,39,66

Reduce

Another common higher-order function is reduce, which is more commonly known as a fold. This function reduces a list to a single value.

Listing 6 uses JavaScript's reduce() higher-order function to reduce an array of numbers to a single number, which is then divided by the array's length to obtain an average.

Listing 6. Reducing an array of numbers to a single number (script4.js)

var numbers = [22, 30, 43] print(numbers.reduce(function(acc, curval) { return acc + curval }) / numbers.length)

Run Listing 6's script (in script4.js) as follows:

java RunScript script4.js

You should observe the following output:

31.666666666666668

You might think that the filter, map, and reduce higher-order functions obviate the need for if-else and various looping statements, and you would be right. Their internal implementations take care of decisions and iteration.

A higher-order function uses recursion to achieve iteration. A recursive function invokes itself, allowing an operation to repeat until it reaches a base case. You can also leverage recursion to achieve iteration in your functional code.

Functional programming with lazy evaluation

Another important functional programming feature is lazy evaluation (also known as nonstrict evaluation), which is the deferral of expression evaluation for as long as possible. Lazy evaluation offers several benefits, including these two:

  • Expensive (timewise) calculations can be deferred until they're absolutely necessary.
  • Unbounded collections are possible. They'll keep delivering elements for as long as they're requested to do so.

Lazy evaluation is integral to Haskell. It won't calculate anything (including a function's arguments before the function is called) unless it's strictly necessary to do so.

Java's Streams API capitalizes on lazy evaluation. A stream's intermediate operations (e.g., filter()) are always lazy; they don't do anything until a terminal operation (e.g., forEach()) is executed.

Although lazy evaluation is an important part of functional languages, even many imperative languages provide builtin support for some forms of laziness. For example, most programming languages support short-circuit evaluation in the context of the Boolean AND and OR operators. These operators are lazy, refusing to evaluate their right-hand operands when the left-hand operand is false (AND) or true (OR).

Listing 7 is an example of lazy evaluation in a JavaScript script.

Listing 7. Lazy evaluation in JavaScript (script5.js)

var a = false && expensiveFunction("1") var b = true && expensiveFunction("2") var c = false || expensiveFunction("3") var d = true || expensiveFunction("4") function expensiveFunction(id) { print("expensiveFunction() called with " + id) }

Run the code in script5.js as follows:

java RunScript script5.js

You should observe the following output:

expensiveFunction() called with 2 expensiveFunction() called with 3

Lazy evaluation is often combined with memoization, an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs reoccur.

Because lazy evaluation doesn't work with side effects (such as code that produces exceptions and I/O), imperative languages mainly use eager evaluation (also known as strict evaluation), where an expression is evaluated as soon as it's bound to a variable.

More about lazy evaluation and memoization

A Google search will reveal many useful discussions of lazy evaluation with or without memoization. One example is "Optimizing your JavaScript with functional programming."

Functional programming with closures

First-class functions are associated with the concept of a closure, which is a persistent scope that holds onto local variables even after the code execution has left the block in which the local variables were defined.

Crafting closures

Operationally, a closure is a record that stores a function and its environment. The environment maps each of the function's free variables (variables used locally, but defined in an enclosing scope) with the value or reference to which the variable's name was bound when the closure was created. It lets the function access those captured variables through the closure's copies of their values or references, even when the function is invoked outside their scope.

To help clarify this concept, Listing 8 presents a JavaScript script that introduces a simple closure. The script is based on the example presented here.

Listing 8. A simple closure (script6.js)

function add(x) { function partialAdd(y) { return y + x } return partialAdd } var add10 = add(10) var add20 = add(20) print(add10(5)) print(add20(5))

Listing 8 defines a first-class function named add() with a parameter x and a nested function partialAdd(). The nested function partialAdd() has access to x because x is in add()'s lexical scope. Function add() returns a closure that contains a reference to partialAdd() and a copy of the environment around add(), in which x has the value assigned to it in a specific invocation of add().

Because add() returns a value of function type, variables add10 and add20 also have function type. The add10(5) invocation returns 15 because the invocation assigns 5 to parameter y in the call to partialAdd(), using the saved environment for partialAdd() where x is 10. The add20(5) invocation returns 25 because, although it also assigns 5 to y in the call to partialAdd(), it's now using another saved environment for partialAdd() where x is 20. Thus, while add10() and add20() use the same function partialAdd(), the associated environments differ and invoking the closures will bind x to two different values in the two invocations, evaluating the function to two different results.

Run Listing 8's script (in script6.js) as follows:

java RunScript script6.js

You should observe the following output:

15 25

Functional programming with currying

Currying is a way to translate the evaluation of a multi-argument function into the evaluation of an equivalent sequence of single-argument functions. For example, a function takes two arguments: x and y. Currying transforms the function into taking only x and returning a function that takes only y. Currying is related to but is not the same as partial application, which is the process of fixing a number of arguments to a function, producing another function of smaller arity.

Listing 9 presents a JavaScript script that demonstrates currying.

Listing 9. Currying in JavaScript (script7.js)

function multiply(x, y) { return x * y } function curried_multiply(x) { return function(y) { return x * y } } print(multiply(6, 7)) print(curried_multiply(6)(7)) var mul_by_4 = curried_multiply(4) print(mul_by_4(2))

The script presents a noncurried two-argument multiply() function, followed by a first-class curried_multiply() function that receives multiplicand argument x and returns a closure containing a reference to an anonymous function (that receives multiplier argument y) and a copy of the environment around curried_multiply(), in which x has the value assigned to it in an invocation of curried_multiply().

The rest of the script first invokes multiply() with two arguments and prints the result. It then invokes curried_multiply() in two ways:

  • curried_multiply(6)(7) results in curried_multiply(6) executing first. The returned closure executes the anonymous function with the closure's saved x value 6 being multiplied by 7.
  • var mul_by_4 = curried_multiply(4) executes curried_multiply(4) and assigns the closure to mul_by_4. mul_by_4(2) executes the anonymous function with the closure's 4 value and the passed argument 2.

Run Listing 9's script (in script7.js) as follows:

java RunScript script7.js

You should observe the following output:

42 42 8

Why use currying?

In his blog post "Why curry helps," Hugh Jackson observes that "little pieces can be configured and reused with ease, without clutter." Quora's "What are the advantages of currying in functional programming?" describes currying as "a cheap form of dependency injection," that eases the process of mapping/filtering/folding (and higher order functions generally). This Q&A also notes that currying "helps us create abstract functions."

In conclusion

In this tutorial you've learned some basics of functional programming. We've used examples in JavaScript to study five core functional programming techniques, which we'll further explore using Java code in Part 2. In addition to touring Java 8's functional programming capabilities, the second half of this tutorial will help you begin to think functionally, by converting an example of object-oriented Java code to its functional equivalent.

Learn more about functional programming

I found the book Introduction to Functional Programming (Richard Bird and Philip Wadler, Prentice Hall International Series in Computing Science, 1992) helpful in learning the basics of functional programming.

This story, "Functional programming for Java developers, Part 1" was originally published by JavaWorld .