Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

10,354 views

Published on

2012 Java TWO

你可以在以下鏈結找到中文內容:
http://www.codedata.com.tw/java/understanding-lambda-closure-1-from-javascript-function-1/

Published in: Technology
  • 第11頁有錯, 應該是「 f(x) 代入 g(y) = y-1」
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫

  1. 1. Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫
  2. 2. 林信良 http://openhome.cccaterpillar@openhome.cc
  3. 3. 議程• 一級函式與 λ 演算• JDK8 的 Lambda 語法• 介面預設方法(Default method)• 擴充的 Collection 框架• 函數式風格的可能性
  4. 4. 一級函式與 λ 演算 第十一個希臘字母 物理上的波長符號 放射學的衰變常數 線性代數的特徵值 λ 演算 ….
  5. 5. 一級值• 值(Value)可指定給變數• 一級(First-class)值可傳給函式或從函式中傳回 物件 函式? 基本型態 運算式?
  6. 6. 一級函式• 有些語言中函式是一等公民(First-class citizen) – 高階函式(High order function) function doSome(param) { // 作些事 } var doSome = function(param) { // 作些事 };
  7. 7. 一級函式• 有些語言中函式是一等公民(First-class citizen) – 高階函式(High order function) function doSome(param) { // 作些事 } var doSome = function(param) { // 作些事 }; doOther(function(param) { function(param) // 作些事 // 作些事 }); };
  8. 8. 一級函式• 有些語言中函式是一等公民(First-class citizen) – 高階函式(High order function) function doSome(param) { // 作些事 } var doSome = function(param) { // 作些事 }; function doAction(opt) { return function(param) { // 作些事 作些事 // }; }; };
  9. 9. What is http://en.wikipedia.org/wiki/Lambda In programming languages such as Lisp and Python, lambda is an operator used to denote anonymous functions or closures/lambda/
  10. 10. λ 演算(Lambda calculus)• 每個表達式(Expression)代表具單一參數函數• 參數本身亦可接受具有單一參數的函式• f(x) = x * 2 可匿名地表示為 … λ x. x * 2 x -> x * 2
  11. 11. λ 演算(Lambda calculus)• 如果要套用 x 為 2 (x -> x * 2)(2) = 2 * 2 = 4• g(y) = y -1 代入 f(x) 匿名地表達為… (y -> y - 1)(x -> x * 2) = x -> x * 2 - 1
  12. 12. λ 演算(Lambda calculus)• 多參數的函數可使用單參數函數套用而成 (x, y) -> x * x + y * y = x -> (y -> x * x + y * y)•若x為2而y為3(x -> (y -> x * x + y * y))(2)(3)= (y -> 2 * 2 + y * y)(3)= 4 + 3 * 3= 4 + 9= 13
  13. 13. λ 演算(Lambda calculus)• 可用來表現任何可計算函數• 可使用函數實現控制結構(如 if、forEach等)• 一個小型通用語言 butThat = cond->fv->tv-> (cond or fv) and (cond and tv) butThat(true)(10)(20) = (true or 10) and (true and 20) = true and 20 = 20
  14. 14. 一級函式與 λ 演算• 不同語言提供不同程度的 Lambda 表達式支援(x -> x * 2)(2) function(x) { return x * 2; }(2);
  15. 15. 一級函式與 λ 演算• JDK8 前存在 Lambda 語法類似品 – 匿名內部類別(Anonymous inner class)• 定義單一抽象方法(Single abstract method)介面模擬一級函式 interface Func<P, R> { R apply(P p); }
  16. 16. 一級函式與 λ 演算• 使用匿名內部類別來表示 x -> x * 2new Func<Integer, Integer>() { public Integer apply(Integer x) { return x * 2; }};
  17. 17. 一級函式與 λ 演算• 設計 compose(f, g) 達成 g(f(x)) 的函數組合static <A, B, C> Func<A, C> compose( final Func<A, B> f, final Func<B, C> g) { return new Func<A, C>() { public C apply(A x) { return g.apply(f.apply(x)); } };}
  18. 18. 一級函式與 λ 演算• f(x) = x + 2 而 g(y) = y * 3• h(x) = g(f(x))compose( new Func<Integer, Integer>() { public Integer apply(Integer x) { return x + 2; } }, new Func<Integer, Integer>() { public Integer apply(Integer y) { return y * 3; } });
  19. 19. JDK8 的 Lambda 語法 λ
  20. 20. JDK8 的 Lambda 語法 • 使用 JDK8 的 Lambda 語法來表示 x -> x * 2 (Integer x) -> x + 2 • 設計 compose(f, g) 達成 g(f(x)) 的函數組合static <A, B, C> Func<A, C> compose(Func<A, B> f, Func<B, C> g) { // 忽略.apply 就是 x -> g(f(x)) return x -> g.apply(f.apply(x));} • f(x) = x + 2 、 g(y) = y * 3、h(x) = g(f(x)) compose((Integer x) -> x + 2, (Integer y) -> y * 3)
  21. 21. JDK8 的 Lambda 表示式• (type parameter) -> function_body – 單一運算式 (int x, int y) -> x + y () -> 42 – 陳述區塊 (String s) -> { System.out.println(s); }
  22. 22. 函式介面(Functional interface)• 單一抽象方法的介面 public interface Runnable { void run(); } public interface Comparator<T> { int compare(T o1, T o2); } public interface Callable<V> { V call() throws Exception }
  23. 23. 目標定型(Target typing)• 目標型態與環境(Context)有關 – 目標型態可用於類型推斷(Type inference)Runnable r = () -> { out.println("later"); };Comparator<String> c = (s1, s2) -> s1.compareTo(s2);Collections.sort(lists, (s1, s2) -> -s1.compareTo(s2));• 獨立於型態的Lambda 表示式Callable<String> c = () -> "done";PrivilegedAction<String> a = () -> "done";
  24. 24. 不只是語法蜜糖• 語彙範圍(Lexical scoping) – Lambda 語法本體中的名稱與包裹環境相同public class Hello { Runnable r1 = () -> { out.println(this); } Runnable r2 = () -> { out.println(toString()); } public String toString() { return "Hello, world!"; } public static void main(String... args) { new Hello().r1.run(); new Hello().r2.run(); }}
  25. 25. 不只是語法蜜糖• 變數捕捉(Variable capture) – 與 final 等效的區域變數不需要 finalCallable<String> helloCallable(String name) { String hello = "Hello"; return () -> (hello + ", " + name);}• 編譯錯誤...int sum = 0;list.forEach(e -> { sum += e.size(); });• 建議...int sum = list.map(e -> e.size()) .reduce(0, (a, b) -> a + b);
  26. 26. 方法參考(Method reference) • 類別靜態方法public class Person { ... public static int compareByAge(Person a, Person b) { ... } public static int compareByName(Person a, Person b) { ... }}Person[] people = ...// Arrays.sort(people, (a, b) -> a.getAge() – b.getAge());Arrays.sort(people, Person::compareByAge);interface Block<T> { void run(T arg); }Block<Integer> b1 = System::exit; // void exit(int status)Block<String[]> b2 = Arrays::sort; // void sort(Object[] a)Block<String> b3 = MyProgram::main; // void main(String... args)Runnable r = MyProgram::main; // void main(String... args)
  27. 27. 方法參考(Method reference)• 特定物件實例方法public class ComparisonProvider { public int compareByName(Person p1, Person p2) { ... } public int compareByAge(Person p1, Person p2) { ... }}...ComparisonProvider comparisonProvider = ...;Arrays.sort(people, comparisonProvider::compareByName);• 實例方法中可帶物件狀態 – 相當於讓 Lambda 語法帶有狀態(Closure 的替代?)
  28. 28. 方法參考(Method reference)• 任意類別的實例方法List<String> names = Arrays.asList("Justin", ...);Collections.sort(names, (s1, s2) -> s1.compareToIgnoreCase(s2));Collections.sort(names, String::compareToIgnoreCase);
  29. 29. 方法參考(Method reference)• 讓 Lambda 語法的目標類型可以變換Callable<String> c = () -> "done";PrivilegedAction<String> a = c::call;
  30. 30. 建構式參考(Constructor reference)• 像是特殊的靜態方法參考: SocketFactory factory = SocketImpl::new;
  31. 31. 介面預設方法(Default method)
  32. 32. 介面預設方法(Default method)• JDK8 前實作介面以實現多個規格的繼承來源 – 捨棄繼承多個實作來源的可能性,規避了多重繼承下實 作衝突等問題public class Ball implements Comparable<Ball> { public boolean notEquals(Ball that) { return this.radius - that.radius; } // 底下都是根據notEquals()實作 public boolean lessThan(Ball that) { return this.notEquals(that) < 0; } public boolean lessOrEquals(Ball that) { return this.lessThan(that) || this.notEquals(that) == 0; } public boolean greaterThan(Ball that) { return !this.lessOrEquals(that); } ...
  33. 33. 介面預設方法(Default method)• JDK8 介面可以是根據抽象的共用實作public interface Comparable<T> { boolean notEquals(T that); boolean lessThan(T that) default { return this.notEquals(that) < 0; } boolean lessOrEquals(T that) default { return this.lessThan(that) || this.notEquals(that) == 0; } boolean greaterThan(T that) default { return !this.lessOrEquals(that); } ...
  34. 34. 介面預設方法(Default method)• JDK8 可以實作具有預設方法的介面public class Ball implements Comparable<Ball> { public boolean notEquals(Ball that) { return this.radius - that.radius; } ...• 實作可能發生衝突.. interface Robot implements Artist, Gun { void draw() default { Artist.super.draw(); } }
  35. 35. 介面預設方法(Default method)• 擴充既有程式庫以搭配 Lambda 語法 – 僅在介面中新增方法定義? – 使用靜態方法?像是 Collections.filter(…)? – 獨立的新 API?像是 Collection2 框架? interface Iterator<E> { boolean hasNext(); E next(); void remove(); void skip(int i) default { for (; i > 0 && hasNext(); i--) next(); } }
  36. 36. 擴充的 Collection 框架• 高階函式 – 可讀性 – 簡潔性 – 導入新的抽象層• 以迭代為例… for(Ball b : balls) { b.setColor(RED); } balls.forEach(b -> { b.setColor(RED); });
  37. 37. 擴充的 Collection 框架• 匿名類別作不到嗎?– 想分離關切點,但又帶入新的關切點 balls.forEach(new Block<Ball> { public void apply(Ball ball) { ball.setColor(RED); } });• 傳入 Lambda 表示式,而非傳入不想要的雜訊
  38. 38. 擴充的 Collection 框架• 為什麼是 Collection 框架?• 許多問題都是資料處理問題… – 關聯式資料通常就是待處理資料群集…• filter、map、fold 等是處理資料時的高階抽象 – 過濾一組資料 – 將一組資料對應至另一組資料 – 逐一取得資料計算單一結果 – …
  39. 39. 擴充的 Collection 框架• 管線化(Piped)操作int sum = names.filter(s -> s.length() < 3) .map(s -> s.length()) .reduce(0, (sum, len) -> sum + len);int sum = names.filter(s -> s.length() < 3) .map(s -> s.length()) .sum();
  40. 40. 擴充的 Collection 框架• 管線化(Piped)操作blocks.filter(b -> b.getColor() == BLUE) .forEach(b -> { b.setColor(RED); });List<Block> blue = blocks.filter(b -> b.getColor() == BLUE) .into(new ArrayList<>());Set<Box> blueBlock = blocks.filter(b -> b.getColor() == BLUE) .map(b -> b.getContainingBox()) .into(new HashSet<>());
  41. 41. 擴充的 Collection 框架• 延遲(Laziness) int sum = blocks.filter(b -> b.getColor() == BLUE) .map(b -> b.getWeight()) .sum();• 捷徑(short-circuiting) Block firstBlue = blocks.filter(b -> b.getColor() == BLUE) .getFirst();
  42. 42. 擴充的 Collection 框架• 共用的函式介面(java.util.functions) – Predicate – boolean test(T t) – Block – void apply(T t); – Mapper – U map(T t) – ...
  43. 43. 擴充的 Collection 框架• 已實作 filter、map 與 fold 的細節 – 循序? – 遞迴? – 共用資料結構? – 平行化?• 可以用更高階的抽象來處理資料 int sum = blocks.parallel() .filter(b -> b.getColor() == BLUE) .map(b -> b.getWeight()) .sum();
  44. 44. 全部放在一起for (Method m : enclosingInfo.getEnclosingClass().getDeclaredMethods()) { if (m.getName().equals(enclosingInfo.getName()) ) { Class<?>[] candidateParamClasses = m.getParameterTypes(); if (candidateParamClasses.length == parameterClasses.length) { boolean matches = true; for(int i = 0; i < candidateParamClasses.length; i++) { if (!candidateParamClasses[i].equals(parameterClasses[i])) { matches = false; break; } } if (matches) { // finally, check return type if (m.getReturnType().equals(returnType) ) return m; } } }}throw new InternalError("Enclosing method not found");
  45. 45. 全部放在一起Method matching = Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods()) .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName()) .filter(m -> Arrays.equals(m.getParameterTypes(), parameterClasses)) .filter(m -> Objects.equals(m.getReturnType(), returnType)) .getFirst();if (matching == null) throw new InternalError("Enclosing method not found");return matching;
  46. 46. 全部放在一起List<Album> favs = new ArrayList<>();for (Album a : albums) { boolean hasFavorite = false; for (Track t : a.tracks) { if (t.rating >= 4) { hasFavorite = true; break; } } if (hasFavorite) favs.add(a);}Collections.sort(favs, new Comparator<Album>() { public int compare(Album a1, Album a2) { return a1.name.compareTo(a2.name); }});
  47. 47. 全部放在一起List<Album> sortedFavs = albums.filter(a -> a.tracks.anyMatch(t -> (t.rating >= 4))) .sortedBy(a -> a.name) .into(new ArrayList<>());
  48. 48. 函數式風格的可能性
  49. 49. 函數式風格的可能性• Lambda 語法在原語言中提供小型通用語言• 在某些問題領域… – 提供以 λ 演算解題的可能性• Java 在函數式程式設計(FunctionalParogramming)的可能性?
  50. 50. 函數式風格的可能性• 費式數的定義 { F0 = 0, F1 = 1, Fn = Fn-1 + Fn-2 }• 指令式程式設計(Imperative programming)? int fib(int n) { int a = 1; int b = 1; for(int i = 2; i < n; i++) { int tmp = b; b = a + b; a = tmp; } return b; }
  51. 51. 函數式風格的可能性• 函數式程式設計? int fib(int n) { if(n == 0 || n == 1) return n; else return fib(n - 1) + fib(n - 2); }• 使用 Haskell fib 0 = 0 fib 1 = 1 fib n = fib (n-1) + fib (n-2)
  52. 52. 函數式風格的可能性• 函數式語言的特徵 fib 0 = 0 fib 1 = 1– 一級函式 fib n = fib (n-1) + fib (n-2)– Immutable sum [] = 0– Lazy evaluation sum (x:xs) = x + sum xs– Pattern match {2 * x | x ∈ {1, 2..100}}– List comprehension [2 * x | x <- [1, 2..100]]– Curried function f x y = x + y g y = f 1 y
  53. 53. 函數式風格的可能性• JDK8 有多少函數式風格的特性?– 直接支援 Lambda 語法– 使用 final 變數強制 Immutable– 以 API 封裝複雜細節達到… – 延遲求值 – 模式匹配 – List comprehension – Partially applied 與 Curried function
  54. 54. 函數式風格的可能性 • 以 API 封裝複雜細節,留下函數式外觀給使用者 – Functional Java(functionaljava.org) – 目的就是為了讓 Java 實現函數式風格 – 主要針對 JDK5 以上 – 使用匿名內部類別繼承抽象類別 final Array<String> a = array("Hello", "There", "what"); final boolean b = a.exists(new F<String, Boolean>() { public Boolean f(final String s) { return fromString(s).forall(isLowerCase); } });boolean b = a.exists(s -> fromString(s).forall(isLowerCase));
  55. 55. 函數式風格的可能性• 以 API 封裝複雜細節,留下函數式外觀給使用者– Guava(code.google.com/p/guava-libraries) – 主要目的並非為了讓 Java 實現函數式風格 – 主要針對 JDK5 以上 – 使用匿名內部類別實作介面
  56. 56. 函數式風格的可能性 • Guava 目前不鼓勵函數式風格Multiset<Integer> lengths = HashMultiset.create( FluentIterable.from(strings) .filter(new Predicate<String>() { public boolean apply(String string) { return CharMatcher.JAVA_UPPER_CASE.matchesAllOf(string); } }) .transform(new Function<String, Integer>() { public Integer apply(String string) { return string.length(); } }));Multiset<Integer> lengths = HashMultiset.create( FluentIterable.from(strings) .filter(s -> CharMatcher.JAVA_UPPER_CASE.matchesAllOf(s)) .transform(s -> s.length())));
  57. 57. 總結• λ 語法為 Java 提供了小型通用語言,在某些問題領域,提供 λ 演算解題的可能性• 除 JDK 程式庫將搭配 λ 語法演化,開放原始碼如Functional Java、Guava 等也將呈現不同風格• 新的抽象層將平行化、函數式等實作予以封裝,簡潔的表述能力,使開發者進一步思索 map、filter、fold 等問題的基本形式
  58. 58. 參考資料• State of the Lambda v4 – http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html• State of the Lambda: Libraries Edition – http://cr.openjdk.java.net/~briangoetz/lambda/collections- overview.html• Project Lambda – http://openjdk.java.net/projects/lambda/• Lambda calculus – http://en.wikipedia.org/wiki/Lambda_calculus• Functional Programming for Java Developers – http://shop.oreilly.com/product/0636920021667.do• APIO讲稿——函数式编程 – http://www.byvoid.com/blog/apio-fp/
  59. 59. 感謝 Orz林信良http://openhome.cccaterpillar@openhome.cc

×