Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Excerpt
hiddentrue

Functional programming is a style of programming that emphasizes the application of functions to solve computing problems. This is in contrast with imperative programming, which emphasizes changes in state and the execution of sequential commands. If you want use a functional-only programming language, you should consider something like Haskell. If however you like Groovy but want to apply some functional style magic, read on.


関数型プログラミングはコンピュータで問題解決するために関数の機能の活用を強調するプログラミングスタイルです。これは状態変化を強調し、逐次コマンドを実行する命令型プログラミングとは対照的です。もしあなたが関数のみのプログラミング言語を利用したいのであれば、Haskellを検討してみてはいかがでしょうか。しかしそれでもGroovyが好きで、少しでも関数スタイルの魔法を適用させたいのであれば、読み進めて下さい。

Excerpt
hiddentrue

Functional Basics

関数型の基礎

Excerpt
hiddentrue

Groovy's functions (like Java's) can be used to define functions which contain no imperative steps, e.g. a factorial function may look like:


Groovyの関数(Javaのような) は命令ステップを一切含まぬよう宣言することができます。例えばこのような関数です。

Code Block
def fac(n) { n == 0 ? 1 : n * fac(n - 1) }
assert 24 == fac(4)

Excerpt
hiddentrue

In Groovy, we gain some slight syntactic sugar over Java in that we can leave out the return statements (the last evaluated expression is the default return value).


Groovyでは、 return 文を省略できる(最後に評価された式がデフォルトの戻り値となります)という微量のシンタクスシュガーがJavaに加えられています。

Excerpt
hiddentrue

You can also define the above factorial function using a Closure:


上記の階乗関数はクロージャを使って宣言することもできます。

Code Block
def fac2 = { n -> n == 0 ? 1 : n * call(n - 1) }
assert 24 == fac2(4)

Excerpt
hiddentrue

As you can see from the last line, this Closure version gets called in the same way as the function version (although a long hand fac2.call(4) is also supported). The closure version has the advantage that we can pass the fac2 Closure around (e.g. as a parameter) or place it within a data structure.


最後の行に見えるように、このクロージャ版はメソッドと同様の方法で呼び出されています(ですが、 fac2.call(4) という長い書き方もサポートされています)。クロージャ版は fac2 をClosureパラメータとして渡したり、データ構造に埋め込んだりできるといった利点があります。

Excerpt
hiddentrue

We can also convert between functions and Closures, e.g.


関数とクロージャは変換することもできます。例えば、

Code Block
def fac3 = this.&fac
assert 24 == fac3(4)

Excerpt
hiddentrue

The fac3 variable will also be a closure.


fac3 変数もまたクロージャとなります。

Excerpt
hiddentrue

We can of course start to mix and match functional and imperative coding styles as in this quicksort-like example:


もちろん次のクイックソートの例のように関数型と命令型のコーディングスタイルを混ぜて調和させることから始めることもできます。

Code Block
def sort(list) {
    if (list.isEmpty()) return list
    anItem = list[0]
    def smallerItems = list.findAll{it < anItem}
    def equalItems = list.findAll{it == anItem}
    def largerItems = list.findAll{it > anItem}
    sort(smallerItems) + equalItems + sort(largerItems)
}

assert [1, 3, 4, 5] == sort([1, 4, 5, 3])
assert [1, 1, 3, 4, 4, 5, 8] == sort([4, 1, 4, 1, 8, 5, 3])
assert ['a', 'b', 'c'] == sort(['c', 'b', 'a'])

Excerpt
hiddentrue

Curry functions

カリー関数

Excerpt
hiddentrue

You can fix the values for one or more arguments to a closure instance using the curry() method as follows:


次のようにクロージャのインスタンスに対してcurry()メソッドを使うことで一つ以上の引数の値を固定することができます。

Code Block
def joinTwoWordsWithSymbol = { symbol, first, second -> first + symbol + second }
assert joinTwoWordsWithSymbol('#', 'Hello', 'World') == 'Hello#World'

def concatWords = joinTwoWordsWithSymbol.curry(' ')
assert concatWords('Hello', 'World') == 'Hello World'

def prependHello = concatWords.curry('Hello')
// def prependHello = joinTwoWordsWithSymbol.curry(' ', 'Hello')
assert prependHello('World') == 'Hello World'

Excerpt
hiddentrue

If you supply one argument to the curry() method you will fix the first argument. If you supply N arguments you will fix arguments 1..N. See reference 1 or Chapter 5 of GINA for further details.


もし、 curry() メソッドに引数を一つ与えると、最初の引数を固定します。もし、引数を N 個与えると、 1からN の引数を固定します。詳細はGINAの1章、もしくは5章を参照して下さい。

Excerpt
hiddentrue

Lazy evaluation

遅延評価

Excerpt
hiddentrue

