Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

そもそも静的型付けって?TypeScript入門以前の話

みなさんは「型」についてご存知でしょうか?「intとかcharみたいなアレでしょ?」くらいの知識はあるかと思います。今回の記事では型についての種々の疑問について解説して行きたいと思います。

  • そもそも型とは?
  • 型があるとなんで嬉しいの?
  • 静的型付け・動的型付けって?
  • プログラムに型がなくても静的型付けってどういうこと?

なお、この記事ではTypeScriptの型をもとに話しますが、他の言語でも言える普遍的な内容になっています。

そもそも型とは?

型とは、データの分類の名称のことです。たとえば042number型'''hello world'string型です。

型はプリミティブ値以外の式にもつきます。たとえば式1 + 2number型、式'hello' + ' ' + 'world'string型です。

また、変数や関数の引数・返り値にも型をつけることができます。型がついた変数は、その型の値しか代入できません。

たとえば、number型の値を2つ受け取ってnumber型の値を返すmax関数はこのようにかけます。

const max = (a: number, b: number): number => a > b ? a : b;

型があると嬉しいこと

型があると嬉しいことはいくつか挙げられます。

  1. 型注釈をみることで関数の動作をだいたい想像できる
  2. 静的型検査を行うことで「実行時エラー」をなくすことができる

1つ目はオマケで、2つ目のほうが重要です。

型注釈から関数の動作を予想できる

たとえば、map関数は以下のような型を持ちます。

const map = <T, T2>(f: (bef: T) => T2, a: T[]): T2[] => {
    const res = [];
    for (const item of a)
        res.push(f(item));
    return res;
}

fとaを受け取り、aの各要素にfを適用した結果を返す関数になっており、型定義もそのようになっています。

静的型検査を行って実行時エラーをなくせる

静的型検査というのは、プログラムを実行せずに型が正しくつけられているか検証する作業です。たとえば次のコードはTypeScriptの型検査ではじかれます。

const n = '3'; // おや?
const m = 4;
console.log(n * m); // 文字列と掛け算してしまっている!

実はJavaScriptではこのコードは意図したとおり(?)12を表示してくれます。これは静的型検査がないかわりに実行時に必要に応じてキャストしているためです。

しかし、もしnが外部からの入力だったとしたらどうでしょうか…?

const n = await readFile('hoge.txt', 'utf8'); // hoge.txtから読み込んでいるけど…
const m = 4;
console.log(n * m); // 数値で掛け算できている保証はない

これはごくカンタンな例でしたが、もっと巨大なコードになっていくと、いちいち調べるのは大変です。そこで、型をつけて検査をしてエラーをはじきます。

型検査

静的型付け・動的型付けとは?

  • 静的型付けはプログラムを実行する前に型をつける
  • 動的型付けはプログラム実行時に型をつける

TypeScriptは静的型付けをするので、静的型付き言語です。TS→JSのコンパイル時に型チェックが行われます。コンパイルが通れば実行時エラーが起きないことが保証されます*1

型推論

静的型付き言語であっても、必ずしもすべてに型を書く必要はありません。たとえば目視でも「1 + 2number型だ」という"推論"ができます。二項演算子なら、その左辺と右辺の項、演算子の情報さえあれば推論できます。この推論機能によって、型アノテーションを省略することができます。ただし型推論の目的は型アノーテーションの省力化ではありません

もし型推論がなかったら、次のように書かなければなりません(下のような構文はそもそもTypeScriptにありませんが)。

const a : number = ((1 + 2 : number) * (3 - 4 : number) : number)

さすがにこれは過剰です。演算子は型が言語仕様から決まりきっているので、オペランドとの組み合わせが決まれば返り値の型は一意に決められます。型アノテーションが必要になることはありません。

関数の推論についてはややこしいので、参照記事をご覧ください。

TypeScriptの型推論詳説 - Qiita

ここで重要なのは、型推論は静的に行われることです。静的型検査を行いながら推論をしていくので、型推論によって不要な型アノーテーションを消したからといっても実行時エラーは起きえません。型推論と動的型付けはごっちゃになりがちなので、気をつけてください。

参照

TypeScriptの型推論詳説 - Qiita

*1:ただし、TypeScriptはJavaScriptのスーパーセットで、型がないところは勝手にany型がつけられて検査時にエラーが出ないため、適切な型付けをしていることが前提ですが

就活を振り返る

2018年(学部3年) 4月 院進以外に、就職という道があると気がつく(院には当然進むものだと思いこんでいた)

5月 院進と就職で悩み始める

6月 就職しようと決める

〜12月 「自己分析」とか「業界研究」とか何すればいいか分からないし、部活が忙しくてやる暇がない

1月 近所でやってたインターンや説明会に参加する

2月 就活を始める。

企業ホームページの採用情報のところから直接応募するスタイルを取った。就活サイトに登録したり、説明会に参加したりしていなかったため、手当たりしだい応募する形になった。

  • C社 課題選考通る→一次面接を受ける→お祈り
  • P社 書類選考すら通らず
  • C社 選考通る→いつまでもメールが返ってこなかったので東京から帰る途中に「面接は明日いかがですか?」来る→無理だったので断る
  • C社 ウェブテスト受ける→一次選考→お祈り
  • D社 一次面接(Google Hangout)を受ける→二次面接へ→お祈り
  • H社 書類選考通る→一次面接(ウェブ面接)を受ける→最終選考を受ける→お祈り
  • L社 課題選考を受ける→一次面接(現地)を受ける→最終面接へ→内定!
  • Y社 課題選考に通る→適性検査→一次面接へ

雑感

課題選考はとりあえず通るので、最低限は技術力を評価されていたと思います。しかし、とにかく面接が通りませんでした。後でも書きますが、チームで開発したことがなくて、どういうふうに働くか具体的なイメージができなかったのが要因の一つだったと思います。

最終的に、受けた中で一番年収が高い企業になりました。労働条件もとてもいいので嬉しい限りです。ただ、その環境を享受できるほど能力がある自信がないので非常に不安です。というか周りのレベルについていけるのでしょうか…?がんばります。

面接でたびたび「どういうふうにして情報を仕入れていますか?(技術系の流行の追い方みたいな意味だったと思う)」と聞かれたのが印象的でした。

「Linuxカーネルの役割をカンタンにいくつか挙げてみて」と言われて出てこなかったのが悔しかったです。プロセス管理とかメモリ管理とか分かるけど、それが「カーネル」という言葉と結びついていませんでした()。授業でもやりましたし、応用情報の勉強でもやっていてそのへんの知識はあっただけに、惜しいことをしました・・・

よく聞かれたこと

  • 「将来的にどういうエンジニアを目指しているか?」
  • 「チームで開発したことはあるか?」(これはメチャクチャ聞かれた!!)
  • 「チームで働くとき、どうチームに貢献できるか?」

後悔したこと

チーム開発の経験についてほぼ毎回聞かれましたが、インターンやアルバイトなどに行っていなかったため毎回困りました。開発型のインターンに行っておくと有利かもしれません。インターンに参加しておけば、実際にチーム開発を経験できるので、チーム開発に向いていないのにプログラマになってしまう事故を防げます。

他に、自己分析をしていなかった…というかやり方が分からなかったのは良くありませんでした。自己分析をすることで、自分に合う環境を探す指標が作れるので、「何が好きか」「どう働きたいか」「何が嫌か」「どういう福利厚生が欲しいか」などをまとめるだけでもだいぶ違ったんじゃないかと思います。

ポートフォリオ

就活しているのでブログにもポートフォリオを書いておきます。

自己紹介

名前はクロパンダです。

現在は東北大学の工学部で情報工学を学んでいます。学部4年生なので、まだ研究は始まっていません。

趣味はプログラミングと合気道です。プログラミングは中学生から、合気道は大学から始めました。

主にJavaScriptを書いています。今までに作ったもの・書いたものとして以下が挙げられます。

制作物

  • unitaryjs 宣言的に書けるCanvasライブラリ
  • Paper Memo ブラウザ上で紙のUIを再現するメモサービス
  • 炎上

ほかにもあるのでサイトの制作物一覧をご覧ください。

unitaryjs

HTML5のCanvasは「線を引く」「円を描く」のような命令ベースでプログラムを書かなければなりません。これではコードを読んでもどういう図形を書こうとしているのかがわかりません。そこで、「四角形を描く」「多角形を描く」「5本の線分で星を描く」といった風に宣言的に書けるようにするライブラリを作りました。Qiitaに投稿したところ、52いいねを頂けました。

Paper Memo

ページ上の任意の箇所に入力フォームが設置できるサービスです。テキストファイルは1次元方向(横方向)にしか文字を設置できず、紙のような自由度がなくて使いづらいと思ったのが動機でつくりました。紙のような自由度、キーボードによる入力のしやすさを組み合わせたサービスです。

技術的に(自分としては)新しいことにたくさん取り組みました。たとえば、バックエンドでGoを使い、GraphQLのリクエストをさばいています。また、今までRDBMSしか触ってこなかったのでNoSQLのMongoDBを使ってみました。

ほかにもReact + TypeScript + Redux + immerでフロントエンドを書いたり、メールサーバーを建ててアクティベートメールを送信したり、ソーシャルログインを実装したり(これはまだ未完成)といったことをしました。

これは「新技術を試してみたい」と「こういうサービスを作りたい」というタイミングがマッチして作ったサービスです。サービスをつくるときは「新技術を試したい」という動機で作ることが多いのでめずらしいパターンでした。

炎上

これはWebSocketの習作として作ったミニゲームです。アクセスしているユーザーは「火を起こす」か「薪を足す」ことができます。そして、アクセスしている人みんなで火を絶やさず燃やし続けようというゲームです。誰かが「火を起こす」か「薪を足」したらサーバーから通知が送信され、画面が更新されます。

こっちは「WebSocketを試したい!」という理由だけでつくりました。「薪を燃やしてぇッッ!!」みたいな動機はありません。上でも書きましたが、「新技術を試したい」という動機でサービスを作ることが多く、これはその最たる例です。

webpack.config.jsのプラクティスを考えてみた

何度もwebpack.config.jsを手書きしていて面倒くさくなってきたので、ここに書き方を残しておきます。

webpack.config.jsを書く際によく使うTipsとして、これらが挙げられます。

  • mode: env.mode || 'development'
  • path.resolve(__dirname, path_to_file)を使う
  • options.resolve.extensionsに読み込みたいファイルの拡張子を記述
  • entryを複数書くとoptions.output.filename'[name].min.js'のように指定できる
  • loaderはoptiopns.module.rulesに書き込む

ひとまずテンプレ

最初にwebpack.config.jsのテンプレを載せておきます。どうせ未来の僕がこの記事を読むときは、このテンプレを見たいときだけなので。

const path = require('path');

module.exports = (argv, env) => ({
    mode: env.mode || 'development',
    entry: path.resolve(__dirname, './src/main.js'),
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, './dist'),
    },
    module: {
        rules: [],
    },
});

記事の後半の内容が必要そうなら使うバージョン。

const path = require('path');

module.exports = (argv, env) => ({
    mode: env.mode || 'development',
    entry: path.resolve(__dirname, './src/main.js'),
    // entry: {
    //     main: path.resolve(__dirname, './src/js/main.js'),
    //     index: path.resolve(__dirname, './src/js/index.js'),
    //     gallery: path.resolve(__dirname, './src/js/gallery.js'),
    // },
    // resolve: {
    //     extensions: ['.js'],
    // },
    output: {
        // filename: '[name].min.js',
        filename: 'bundle.js',
        path: path.resolve(__dirname, './dist'),
    },
    module: {
        rules: [
            // {
            //     test: /\.css$/,
            //     use: ['to-string-loader', 'css-loader'],
            // },
        ],
    },
});

mode: env.mode || 'development'

この設定をしておくと、package.json内で開発ビルドとプロダクションビルドをカンタンに切り替えることができます。

  "scripts": {
    "build": "webpack --mode production",
    "build:dev": "webpack",
    ...
  },

応用として、options.output.filenameを書き換えることもできます。

{
    output: {
        filename: env.mode == 'production' ? 'bundle.min.js' : 'bundle.js'
    }
}

path.resolveを使う

pathモジュールのpath.resolveメソッドを使ってファイルパスを指定するようにします。例えばoptions.entryoptions.output.pathなどで使うといいです。

なぜわざわざpath.resolveを使うかという理由は webpack.config.jsで思ったpath.resolveって何のためにあるの?に書いてあります。

Windowsとかだとパス区切りが/じゃないこともあるみたい。バックスラッシュっていうやつ(\)。だから__dirname + '/src'だとパスがおかしくなってしまうことがあるからpath.resolveを使って安心安全で行こうぜ!ってことらしい。 (引用: webpack.config.jsで思ったpath.resolveって何のためにあるの?)

環境間の差異を吸収する目的のようです。

読み込みたいファイルの拡張子はoptions.resolve.extensions に

よく忘れるので書いただけです。特に解説することはないです。

entryが複数あるときのoutput.filenameの書き方

これはそこまで頻度高くないです。

{
    entry: {
        main: path.resolve(__dirname, './src/main.js'),
        sub1: path.resolve(__dirname, './src/sub1.js'),
        sub2: path.resolve(__dirname, './src/sub2.js'),
    },
    output: {
        filename: '[name].bundle.js'
    }
}

loaderは optiopns.module.rules に書き込む

めちゃくちゃ当たり前なんですが、手書きすると「あれ?どこに書くんだっけ?」となるので書いておきます。

参考

webpack.config.jsで思ったpath.resolveって何のためにあるの?