java8的函數式編程解析

2020-12-11 淺源深科

其實在java8就已經有java的函數式編程寫法,只是難度較大,大家都習慣了對象式用法,但在其它語言中都有函數式的用法,如js,scala,函數式其實是抽象到極致的思想。

什麼是函數式編程

函數式編程並不是Java新提出的概念,其與指令編程相比,強調函數的計算比指令的計算更重要;與過程化編程相比,其中函數的計算可以隨時調用。

當然,大家應該都知道面向對象的特性(抽象封裝繼承多態)。其實在Java8出現之前,我們關注的往往是某一類對象應該具有什麼樣的屬性,當然這也是面向對象的核心--對數據進行抽象。但是java8出現以後,這一點開始出現變化,似乎在某種場景下,更加關注某一類共有的行為(這似乎與之前的接口有些類似),這也就是java8提出函數式編程的目的。如圖1-1所示,展示了面向對象編程到面向行為編程的變化。

函數接口

java8 函數式編程的入口,每個函數接口都帶有 @FunctionalInterface 注釋,有且僅有一個未實現的方法,表示接收 Lambda 表達式,它們存在的意義在於將代碼塊作為數據打包起來。這幾個函數接口,完全可以把它們看成普通的接口,不過他們有且僅有一個抽象方法(因為要接收 Lambda 表達式)。@FunctionalInterface 該注釋會強制 javac 檢查一個接口是否符合函數接口的標準。 如果該注釋添加給一個枚舉類型、 類或另一個注釋, 或者接口包含不止一個抽象方法, javac 就會報錯。

Lambda 表達式

Lambda 表達式,有時候也稱為匿名函數或箭頭函數,幾乎在當前的各種主流的程式語言中都有它的身影。Java8 中引入 Lambda 表達式,使原本需要用匿名類實現接口來傳遞行為,現在通過 Lambda 可以更直觀的表達。

Lambda 表達式,也可稱為閉包。閉包就是一個定義在函數內部的函數,閉包使得變量即使脫離了該函數的作用域範圍也依然能被訪問到。Lambda 表達式的本質只是一個」語法糖」,由編譯器推斷並幫你轉換包裝為常規的代碼,因此你可以使用更少的代碼來實現同樣的功能。Lambda 表達式是一個匿名函數,即沒有函數名的函數。有些函數如果只是臨時一用,而且它的業務邏輯也很簡單時,就沒必要非給它取個名字不可。Lambda 允許把函數作為一個方法的參數(函數作為參數傳遞進方法中).Lambda 表達式語法如下:形參列表=>函數體(函數體多於一條語句的可用大括號括起)。在Java裡就是() -> {}:

(parameters) -> expression(parameters) ->{ statements; }

Lambda表達式的重要特徵:

Lambda 表達式主要用來定義行內執行的方法類型接口,例如,一個簡單方法接口。Lambda表達式是通過函數式接口(必須有且僅有一個抽象方法聲明)識別的可選類型聲明:不需要聲明參數類型,編譯器可以統一識別參數值。可選的參數圓括號:一個參數無需定義圓括號,但多個參數需要定義圓括號。可選的大括號:如果主體包含了一個語句,就不需要使用大括號。可選的返回關鍵字:如果主體只有一個表達式返回值,則編譯器會自動返回值,大括號需要指定表達式返回一個值。Lambda表達式中的變量作用域:

訪問權限與匿名對象的方式非常類似。只能夠訪問局部對應的外部區域的局部final變量,以及成員變量和靜態變量。在Lambda表達式中能訪問域外的局部非final變量、但不能修改Lambda域外的局部非final變量。因為在Lambda表達式中,Lambda域外的局部非final變量會在編譯的時候,會被隱式地當做final變量來處理。Lambda表達式內部無法訪問接口默認(default)方法.例子:使用Java 8之前的方法來實現對一個string列表進行排序:

List<String> names = Arrays.asList("peter","anna","mike","xenia");Collections.sort(names,newComparator<String>(){@Overridepublic int compare(String a, String b){return b.compareTo(a);}});

Java 8 Lambda 表達式:

Collections.sort(names,(String a, String b)->{return b.compareTo(a);});// 只有一條邏輯語句,可以省略大括號Collections.sort(names,(String a, String b)-> b.compareTo(a));// 可以省略入參類型Collections.sort(names,(a, b)-> b.compareTo(a));

