{ }codelper

学ぶ › JavaScript

関数 · 15

関数をもう一歩

変数のスコープ、複数の戻り値、関数を値として渡す。

関数をもう一歩使いこなすための三つです:変数がどこにいるか(スコープ)、値を二つ返す方法、関数を値のように渡す方法。
下の例は、数の中からいちばん小さい値といちばん大きい値を一度に受け取り、条件の関数を渡して偶数と 10 以上の数を数え、条件を全部で何回確かめたかを関数の外の変数に記録します。

let checks = 0;   // 함수 밖 변수: 프로그램이 끝날 때까지 남아요 function isEven(x) {  return x % 2 === 0;} function isBig(x) {  return x >= 10;} function minMax(nums) {  let lo = nums[0], hi = nums[0];  for (const x of nums) {    lo = Math.min(lo, x);    hi = Math.max(hi, x);  }  return [lo, hi];   // 두 값을 배열에 담아 돌려줘요} function countIf(nums, test) {   // test에는 함수가 들어와요  let count = 0;   // 함수 안 변수: 부를 때마다 새로 만들어져요  for (const x of nums) {    checks++;      // 밖의 변수를 바꿔요    if (test(x)) count++;  }  return count;} const nums = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [lo, hi] = minMax(nums);console.log(lo + " " + hi);console.log(countIf(nums, isEven));console.log(countIf(nums, isBig));console.log(checks);
stdin
4 15 8 23 6 7
stdout
4 23
3
2
12

ポイント

  • 変数にはいる場所(スコープ)があります。
    関数の中で作った変数はその関数の中だけで使え、呼ぶたびに新しく作られます。
    例の count は呼ぶたびに 0 から始まります。
  • 関数の外で作った変数は、プログラムが終わるまで残ります。
    例の checks は関数を2回呼ぶあいだ値が続いて、12 になります。
  • 関数の中から外の変数を変える方法は、言語ごとに違います。
    外の変数をあちこちで変えると、どこで変わったのか探しにくくなります。
    そこで、例の checks のようにプログラム全体で一つだけの記録にだけ使い、必要な値は引数で受け取り、count のような計算結果は return で返します。
  • 値を二つ返すには、一つのまとまりにして返し、受け取る側でまた分けます。
    まとめる入れ物(タプル、配列、pair、リスト)は言語ごとに違います。
    C は結果を書き込むアドレスを受け取ります。
  • 関数も値なので、ほかの関数に渡すことができます。
    例は数える関数一つで「偶数を数える」と「10 以上を数える」の両方を行います。
    違う部分(条件)だけを関数にして渡します。
  • 渡すときはかっこを付けずに名前だけを書きます。
    is_even は関数そのもので、is_even(4) は関数を呼んだ結果です。

言語ごとの書き方

Python関数の中で外の変数を読むだけならそのまま使い、変えるときは関数の先頭に global checks と書きます。
二つの値は return lo, hi(タプル)で返し、lo, hi = min_max(nums) で受け取ります。
関数は count_if(nums, is_even) のように名前だけを渡し、短い条件は lambda x: x >= 10 でその場で作ります
JavaScript関数の外で let で作った変数は、関数の中で目印なしに読み書きできます。
関数の中で同じ名前をもう一度 let で作ると、外のものとは別の変数になります。
二つの値は return [lo, hi]; で返し、const [lo, hi] = minMax(nums); で受け取ります。
関数は countIf(nums, isEven) のように渡し、短い条件はアロー関数 (x) => x >= 10
TypeScriptJavaScript と同じです。
二つの値を返す型は [number, number]、関数を受け取る引数は test: (x: number) => boolean のように書きます
Javaクラスの外には変数を置けないので、関数の外の変数はクラスの static フィールドにします(static int checks = 0;)。
二つの値は int[] に入れるか、record MinMax(int lo, int hi) {} を作って返します。
関数は IntPredicate test で受け取って test.test(x) で呼び、渡すときは Main::isEven やラムダ x -> x >= 10。
ラムダの中で main のローカル変数を変えるとコンパイルエラーです
C#クラスの外には変数を置けないので、関数の外の変数はクラスの static フィールドにします。
二つの値はタプル (int, int) で return (lo, hi);、受け取るときは var (lo, hi) = MinMax(nums);。
out 引数(out int lo)で受け取る方法もあります。
関数は Func<int, bool> test で受け取り、渡すときは IsEven やラムダ x => x >= 10
C++関数の外に書いた変数はグローバル変数で、どの関数からでも読み書きできます。
二つの値は std::pair<int, int> で return {lo, hi};、受け取るときは auto [lo, hi] = minMax(nums);。
関数は std::function<bool(int)>(<functional>)で受け取り、渡すときは isEven やラムダ [](int x) { return x >= 10; }
C関数の外に書いた変数はグローバル変数です。
値を二つ返す文法がないので、例のように結果を書き込むアドレスを受け取るか(int *lo, int *hi、呼ぶときは &lo, &hi)、構造体に入れて返します。
関数は関数ポインタ int (*test)(int) で受け取り、count_if(nums, n, is_even) のように名前を渡します。
ラムダはありません
Craykonいちばん上で作った変数は関数の中でも同じ変数なので、目印なしに読み書きできます。
関数の中で初めて作った変数は、その関数の中だけで使えます。
二つの値は 返す [lo, hi]; のようにリストに入れて返し、r[0]、r[1] で取り出します。
ラムダがないので、名前のある関数を countIf(nums, isEven) のように渡します

よくある間違い

return lo, hi; で二つの値を返そうとしたJavaScript · C · C++

function minMax(nums) {
  let lo = nums[0], hi = nums[0];
  for (const x of nums) { lo = Math.min(lo, x); hi = Math.max(hi, x); }
  return lo, hi;
}
console.log(minMax([4, 15, 8]));

→ 15 — カンマ演算子は後ろの値だけを残すので、hi だけが返ります。C・C++ も警告が出るだけでコンパイルでき、同じく 15 です。TypeScript は型検査でエラー(TS2695)にして止めてくれます

JavaScript は return [lo, hi];、C++ は std::pair で return {lo, hi};、C はアドレスを受け取るか構造体で返します。
return lo, hi で二つの値が返るのは Python です。

global なしで外の変数を変えたPython

checks = 0
def check(x):
    checks += 1
    return x % 2 == 0
print(check(4))

→ UnboundLocalError: cannot access local variable 'checks' where it is not associated with a value — 関数の中で値を入れる名前は、その関数の変数として扱われます。そのため、まだ値のない checks に 1 を足そうとして止まります

関数の先頭に global checks と書きます。
読むだけなら必要ありません。

関数の中の変数名がいちばん上の変数と同じだったCraykon

total = 100;
twice(x)
{
    total = x * 2;
    返す total;
}
コンソール.書く(twice(4));
コンソール.書く(total);

→ 8、8 — 関数は新しい変数を作ったのではなく、いちばん上の total を変えたので、100 が消えました

関数の中だけで使う変数には、いちばん上の変数と違う名前を付けます。
引数は名前が同じでも、その関数だけの変数です。
ほかの言語では、関数の中で let total や int total のように新しく作れば(Python は global なしで値を入れれば)外のものとは別の変数になり、8、100 と出ます。

言語ごとの違い
関数を渡すときに countIf(nums, isEven()) のようにかっこを付けると、渡すのではなくその場で呼ぶことになります。
JavaScript は TypeError: test is not a function、Python は TypeError: is_even() missing 1 required positional argument: 'x' で止まります。
TypeScript は型検査で、Craykon は「isEven には値が 1 個必要なのに 0 個入れた」というエラーで知らせてくれます。

練習問題