質問: (...items: any[]) => { return _.reduce(items, (a, b) => { return _.flatten(_.map(a, (x) => { return _.map(b, (y) => { // この行の_.mapの第1引数"b"でエラー return x.concat([y]); }); }), true); }, [[]]); }; ``` エラー内容は以下の通りです。 ``` Argument of type '{}' is not assignable to parameter of type 'Dictionary<{}>'. Index signature is missing in type '{}'. ``` Underscore.jsの型定義はtypingsで読み込んであります。 おそらく中身の無名関数の引数に型を定めていないことによるものかと思います。 しかしいざ型を指定しようとも、この関数からどのようにして直積が求まるのか、仕組みがよく理解できていません。 各無名関数にはどういった型を設定するのが適当か、またこの関数たちがどのように作用しあって最終的に直積が求められるのか、ご教授頂ければ幸いです。よろしくお願い致します。
回答: 私もちょうど数日前から TypeScript を勉強し始めたので、提示されているコードを試してみたところ、同じようにエラーが出るのを確認しました。 解決例を先に示します。厳密にする場合、たぶん、以下のような定義で型の部分の問題は解決すると思います。 ``` const cartesianProductOf = (...items: T[][]) => { return _.reduce(items, (a: T[][], b: _.Dictionary) => { return _.flatten(_.map(a, (x: T[]) => { return _.map(b, (y: T) => { return x.concat([y]); }); }), true); }, [[]]); }; ``` Dictionary の部分は List でも動くかも?reduce() と map() で共通していた Dictionary で定義しました。あと、そのまま書くのは面白くなかったので Generics (ジェネリック/総称型) を使って定義してみました。これを上手く利用すると配列の中身を数値だけに限定したい場合など、TypeScript の型チェックが効果的に使えるようになります。 例えば、以下のように書いた場合、文字列部分を含む配列がエラーとみなされます。 `cartesianProductOf([1,2], ['a',20])` 実際使うかどうかは解りませんが `number` 部分を `string` や `Object` や `Function` なんかに変えたり色々できますね。 少々話が脱線しましたが、質問にある『どういった型を設定するのが適当か?』という問題は堅めの言語を齧ってない人には結構難しい問題なのかもしれません。 型を特定する策として(邪道だとツッコミが入るかもしれませんが)今回の場合だと、map 部分の引数 b の型が不明瞭なわけですが、もうそのまま `b as any` にするとか、型でエラーが出ている箇所全てを any にして(型を無視して)しまい、手っ取り早くコードが動く状態を作った上で、開発環境からステップ実行して実際の型を調べたり、適切な箇所において引数や処理結果をコンソールに出力して調べたり、typings で取ってきた型定義を直接調べたり。といった作業を繰り返し、解った所から**どんどん any を排除していく**のが手探り状態でもできる一つの方法でしょう。大変に泥臭い方法ですが、言語仕様を理解できており、ライブラリに対する型定義自体が整備されていれば、あとは地道に動かしながら調べさえすれば、自ずと答えを求められるようになるのではないでしょうか? というのも、自分が1から書いているコードであれば、都度使うべき型は明確である/明確にできると思いますが、型が省略されている他人の書いたコードで、更に動かせない。となると、なかなか手が出しづらいのも納得できる話です。コードを目で読んだだけで解るレベルに到達するのは、コードの内容に準じたスキルを持っていないと厳しい。と私は思っているため、拾ってきたコードなりで勉強するのであれば、動かせる状況を早めに作り、実際に動かしながら手を入れていく方が理解も深まりやすいでしょう。 今回のコードの場合だと、アロー関数式が使われていますし、Underscore.js の型定義を調べてみたところ、ジェネリックが多用されていました。逆に、この辺の言語仕様が正しく理解できているのであれば時間をかけさえすれば読み解けますので、後はパズルを解くように順に調べれば良いだけです。物は試しに自分で解いてみるとよいでしょう。解らない点が出てくれば、そこがまた伸ばすべき箇所です。 もう一つの質問である関数内部の詳細な挙動に関して、このコードは『極めて素直』なので、動く状態さえ作れれば確認自体は簡単なハズです。また、Underscore.js の reduce() / flatten() / map() が、それぞれどのような機能なのかを調べ、個別に叩くことで更に理解も深まると思います。もし、そこまで行った上で解らない箇所があれば、要件を絞って改めて質問を投げてみてはどうでしょうか?まずはコードを叩いて自分で確認してみましょう。 以上、ご参考になれば幸いです。