FunctionalInterface

Java8的新引入,包含函數式的設計,接口都有@FunctionalInterface的註解。如聲明一個接口

@FunctionalInterfacepublic interface fun{} 這會編譯錯,編譯器會告訴你*no target method*。而如果加一個方法: @FunctionalInterface public interface fun{ void run(); } 這就OK了,一個函數式接口聲明好了。再加一個呢? @FunctionalInterface public interface fun{ void run(); void test(); } 不ok,明確說了只有一個抽象方法嘛。但是如果換一種函數籤名: @FunctionalInterface public interface fun{ Object equals(Object o); void test(); } 這就OK了。一個抽象方法,一個Object的public方法,相安無事。Object還有其他方法,clone方法試試會怎麼樣? @FunctionalInterface public interface fun{ Object clone(); void test(); }這又不行了,因為前面明確說了,要是Object的public方法,而clone是protected的。

小結:函數式接口,有且僅有一個抽象方法,Object的public方法除外。

因為Java本身支持多接口實現,你定義一個Class可以implements多個interface。所以這個限制也沒什麼影響,如果想約定一個函數式接口來統一,也可以做一些默認的實現來達到一個接口多個抽象方法的目的,比如下面這種做法:

一個普通接口NonFunc:

public interface NonFunc {void foo(); void voo(); } 函數式接口Func: public interface Func extends NonFunc { default void foo(); default void voo(); void run(); } 實現的測試類: Public class FunTest implements Func { public static void main(String... args) { Func func = new FunTest(); func.run(); func.foo(); func.voo(); } @Override public void run() { System.out.println("run"); } @Override public void foo() { System.out.println("foo"); } @Override public void voo() { System.out.println("voo"); } } 函數式接口的一大特性就是可以被lambda表達式和函數引用表達式代替。 public class FunTest { public static void main(String... args) { FunTest t = new FunTest(); //lambda t.test(10, ()->System.out.println("xxxxx")) //method reference t.test(100, t::customedFunc); } public void customedFunc(){ System.out.println("a customed method reference"); } public void test(int x, Func func) { System.out.println(x); func.run(); } } 上面例子列舉了一個lambda模式和一個方法引用模式,這樣就可以利用函數式編程強大的能力,將方法作為參數了。 下面再加一例 public class Main { public static void main(String[] args) { Action action = System.out :: println; action.execute("Hello World!"); test(System.out :: println, "Hello World!"); } static void test(Action action, String str) { action.execute(str); } } @FunctionalInterface interface Action<T> { public void execute(T t); }

Function

關於Function接口,其接口聲明是一個函數式接口,其抽象表達函數為

@FunctionalInterfacepublicinterfaceFunction<T,R>{Rapply(T t);...}

函數意為將參數T傳遞給一個函數,返回R。即R=Function(T)其默認實現了3個default方法,分別是compose、andThen和identity,對應的函數表達為:compose對應V=Function(ParamFunction(T)),體現嵌套關係;andThen對應V=ParamFunction(Function(T)),轉換了嵌套的順序;還有identity對應了一個傳遞自身的函數調用對應Function(T)=T。從這裡看出來,compose和andThen對於兩個函數f和g來說,f.compose(g)等價於g.andThen(f)。看個例子:

public static void lambdaFunction(){Function<Integer, Integer> incr1 = x->x*2; Function<Integer, Integer> multiply = x->x*2; int x=2; System.out.println("f(x)=x+1,when x="+x+", f(x)="+incr1.apply(x)); System.out.println("f(x)=x+1,g(x)=2x, when x="+x+",f(g(x))="+incr1.compose(multiply).apply(x)); System.out.println("f(x)=x+1,g(x)=2x, when x="+x+",g(f(x))="+incr1.andThen(multiply).apply(x)); System.out.println("compose vs andThen: f(g(x))="+incr1.compose(multiply).apply(x)+","+multiply.andThen(incr1).apply(x));}控制臺輸出:-------------------------------------------------------f(x)=x+1,when x=2, f(x)=4f(x)=x+1,g(x)=2x, when x=2,f(g(x))=8f(x)=x+1,g(x)=2x, when x=2,g(f(x))=8compose vs andThen: f(g(x))=8,8

高階函數

只是普通的lambda表達式,其能力有限。我們會希望引入更強大的函數能力——高階函數,可以定義任意同類計算的函數。

比如這個函數定義,參數是z,返回值是一個Function,這個Function本身又接受另一個參數y,返回z+y。於是我們可以根據這個函數,定義任意加法函數:

//high orderfunctionFunction<Integer,Function<Integer, Integer>> makeAdder = z->y->z+y;x=2;//defineadd1Function<Integer, Integer> add1 = makeAdder.apply(1);System.out.println("f(x)=x+1,when x="+x+",f(x)="+add1.apply(x));//define add5Function<Integer, Integer> add5 = makeAdder.apply(5);System.out.println("f(x)=x+5, when x="+x+", f(x)="+add5.apply(x));

由於高階函數接受一個函數作為參數,結果返回另一個函數,所以是典型的函數到函數的映射。BiFunction提供了二元函數的一個接口聲明,舉例來說:

//binary funBiFunction<Integer, Integer, Integer> multiply =(a,b)>a*b;System.out.println("f(z)=x*y, when x=3,y=5 when f(z)="+multiply,apply(3,5));

其輸出結果將是:f(z)=x*y, when x=3,y=5, then f(z)=15。二元函數沒有compose能力,只是默認實現了andThen。

有了一元和二元函數,那麼可以通過組合擴展出更多的函數可能。

Function接口相關的接口包括:

BiFunction :R apply(T t, U u);接受兩個參數,返回一個值,代表一個二元函數;DoubleFunction :R apply(double value);只處理double類型的一元函數;IntFunction :R apply(int value);只處理int參數的一元函數;LongFunction :R apply(long value);只處理long參數的一元函數;ToDoubleFunction:double applyAsDouble(T value);返回double的一元函數;ToDoubleBiFunction:double applyAsDouble(T t, U u);返回double的二元函數;ToIntFunction:int applyAsInt(T value);返回int的一元函數;ToIntBiFunction:int applyAsInt(T t, U u);返回int的二元函數;ToLongFunction:long applyAsLong(T value);返回long的一元函數;ToLongBiFunction:long applyAsLong(T t, U u);返回long的二元函數;DoubleToIntFunction:int applyAsInt(double value);接受double返回int的一元函數;DoubleToLongFunction:long applyAsLong(double value);接受double返回long的一元函數;IntToDoubleFunction:double applyAsDouble(int value);接受int返回double的一元函數;IntToLongFunction:long applyAsLong(int value);接受int返回long的一元函數;LongToDoubleFunction:double applyAsDouble(long value);接受long返回double的一元函數;LongToIntFunction:int applyAsInt(long value);接受long返回int的一元函數;Operator

Operator其實就是Function,函數有時候也叫作算子。算子在Java8中接口描述更像是函數的補充,和上面的很多類型映射型函數類似。算子Operator包括:UnaryOperator和BinaryOperator。分別對應單元算子和二元算子。算子的接口聲明如下:

@FunctionalInterfacepublicinterfaceUnaryOperator<T>extendsFunction<T,T>{static<T>UnaryOperator<T>identity(){return t->t;}}

二元算子的聲明:算子就是一個針對同類型輸入輸出的一個映射。在此接口下,只需聲明一個泛型參數T即可。對應上面的例子:

publicclassTestOperator{publicstaticvoidmain(String... args){UnaryOperator<Integer>add= x->x+1; System.out.println(add.apply(1));BinaryOperator<Integer> addxy =(x,y)->x+y; System.out.println(addxy.apply(3,5));BinaryOperator<Integer> min = BinaryOperator.minBy((o1,o2)->o1-o2); System.out.println(min.apply(100,200));BinaryOperator<Integer> max = BinaryOperator.maxBy((o1,o2)->o1-o2); System.out.println(max.apply(100,200));}}

例子裡補充一點的是,BinaryOperator提供了兩個默認的static快捷實現,幫助實現二元函數min(x,y)和max(x,y),使用時注意的是排序器可別傳反了:)

其他的Operator接口:(不解釋了)

LongUnaryOperator:long applyAsLong(long operand);IntUnaryOperator:int applyAsInt(int operand);DoubleUnaryOperator:double applyAsDouble(double operand);DoubleBinaryOperator:double applyAsDouble(double left, double right);IntBinaryOperator:int applyAsInt(int left, int right);LongBinaryOperator:long applyAsLong(long left, long right);Predicate

predicate是一個謂詞函數,主要作為一個謂詞演算推導真假值存在,其意義在於幫助開發一些返回bool值的Function。本質上也是一個單元函數接口,其抽象方法test接受一個泛型參數T,返回一個boolean值。等價於一個Function的boolean型返回值的子集。

@FunctionalInterfacepublicinterfacePredicate<T>{booleantest(T t);...}

其默認方法也封裝了and、or和negate邏輯。寫個小例子看看:

publicclassTestJ8Predicate{publicstaticvoidmain(String... args){TestJ8Predicate t =newTestJ8Predicate();t.pringBigValue(10, val->val>5);t.printBigValueAnd(10, val->val>5);t.printBigValueAnd(6, val->val>5);}publicvoidpringBigValue(intvalue, Predicate<Integer> predicate){if(predicate.test(value)){System.out.println(value);}}publicvoidprintBigValueAnd(intvalue, Predicate<Integer> predicate){if(predicate.and(v->v<8).test(value)){System.out.println("value < 8:"+value);}else{System.out.println("value should < 8 at least.");}}}

Predicate在Stream中有應用,Stream的filter方法就是接受Predicate作為入參的。這個具體在後面使用Stream的時候再分析深入。

其他Predicate接口:

BiPredicate:boolean test(T t, U u);接受兩個參數的二元謂詞DoublePredicate:boolean test(double value);入參為double的謂詞函數IntPredicate:boolean test(int value);入參為int的謂詞函數LongPredicate:boolean test(long value);入參為long的謂詞函數Consumer

看名字就可以想到,這像謂詞函數接口一樣,也是一個Function接口的特殊表達——接受一個泛型參數,不需要返回值的函數接口。

@FunctionalInterfacepublicinterfaceConsumer<T>{voidaccept(T t);...}

這個接口聲明太重要了,對於一些純粹consume型的函數,沒有Consumer的定義真無法被Function家族的函數接口表達。因為Function一定需要一個泛型參數作為返回值類型(當然不排除你使用Function來定義,但是一直返回一個無用的值)。比如下面的例子,如果沒有Consumer,類似的行為使用Function表達就一定需要一個返回值。

publicstaticvoidmain(String... args){Consumer<Integer> consumer = System.out::println; consumer.accept(100); //use function, you always need one return value. Function<Integer,Integer> function = x->{System.out.println(x);return x;}function.apply(100);}

其他Consumer接口:

BiConsumer:void accept(T t, U u);接受兩個參數DoubleConsumer:void accept(double value);接受一個double參數IntConsumer:void accept(int value);接受一個int參數LongConsumer:void accept(long value);接受一個long參數ObjDoubleConsumer:void accept(T t, double value);接受一個泛型參數一個double參數ObjIntConsumer:void accept(T t, int value);接受一個泛型參數一個int參數ObjLongConsumer:void accept(T t, long value);接受一個泛型參數一個long參數Supplier

其聲明如下:

@FunctionalInterfacepublicinterfaceSupplier<T>{Tget();...}

其簡潔的聲明,會讓人以為不是函數。這個抽象方法的聲明,同Consumer相反,是一個只聲明了返回值,不需要參數的函數(這還叫函數?)。也就是說Supplier其實表達的不是從一個參數空間到結果空間的映射能力,而是表達一種生成能力,因為我們常見的場景中不止是要consume(Consumer)或者是簡單的map(Function),還包括了new這個動作。而Supplier就表達了這種能力。

比如你要是返回一個常量,那可以使用類似的做法:

這保證supplier對象輸出的一直是1。

如果是要利用構造函數的能力呢?就可以這樣:

Supplier<TestJ8Supplier> anotherSupplier;for(int i=0; i<10; i++){anotherSupplier = TestJ8Supplier::new; System.out.println(anotherSupplier.get())}

這樣的輸出可以看到,全部的對象都是new出來的。

這樣的場景在Stream計算中會經常用到,具體在分析Java 8中Stream的時候再深入。

其他Supplier接口:BooleanSupplier:boolean getAsBoolean();返回booleanDoubleSupplier:double getAsDouble();返回doubleIntSupplier:int getAsInt();返回intLongSupplier:long getAsLong();返回long

相關焦點

  • 函數式編程簡介—Lambda 表達式
    Java作為面向對象的程式語言,如果按照編程種類劃分屬於命令式編程(Imperative Programming)。常見的編程範式還有邏輯式編程(Logic Programming),函數式編程(Functional Programming)。
  • 函數式編程
    來源:酷殼網-陳皓 連結:https://coolshell.cn/articles/10822.html當我們說起函數式編程來說,我們會看到如下函數式編程的長相:函數式編程的三大特性函數式編程的幾個技術map & reduce:這個技術不用多說了,函數式編程最常見的技術就是對一個集合做Map和Reduce操作。這比起過程式的語言來說,在代碼上要更容易閱讀。
  • 函數式編程那些事兒
    函數式編程是一種編程範式,在其中它試圖將每個函數都綁定到純數學函數中。這是一種聲明式的編程風格,著重於解決什麼而不是如何解決。Clojure,Common Lisp,Erlang,Haskell和Scala是遵循函數式編程方法的一些著名程式語言。編程範例基於lambda演算,下面簡要說明:Lambda演算它使用表達式來代替語句。
  • Java如何支持函數式編程?
    Java是面向對象的語言,無法直接調用一個函數。Java 8開始,引入了函數式編程接口與Lambda表達式,便於開發者寫出更少更優雅的代碼。什麼是函數式編程?函數式編程的特點是什麼?函數式編程並非一個很新的東西,早在50多年前就已經出現了。近幾年,函數式編程越來越被人關注,出現了很多新的函數式程式語言,比如Clojure、Scala、Erlang等。一些非函數式程式語言也加入了很多特性、語法、類庫來支持函數式編程,比如Java、Python、Ruby、JavaScript等。除此之外,Google Guava也有對函數式編程的增強功能。
  • 函數式編程/lambda表達式入門
    函數式編程/lambda表達式入門本篇主要講解 lambda表達式的入門,涉及為什麼使用函數式編程,以及jdk8提供的函數式接口 和 接口的默認方法 等等1.什麼是命令式編程命令式編程就是我們去告訴程序如何實現
  • 大數據入門:Scala函數式編程
    提到Scala,首先會提到的一個概念,就是函數式編程,這也是Scala語言區別與其他程式語言的典型特徵。Scala是一門多範式(multi-paradigm)的程式語言,設計初衷是要集成面向對象編程和函數式編程的各種特性。
  • 程式設計師為何與函數式編程「墜入愛河」?
    簡單來說,函數式編程就是為不可變變量構建函數。與之相反,面向對象的編程則是有一組相對固定的函數,而用戶主要是修改或添加新變量。由於函數式編程的特性,它非常適合完成諸如數據分析和機器學習之類的需求任務。但是這並不意味著用戶要告別面向對象的編程,轉而完全使用函數式編程。但用戶需要了解其基本原理,以便在適當的時候使用它們以發揮優勢。
  • 為什麼函數式編程在Java中很危險?
    在我的日常工作中,我身邊的開發者大多是畢業於CS編程頂級院校比如MIT、CMU以及Chicago,他們初次涉及的語言是Haskell、Scheme及Lisp。他們認為函數式編程是一種自然的、直觀的、美麗的且高效的編程樣式。
  • Java8新特性探索之函數式接口
    作為Java函數式編程愛好者,我們都知道方法引用和 Lambda 表達式都必須被賦值,同時賦值需要類型信息才能使編譯器保證類型的正確性。為了解決上述問題,Java 8 引入了函數式接口,在 java.util.function 包,它包含一組接口,這些接口是 Lambda 表達式和方法引用的目標類型,每個接口只包含一個抽象方法,稱為函數式方法。
  • 函數式編程是未來的最佳編碼範例嗎?
    簡而言之,函數式編程需要為固定變量構建純函數並通過其響應更改狀態。 相反,其他程式語言具有通過更改程序中變量引用來更改應用程式狀態的能力。 您可以在本文中了解功能編程與面向對象程序的更多區別。由於其純粹的性質,函數式編程對於令人垂涎的任務(如機器學習和數據分析)印象深刻。
  • javascript函數式編程基礎指北
    JavaScript 作為一種典型的多範式程式語言,這兩年隨著React\vue的火熱,函數式編程的概念也開始流行起來,lodashJS、folktale等多種開源庫都使用了函數式的特性。程序的本質是:根據輸入通過某種運算得到輸出函數式編程(Functional programming)是一種編程思想/範式 ,其核心思想是將運算過程抽象成函數(指數學的函數而不是程序的方法或函數即純函數),也就是面向函數編程,描述函數/數據 之間的映射,做到最大程度的復用; 學習函數式編程真正的意義在於讓你認識到一種用函數的角度去抽象問題的思路
  • Java新特性之-函數式編程
    面向對象編程範式的優點在於其抽象方式與現實中的概念比較相近。比如,學生、課程、汽車和訂單等這些現實中的概念,在抽象成相應的類之後,我們很容易就能理解類之間的關聯關係。這些類中所包含的屬性和方法可以很直觀地設計出來。函數式編程範式則相對較難理解。這主要是由於函數所代表的是抽象的計算,而不是具體的實體。因此比較難通過類比的方式來理解。
  • 面試官:java8中parallelStream提升數倍查詢效率是怎樣實現的
    的parallelStream(並行流),這裡為什麼不用Thread呢,這裡有一個注意點,我們需要獲取所有所有子任務執行完的時間點,在這個時間點之後才能將結果封裝返回,Thread沒有辦法滿足,這裡parallelStream和函數式接口就登場了。
  • 這樣理解 Java 中的函數式編程就對了
    在眾多的編程範式中,大多數開發人員比較熟悉的是面向對象編程範式。一方面是由於面向對象程式語言比較流行,與之相關的資源比較豐富,比如Java,c++等。函數式編程範式則相對較難理解。這主要是由於函數所代表的是抽象的計算,而不是具體的實體。因此比較難通過類比的方式來理解。
  • go 學習筆記之學習函數式編程前不要忘了函數基礎
    生物學家會下意識對動植物進行分類歸納,面向對象編程也是如此,用一系列的抽象模型去模擬現實世界的行為規律.數學家向來以嚴謹求學著稱,作為最重要的基礎科學,數學規律以及歸納演繹方法論對應的就是函數式編程,不是模擬現實而是描述規律更有可能創造規律.標準的函數式編程具有濃厚的數學色彩,幸運的是,Go 並不是函數式語言,所以也不必受限於近乎苛責般的條條框框.
  • 從Python到Haskell:程式設計師為何與函數式編程「墜入愛河」?
    不僅是Java或Python這樣的語言越來越多地採用了函數式編程的概念,類似Haskell這樣的新語言也正在完全實現函數式編程。簡單來說,函數式編程就是為不可變變量構建函數。與之相反,面向對象的編程則是有一組相對固定的函數,而用戶主要是修改或添加新變量。由於函數式編程的特性,它非常適合完成諸如數據分析和機器學習之類的需求任務。
  • 致開發人員:沉迷面向對象編程不可自拔?函數式編程了解一下
    函數式編程已經存在了60多年,但是到目前為止,它一直都很小眾。只有像Google這樣的改變遊戲規則的企業才會依賴函數式編程,普通程式設計師對此幾乎一無所知。這裡有一個容易發現副作用的簡單規則:由於每個函數必須具有某種輸入和輸出,因此沒有任何輸入或輸出的函數聲明必須是不純的。如果採用函數式編程,這是你可能想要更改的第一個聲明。函數式編程不僅是 map 和 reduce循環不是函數式編程中的東西。
  • 面試被虐題:說說 JVM 系語言的函數式編程
    >函數式編程在上世紀五十年代就有了,只不過在工業界一直不溫不火,最近十年才被廣泛認知。其理論基礎也並非為編程而設計,而是一種數學抽象(Lamda演算),其實初中就學過了,λ表達式。在 JS(建議把 JS 作為函數式編程思想學習的入門語言,Java 的實現略顯臃腫,可能不太便於理解)當中,函數式編程算是應用比較多的了。各現代高級程式語言,都或多或少地支持了函數式編程。
  • Swift 不是多範式函數式程式語言
    但是面向對象編程(OOP)只是思考問題的一種方式。函數式編程是思考問題的另一種方式。函數式編程主要是將問題分解為接受並返回不可變值的函數。它通常的結構是一些將值轉換為其他值的函數,以及各種組合函數的方法的集合。它避免了可變狀態,並且不要求函數的求值以任何特定的順序進行。函數式編程將程序視為一個數學問題,而不是一系列操作。