Upload
justin-lin
View
5.781
Download
3
Embed Size (px)
DESCRIPTION
2012 Java TWO 你可以在以下鏈結找到中文內容: http://www.codedata.com.tw/java/understanding-lambda-closure-1-from-javascript-function-1/
Citation preview
Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫
林信良 http://openhome.cc
議程
•一級函式與 λ 演算
• JDK8 的 Lambda 語法
•介面預設方法(Default method)
•擴充的 Collection 框架
•函數式風格的可能性
一級函式與 λ 演算
第十一個希臘字母 物理上的波長符號 放射學的衰變常數 線性代數的特徵值 λ 演算 ….
一級值
•值(Value)可指定給變數
•一級(First-class)值可傳給函式或從函式中傳回
基本型態
物件
函式?
運算式?
一級函式
•有些語言中函式是一等公民(First-class citizen) – 高階函式(High order function)
function doSome(param) {
// 作些事 }
var doSome = function(param) {
// 作些事 };
function(param) {
// 作些事 };
一級函式
•有些語言中函式是一等公民(First-class citizen) – 高階函式(High order function)
doOther(function(param) {
// 作些事 });
function doSome(param) {
// 作些事 }
var doSome = function(param) {
// 作些事 };
function(param) {
// 作些事 };
一級函式
•有些語言中函式是一等公民(First-class citizen) – 高階函式(High order function)
function doAction(opt) {
return function(param) {
// 作些事 };
};
function doSome(param) {
// 作些事 }
var doSome = function(param) {
// 作些事 };
function(param) {
// 作些事 };
/lambda/
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 calculus)
•每個表達式(Expression)代表具單一參數函數
•參數本身亦可接受具有單一參數的函式
• f(x) = x * 2 可匿名地表示為 …
λ x. x * 2
x -> x * 2
λ 演算(Lambda calculus)
•如果要套用 x 為 2
• g(y) = y -1 代入 f(x) 匿名地表達為…
(x -> x * 2)(2)
= 2 * 2
= 4
(y -> y - 1)(x -> x * 2)
= x -> x * 2 - 1
λ 演算(Lambda calculus)
•多參數的函數可使用單參數函數套用而成
•若 x 為 2 而 y 為 3
(x, y) -> x * x + y * y
= x -> (y -> x * x + y * y)
(x -> (y -> x * x + y * y))(2)(3)
= (y -> 2 * 2 + y * y)(3)
= 4 + 3 * 3
= 4 + 9
= 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
一級函式與 λ 演算
•不同語言提供不同程度的 Lambda 表達式支援
(x -> x * 2)(2)
function(x) {
return x * 2;
}(2);
一級函式與 λ 演算
• JDK8 前存在 Lambda 語法類似品 – 匿名內部類別(Anonymous inner class)
•定義單一抽象方法(Single abstract method)介面模擬一級函式
interface Func<P, R> {
R apply(P p);
}
一級函式與 λ 演算
•使用匿名內部類別來表示 x -> x * 2
new Func<Integer, Integer>() {
public Integer apply(Integer x) {
return x * 2;
}
};
一級函式與 λ 演算
•設計 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));
}
};
}
一級函式與 λ 演算
• 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;
}
}
);
JDK8 的 Lambda 語法
λ
JDK8 的 Lambda 語法
•使用 JDK8 的 Lambda 語法來表示 x -> x * 2
•設計 compose(f, g) 達成 g(f(x)) 的函數組合
• f(x) = x + 2 、 g(y) = y * 3、h(x) = g(f(x))
(Integer x) -> x + 2
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));
}
compose((Integer x) -> x + 2, (Integer y) -> y * 3)
JDK8 的 Lambda 表示式
• (type parameter) -> function_body – 單一運算式
– 陳述區塊
(int x, int y) -> x + y
() -> 42
(String s) -> { System.out.println(s); }
函式介面(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
}
目標定型(Target typing)
•目標型態與環境(Context)有關 – 目標型態可用於類型推斷(Type inference)
•獨立於型態的Lambda 表示式
Runnable r = () -> { out.println("later"); };
Comparator<String> c = (s1, s2) -> s1.compareTo(s2);
Collections.sort(lists, (s1, s2) -> -s1.compareTo(s2));
Callable<String> c = () -> "done";
PrivilegedAction<String> a = () -> "done";
不只是語法蜜糖
•語彙範圍(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();
}
}
不只是語法蜜糖
•變數捕捉(Variable capture) – 與 final 等效的區域變數不需要 final
•編譯錯誤...
•建議...
Callable<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);
方法參考(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)
方法參考(Method reference)
•特定物件實例方法
•實例方法中可帶物件狀態 – 相當於讓 Lambda 語法帶有狀態(Closure 的替代?)
public class ComparisonProvider {
public int compareByName(Person p1, Person p2) { ... }
public int compareByAge(Person p1, Person p2) { ... }
}
...
ComparisonProvider comparisonProvider = ...;
Arrays.sort(people, comparisonProvider::compareByName);
方法參考(Method reference)
•任意類別的實例方法
Collections.sort(names, String::compareToIgnoreCase);
List<String> names = Arrays.asList("Justin", ...);
Collections.sort(names,
(s1, s2) -> s1.compareToIgnoreCase(s2));
方法參考(Method reference)
•讓 Lambda 語法的目標類型可以變換
Callable<String> c = () -> "done";
PrivilegedAction<String> a = c::call;
建構式參考(Constructor reference)
•像是特殊的靜態方法參考:
SocketFactory factory = SocketImpl::new;
介面預設方法(Default method)
介面預設方法(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);
} ...
介面預設方法(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);
} ...
介面預設方法(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(); }
}
介面預設方法(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();
}
}
擴充的 Collection 框架
•高階函式 – 可讀性 – 簡潔性 – 導入新的抽象層
•以迭代為例…
for(Ball b : balls) {
b.setColor(RED);
}
balls.forEach(b -> { b.setColor(RED); });
擴充的 Collection 框架
•匿名類別作不到嗎? – 想分離關切點,但又帶入新的關切點
•傳入 Lambda 表示式,而非傳入不想要的雜訊
balls.forEach(new Block<Ball> {
public void apply(Ball ball) {
ball.setColor(RED);
}
});
擴充的 Collection 框架
•為什麼是 Collection 框架?
•許多問題都是資料處理問題… – 關聯式資料通常就是待處理資料群集…
• filter、map、fold 等是處理資料時的高階抽象 – 過濾一組資料 – 將一組資料對應至另一組資料 – 逐一取得資料計算單一結果 – …
擴充的 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();
擴充的 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<>());
擴充的 Collection 框架
•延遲(Laziness)
•捷徑(short-circuiting)
int sum = blocks.filter(b -> b.getColor() == BLUE)
.map(b -> b.getWeight())
.sum();
Block firstBlue = blocks.filter(b -> b.getColor() == BLUE)
.getFirst();
擴充的 Collection 框架
•共用的函式介面(java.util.functions) – Predicate
– boolean test(T t)
– Block
– void apply(T t);
– Mapper
– U map(T t)
– ...
擴充的 Collection 框架
•已實作 filter、map 與 fold 的細節 – 循序? – 遞迴? – 共用資料結構? – 平行化?
•可以用更高階的抽象來處理資料
int sum = blocks.parallel()
.filter(b -> b.getColor() == BLUE)
.map(b -> b.getWeight())
.sum();
全部放在一起 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");
全部放在一起
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;
全部放在一起 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);
}});
全部放在一起
List<Album> sortedFavs =
albums.filter(a -> a.tracks.anyMatch(t -> (t.rating >= 4)))
.sortedBy(a -> a.name)
.into(new ArrayList<>());
函數式風格的可能性
函數式風格的可能性
• Lambda 語法在原語言中提供小型通用語言
•在某些問題領域… – 提供以 λ 演算解題的可能性
• Java 在函數式程式設計(Functional Parogramming)的可能性?
函數式風格的可能性
•費式數的定義
•指令式程式設計(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;
}
{ F0 = 0, F1 = 1, Fn = Fn-1 + Fn-2 }
函數式風格的可能性
•函數式程式設計?
•使用 Haskell
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
int fib(int n) {
if(n == 0 || n == 1) return n;
else return fib(n - 1) + fib(n - 2);
}
函數式風格的可能性
•函數式語言的特徵 – 一級函式 – Immutable – Lazy evaluation – Pattern match – List comprehension – Curried function
{2 * x | x ∈ {1, 2..100}}
[2 * x | x <- [1, 2..100]]
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
sum [] = 0
sum (x:xs) = x + sum xs
f x y = x + y
g y = f 1 y
函數式風格的可能性
• JDK8 有多少函數式風格的特性? – 直接支援 Lambda 語法 – 使用 final 變數強制 Immutable
– 以 API 封裝複雜細節達到… – 延遲求值 – 模式匹配 – List comprehension – Partially applied 與 Curried function
函數式風格的可能性
•以 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));
函數式風格的可能性
•以 API 封裝複雜細節,留下函數式外觀給使用者 – Guava(code.google.com/p/guava-libraries) – 主要目的並非為了讓 Java 實現函數式風格 – 主要針對 JDK5 以上 – 使用匿名內部類別實作介面
函數式風格的可能性
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();
}
}));
•Guava 目前不鼓勵函數式風格
Multiset<Integer> lengths = HashMultiset.create(
FluentIterable.from(strings)
.filter(s -> CharMatcher.JAVA_UPPER_CASE.matchesAllOf(s))
.transform(s -> s.length())
));
總結
• λ 語法為 Java 提供了小型通用語言,在某些問題領域,提供 λ 演算解題的可能性
•除 JDK 程式庫將搭配 λ 語法演化,開放原始碼如 Functional Java、Guava 等也將呈現不同風格
•新的抽象層將平行化、函數式等實作予以封裝,簡潔的表述能力,使開發者進一步思索 map、filter、fold 等問題的基本形式
參考資料
• 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/
感謝 Orz 林信良 http://openhome.cc [email protected]