One particular style of functional programming of particular merit is to make use of lazy evaluation. This allows you to define infinite structures (see the next section), devise particularly efficient solutions to certain kinds of problems, and come up with very elegant solutions to otherwise hard problems. The good news is that several parts of Groovy already make use of this style and they typically hide away the hard bits so you don't need to know what magic is happening on your behalf. Here's some examples:


関数型プログラミングを利用するメリットの一つは遅延評価を使用することです。これは無限の構造の宣言を許可し(次の章をご覧下さい)、いくつかの問題の特に効果的な解決法をひねり出したり、別のとてもエレガントな解決法を思いつかせてくれます。幸いなことに、Groovyはこれらのいくつかをスタイルを既に使うことができ、典型的な難しい部分は包み隠してくれるので、あなたの代わりにどんな魔法が裏で使われているのかを知る必要がありません。以下はいくつかの例です。

Excerpt
hiddentrue
  • XmlSlurper allows arbitrary GPath expressions to be crafted. As you create the expressions, you might think that XML parsing is going on behind the covers pulling XML nodes into and out of lists to match what your expressions are asking for. This is not the case. Instead a lazy representation of your GPath is stored away. When you need to evaluate the final result of a GPath expression, it calculates just what it needs to determine the expressions resulting value. [See chapter 12 of GINA for more information about XmlSlurper.]

XmlSlurper は任意のGPath式が作られることを許容します。あなたは式を作ると、XMLノードをpullしてあなたの式が求めるものにマッチするリストに出し入れしているその裏側では、XMLパースが継続していると思うかもしれません。でもそれは違います。代わりに、あなたのGPathの遅延した評価が蓄えられているのです。最終的なGPathの結果を評価する必要が出たときに、式の結果の値を決定するために必要なものを計算します。(XmlSlurperに関す る更なる情報にはGINAの12章を参照してください。)

Excerpt
hiddentrue
  • Groovy's DataSet feature does the some thing for data stored in relational databases. As you build up your dataset queries, no connections or operations to the database are happening under the covers. At the time when you need the result, an optimised query minimising SQL traffic is invoked to return the required result. [See section 10.2 of GINA for more information about DataSets.]

Groovyの DataSet 機能はRDBに格納されたデータを操作します。 datasetクエリーを作っても、裏では何も接続も操作も発生しません。要求された結果を返すためSQLのトラフィックが最小になるように最適化されたクエリーが、結果が必要になったときに初めて実行されます。 [DataSetに関する更なる情報にはGINAの10.2項を参照してください。]

Excerpt
hiddentrue

Infinite structures

無限の構造

Excerpt
hiddentrue

See reference 2 below for all the details, but to give you a flavour, first you must define some lazy list handling functions, then you can define and use infinite streams. Here is an example:


すべての詳細には下の参考資料 2.を見てください。付け加えて言うなら、まず最初にいくつかのListをハンドリングする関数を定義しなければなりません。それから際限のないストリームを定義して使うことができます。

Code Block
// general purpose lazy list class
class LazyList {
    def car
    private Closure cdr
    LazyList(def car, Closure cdr) { this.car=car; this.cdr=cdr }
    def LazyList getCdr() { cdr ? cdr.call() : null }
    def List take(n) {
        def r = []; def l = this
        n.times { r.add(l.car); l = l.cdr }
        r
    }
    def LazyList filter(Closure pred) {
        if (pred(car)) return pred.owner.cons(car, { getCdr().filter(pred) })
        else return getCdr().filter(pred)
    }
}

// general purpose lazy list function
def cons(val, Closure c) { new LazyList(val, c) }

// now define and use infinite streams
def integers(n) { cons(n, { integers(n+1) }) }
def naturalnumbers = integers(1)
assert '1 2 3 4 5 6 7 8 9 10' == naturalnumbers.take(10).join(' ')
def evennumbers = naturalnumbers.filter{ it % 2 == 0 }
assert '2 4 6 8 10 12 14 16 18 20' == evennumbers.take(10).join(' ')

Excerpt
hiddentrue

If you are not familiar with traditional functional programming terms like cons, car and cdr, you may find the refactored version below (creds to Alexander Kriegisch) somewhat more readable:


もしあなたが、cons, car, cdr のような伝統的な関数型プログラミングの用語に慣れ親しんでいないなら、リファクタリングして少し読みやすくしたバージョン(Alexander Kriegisch)はいかがでしょうか。

Code Block
class LazyList {
    def head
    private Closure tail
    LazyList(def head, Closure tail) { this.head=head; this.tail=tail }
    def LazyList getTail() { tail ? tail.call() : null }
    def List getFirst(n) {
        def result = []; def current = this
        n.times { result.add(current.head); current = current.tail }
        result
    }
    def LazyList filter(Closure matchExpr) {
        if (matchExpr(head))
            return matchExpr.owner.prepend(head, { getTail().filter(matchExpr) })
        else
            return getTail().filter(matchExpr)
    }
}
 
// general purpose lazy list function
def prepend(val, Closure c) { new LazyList(val, c) }
 
