この記事で書くこと
この記事では,Nimで提供されている高階関数(higher-order function)を紹介し,Pythonと比較します.Nimの書き方のTips的なのも含まれます.
Nimってなに?
効率的、表現力豊か、エレガント
Nimは、静的に型指定されたコンパイル済みシステムプログラミング言語です。 Python、Ada、Modulaなどの成熟した言語の成功した概念を組み合わせています。
(引用:https://nim-lang.org/ with Google翻訳)
要はPythonのような構文で,C言語なみに速いプログラミング言語です.普段Pythonを書きながらも速度に不満を持っている僕には夢のような言語であるわけです.
高階関数ってなに?
高階関数とは,関数を引数に取る関数のことです.
古くはLISPやML系の言語で用いられていましたが,近年の関数型言語の隆盛によって,多くのプログラミング言語にも実装されてきています.例えば,Pythonなどのスクリプト言語や,最近ではJavaScript(ECMAScript 2015から)にも実装されています.RustやGoでも書けるらしいけどあんま詳しくないので割愛.
Nimの高階関数
Nimのsequtilモジュールで実装されている高階関数とそのテンプレートを紹介します.
map
proc map[T, S](s: openArray[T]; op: proc (x: T): S {...}): seq[S] {...}
map
はopenArray
型の配列s
とプロシージャop
を引数に持ち,s
の各要素にop
を適用したseq
を返します.openArray
とは配列を示す汎用的な型で,sequences
,string
,array
が含まれます.
import sequtils
let # これはimmutableな変数宣言
s = [1,2,3,4] # これはArray型
op = proc (x: int): int = x * x # 無名関数,引数の2乗を返す関数
echo s.map(op) # => @[1, 4, 9, 16] ## sの各要素が2乗されている
echo s # => [1, 2, 3, 4] ## 元の配列は変わらない
echo s.map(op) == map(s, op) # => true
## NimのTips(プロシージャの呼び方)
## p(x)とx.p() は等価
apply
proc apply[T](s: var openArray[T]; op: proc (x: T): T {...}) {...}
apply
はopenArray
型の配列s
とプロシージャop
を引数に持ち,s
の各要素にop
を適用します.map
と似ていますが,apply
は戻り値をもたず,s
を直接変更します.じつはもう一つapplyがありますが省略.
import sugar # 実験段階のモジュール
var # mutableな変数宣言
x = [true, false, false, true, true]
echo x #=> [true, false, false, true, true]
apply(x, b => not b) # xの各要素のnotをとる
echo x #=> [false, true, true, false, false]
## NimのTips(無名関数の書き方)
## b => not bは無名関数の省略記法.左辺が引数,右辺が戻り値
## ただしimport sugarが必要
filter
proc filter[T](s: openArray[T]; pred: proc (x: T): bool {...}): seq[T] {...}
filter
はopenArray
型の配列s
とプロシージャpred
を引数に持ち,pred
がtrue
を返すs
の要素で構成されるseq
を返します.
import strutils
let
str = "happY holidAYs"
upperStr = str.filter(s => s.isUpperAscii()) # 大文字をフィルタ
echo str #=> "happY holidAYs"
echo upperStr #=> @['Y', 'A', 'Y']
echo upperStr.join("") #=> "YAY" ## string型の配列の要素を結合 (Pythonと同じ)
## NimのTips(命名)
## Nimの変数,プロシージャはcamelCaseでの宣言が推奨されています.
keepIf
proc keepIf[T](s: var seq[T]; pred: proc (x: T): bool {...}) {...}
keepIf
はseq
型のs
とプロシージャpred
を引数に持ち,pred
がtrue
を返す要素のみをs
に残します.ただし,引数がopenArray
でなくseq
であることに注意.
便利なテンプレート(mapIt, applyIt, filterIt, keepItIf)
これらはそれぞれmap, apply, filter, keepIf
のテンプレートです.これらを使えば(少しだけ)簡単に高階関数を呼び出せます.使い方はどれもほとんど同じなので,mapIt
の例を紹介します.
let
v = [1,2,3,4,5]
w = v.mapIt(it*2) # itはvの各要素,これは各要素を2倍にするという意味
echo w # => @[2, 4, 6, 8, 10]
ポイント
-
mapIt
(その他も同様)の引数は配列と式 - テンプレート内の
it
は引数の配列の各要素 - 戻り値はもとの関数と同様に
seq
Pythonとの比較
Pythonにもmap, filter, reduce (foldl, foldrに当たる) が実装されていますが,applyやkeepIfに相当するものはありません(pandasにはある).そのため,リストl
にmap
をかけた後のリストを使いたい場合は,
l = [1, 2, 3, 4] # 各値を二倍したい
mapped_l = map(lambda x: x*2, l) # mapped_lはlistではない
assert type(mapped_l) is map # => true
mapped_l = list(mapped_l) # listに戻す
としなければなりません.map
の戻り値の型はmap object
で引数の方とは異なります.これはジェネレータなので,元の型として使いたい場合は明示的に戻さなければなりません.これはfilter
でも同様でfilter
の戻り値はfilter object
です.例えば,
l = [-3, -2, -1, 0, 1, 2, 3]
p = filter(lambda x: x>0, l) # 正の要素だけ残す
len(p) # pはfilter objectなのでエラー
len(list(p)) # これは大丈夫.答えは3
len([li for li in l if li > 0]) # これも大丈夫.こっちのほうがPythonらしい書き方???
これが個人的にあまり好きではありません.Nimの場合はl.map(proc ...)
の戻り値の型はseq
なので,このような処理をする必要がないため使いやすいです(個人的な感想).
おわりに
Nimの高階関数についてまとめてみました.Nimはまだまだ発展途上の言語ですが,これからのさらなる発展を願っています.すこしでも皆様の参考になれば幸いです