#どんな記事?
ふとコードを書いてて思ったのですが,使用するプログラミング言語によってコードの性質が変わってくるってお話です.ただの学生が書く話なので,細かいところに関しては目をつぶっていただけるとありがたいです笑.
また,この記事はパラダイムについて詳しく説明する記事ではなく,具体的な言語でコーディングしてみると違いがあるね,と確認する記事なので本格的にパラダイムを勉強したい方は他のページを御覧ください.
#言語ごとの考え方の違い
たとえば,引数にリストを取ってその中から奇数のもののみからなるリストを抽出することを考えます.
Javaだとこんな感じにかくのかなと思います.
public class ListConverter{
private List<Integer> list;
public ListConverter(List<Integer> list) {
this.list = new ArrayList<Integer> (list);
}
public List<Integer> makeOddList() {
List<Integer> result = new ArrayList<Integer>();
for(Integer i : list) {
if(i % 2 == 1) result.add(i);
}
return result;
}
}
Java8のStream使えよって感じですが,その場合こんな感じ.
public class ListConverter{
public List<Integer> list;
public ListConverter(List<Integer> list) {
this.list = new ArrayList<Integer> (list);
}
public List<Integer> makeOddList() {
List<Integer> res = list.stream().
filter(i -> (i % 2 == 1)).collect(Collectors.toList());
return res;
}
}
それでこれらのコードに共通してるのが,引数のリストを破壊せずに新しいリストを返していることです.これはJava(やpythonなどの言語)にはGCが搭載されているので,メソッド内でnewされたインスタンスを明示的にfreeしなくていいからこんな風に書けますね.さらに,ListConverterクラスにリストを抽出してもらうという,オブジェクトに〜してもらうという感覚がオブジェクト指向っぽいのかなと思います.
一方,CやC++の場合,
void make_odd_list(const std::vector<int> &vec, std::vector<int> &res){
for(auto i : vec){
if(i % 2 == 1) res.push_back(i);
}
}
こんな感じにあらかじめ結果を格納するオブジェクトを引数として受け取るなり,vecに上書きするなりする場合があります.これは関数内で新しくインスタンスを作成してそれを返すと,このインスタンスをfreeするのは呼び出し側の責任となり,呼び出し側は自分でnewしていないインスタンスに対してfreeを行うことになります.(なお,C++でもスマートポインタの機能を使えば,自動的にfreeを行うことができますが,あくまで一例としての話です)
さらに,関数型の言語Haskellでは,
makeOddList:: [Int] -> [Int]
makeOddList [] = []
makeOddList (x:xs)
| (x `mod` 2 == 1) = x : makeOddList xs
| otherwise = makeOddList xs
こんな感じになります.Haskellは再代入が禁止されているので,引数を破壊するということが起こりえません.また,Haskellは遅延評価なので,再帰のコードに対してメモリ使用量が抑えられ(ることがあり)ますがCやJavaで再帰を使いすぎるとメモリ使用量が不安になりますよね.
こんな風に単なるリスト操作でも各言語ごとにコーディングの違いがでてきておもしろいですね.
#まとめ
上で示したコードはあくまで一例ですが,使用するプログラミング言語ごとにコードの書き方に差が出る可能性があることがわかるかと思います.
コードを書くときに,言語や求められている状況にあったコーディングを意識したいですね.