// now define and use infinite streams
def integers(n) { prepend(n, { integers(n+1) }) }
def naturalnumbers = integers(1)
assert '1 2 3 4 5 6 7 8 9 10' == naturalnumbers.getFirst(10).join(' ')
def evennumbers = naturalnumbers.filter{ it % 2 == 0 }
assert '2 4 6 8 10 12 14 16 18 20' == evennumbers.getFirst(10).join(' ')

Excerpt
hiddentrue

Using Functional Java

Functional Javaを使用する

Excerpt
hiddentrue

You can also consider using Java libraries which support functional programming, e.g. using Functional Java for an example similar to the evens example above:


関数型プログラミングをサポートしているJavaライブラリの利用も考慮に入れることができます。例えば、Functional Javaを使用して、上記で示した偶数の例はこのようになります。

Code Block
import fj.data.Stream

// some metaprogramming to make fj mesh well with Groovy
Stream.metaClass.filter = { Closure c -> delegate.filter(c as fj.F) }
Stream.metaClass.getAt = { n -> delegate.index(n) }
Stream.metaClass.getAt = { Range r -> r.collect{ delegate.index(it) } }
Stream.metaClass.asList = { delegate.toCollection().asList() }

def evens = Stream.range(0).filter{ it % 2 == 0 }
assert evens.take(5).asList() == [0, 2, 4, 6, 8]
assert (8..12).collect{ evens[it] } == [16, 18, 20, 22, 24]
assert evens[3..6] == [6, 8, 10, 12]

Excerpt
hiddentrue

Or for something a little more adventurous, we can consider calculating primes:


もうちょっとだけ大胆にしてみましょう、素数の計算を考えます。

Code Block
import fj.data.Stream
import static fj.data.Stream.cons

// some metaprogramming to make Closures mesh well with fj functions and products
Stream.metaClass.filterTail = { Closure c -> delegate.tail()._1().filter(c as fj.F) }
Stream.metaClass.static.cons = { h, Closure c -> delegate.cons(h, ['_1':c] as fj.P1) }

def sieve(nums) {
  cons nums.head(), { sieve nums.filterTail{ n -> n % nums.head() != 0 } }
}

println sieve(Stream.range(2)).index(100) // => 547

Excerpt
hiddentrue

Remember to add functionaljava.jar to your classpath. You will need a version greater than version 2.8 of Functional Java.


functionaljava.jarをクラスパスに追加するのを忘れないようにして下さい。バージョン2.8以上のFunctional Javaが必要です。

Excerpt
hiddentrue

Another example:


もう一つの例。

Code Block
@Grab('org.functionaljava:functionaljava:3.0')
import static fj.data.Array.array
import static fj.data.List.list
import static fj.Show.*
import fj.F

arrayShow(intShow).println(array(1, 2, 3).map({ it + 42 } as F)) // => {43,44,45}
assert array(1, 2, 3).map({ it + 42 } as F).toCollection().toList() == [43, 44, 45]
assert [1, 2, 3].collect{ it + 42 } == [43, 44, 45] // native Groovy for comparison

listShow(intShow).println(list(1, 2, 3).bind({ it % 2 ? list(it * 2) : list() } as F)) // => <2,6>
assert list(1, 2, 3).bind({ it % 2 ? list(it * 2) : list() } as F).toCollection().toList() == [2, 6]
assert [1, 2, 3].collect{ it % 2 ? [it * 2] : [] }.sum() == [2, 6] // native Groovy for comparison

Excerpt
hiddentrue

Or with a little bit of metaprogramming like so:


もう少しメタプログラミングっぽく。

Code Block
import static fj.data.Java.ArrayList_List as asList
fj.data.List.metaClass.toList = { delegate.toCollection().toList() }
List.metaClass.bind = { c -> asList().f(delegate).bind({ asList().f(c(it)) } as F) }
List.metaClass.map = { c -> asList().f(delegate).map(c as F) }

Excerpt
hiddentrue

The above examples can be re-written like this:


上記の例はこのように書き直すことができます。

Code Block
assert [1, 2, 3].bind{ it % 2 ? [it * 2] : [] }.toList() == [2, 6]
assert [1, 2, 3].map{ it + 42 }.toList() == [43, 44, 45]

Excerpt
hiddentrue

More Information

詳細情報

Excerpt
hiddentrue

See also:


こちらも参照してください。

  1. Practically Groovy: Functional programming with curried closures
  2. Infinite Streams in Groovy
  3. Functional Programming Languages
  4. Why Functional Programming Matters
  5. Functional programming in the Java language
  6. Post on functional programming in Java - maybe a tad verbose
  7. FunctionalJ - A library for Functional Programming in Java
  8. Weak versus strong languages, who wins the fight?
  9. Programming Languages:Application and Interpretation
  10. Beyond Groovy 1.0: Groovy goes Lisp
  11. Concurrency with Groovy