[日本語Alexa] Alexa-SDK Ver2をTypeScriptで書く時のタグ付き共用体の取扱要領について

2018.05.22

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

Alexa SDK Ver2は、TypeScriptで書かれているため、型情報(.d.ts)が最初からバンドルされています。このため、TypeScriptを使用することは、非常にハードルの低いものとなっています。

今回は、「お、それじゃ、一発TypeScriptで書くか!」とチャレンジしてみた方が、最初に戸惑いそうな所についての話です。

以下の例では、ドリンクの注文を受け取るOrderIntentでスロット(飲み物の種類)を受け取ろうとして、問題が発生しているようすです。

「ハイハイ、共用体ね」って、原因が瞬殺で分かる方は、これ以上読み進める必要はないです。 「どうすれば良いのだろ?」って方がおられましたら、もう少し、読み進めてみて下さい。

2 'intent'は型 'Request'に存在しません

そもそも此処でのRequestとは、Alexaからのリクエストそのものを表現したオブジェクトです。

Alexaの代表的なリクエストは、下記の3種類です。

LaunchRequestの例

"request": {
  "type": "LaunchRequest",
  "requestId": "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
  "timestamp": "2015-05-13T12:34:56Z",
  "locale": "string"
}

IntentRequestの例

"request": {
    "type": "IntentRequest",
    "requestId": " amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
    "timestamp": "2015-05-13T12:34:56Z",
    "dialogState": "COMPLETED",
    "locale": "string",
    "intent": {
      "name": "GetZodiacHoroscopeIntent",
      "confirmationStatus": "NONE"
      "slots": {
        "ZodiacSign": {
          "name": "ZodiacSign",
          "value": "virgo",
          "confirmationStatus": "NONE"
        }
      }
    }
  }
}

SessionEndedRequestの例

"request": {
    "type": "SessionEndedRequest",
    "requestId": "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
    "timestamp": "2015-05-13T12:34:56Z",
    "reason": "USER_INITIATED",
    "locale": "string"
}

これを、Alexa SDKでは、それぞれ以下のように定義しています。

export interface SessionEndedRequest {
    'type': 'SessionEndedRequest';
    'requestId': string;
    'timestamp': string;
    'locale': string;
    'reason': SessionEndedReason;
    'error'?: SessionEndedError;
}

export interface IntentRequest {
    'type': 'IntentRequest';
    'requestId': string;
    'timestamp': string;
    'locale': string;
    'dialogState': DialogState;
    'intent': Intent;
}

export interface LaunchRequest {
    'type': 'LaunchRequest';
    'requestId': string;
    'timestamp': string;
    'locale': string;
}

つまり、Requestは、リクエストの種類によって変わってくるため、これをタグ付き共用体として定義しているのでした。

export declare type Request = interfaces.audioplayer.PlaybackStoppedRequest | interfaces.audioplayer.PlaybackFinishedRequest | 
// ・・・略・・・
SessionEndedRequest | 
// ・・・略・・・
IntentRequest | 
// ・・・略・・・
LaunchRequest;

Requestの実際のタイプが分からない段階では、requestの後ろにドットを打つと、各Intaerfaceに共通なオブジェクトのみしか列挙されません。(intentオブジェクトは、IntentRequestにしかないので、列挙されない)

結局、intentを認識させるためには、RequestIntentRequestであると認識させる必要があるという事になります。

3 IntentRequestと認識させる

RequestIntentRequestと認識させる記述方法には、以下のようなものがあります。

(1) 型注釈(キャスト)

<IntentRequest> のように、型注釈を与えることで強制的に型を指定できます。ただし、この場合、誤った型キャストを行うと動作は保証されません。

(2) 型推論

request.type === 'IntentRequest' で評価された後は、型推論でIntentRequestと認識されます。

(3) as 演算子

as演算子も利用可能です。

4 どのように書くべきか

セーフティーに書くのであれば、キャストの使用は避け、また、as演算子や、型推論で予想外の型が来ていた場合のエラー処理などをちゃんと書くべきでしょうが、階層も深くなるし、やや冗長になることは否めません。

実は、このSDKを使用している場合、事前にcanHandlerで、下の処理にくるRequestの種類は絞られています。

この辺を加味して、軽量さを活かすためにも、自分の責任の範囲となりますが、型注釈(キャスト)や、エラー制御なしのas演算子あたりで完結に書くのが有りかなと個人的には感じています。(すいません、あくまで個人的意見です。)

const OrderIntentHandler = {
    canHandle(handlerInput: Alexa.HandlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && handlerInput.requestEnvelope.request.intent.name === 'OrderIntent'; 
    },
    //以下は、IntentRequestのOrderIntentのみ処理するハンドラの定義となっている
    // OrderIntentのslotsに'drink'が存在することにも責任を持つ
    handle(handlerInput: Alexa.HandlerInput) {
        const request = handlerInput.requestEnvelope.request;

        // 例1) as 演算子で簡潔に書く(asにヒットしなかった場合の処理などは省略する)
        const drink = (request as IntentRequest).intent.slots['drink'].value;

        // 例2)型注釈(キャスト)で簡潔に書く
        // const drink = (<IntentRequest>request).intent.slots['drink'].value;


        const speechText = drink + 'の注文をありがとうございます。';
        return handlerInput.responseBuilder
            .speak(speechText)
            .getResponse();
    }
}

5 最後に

Alexa SDK V2をTypeScriptで使用する場合、今回紹介したような、タグ付き共用体で定義されているオブジェクトが幾つかあります。

また、各種オブジェクトの型定義は、ask-sdk-modelに纏められていますので、型情報が必要なオブジェクトについては、下記のようなインポート文が必要になる事にご注意下さい。

import { RequestEnvelope, ResponseEnvelope, services, IntentRequest } from 'ask-sdk-model';

6 参考リンク


[日本語Alexa] Alexa-SDK Ver2 をTypeScriptで使う
ASK SDK v2 for Node.js
[日本語Alexa] Alexa SDK for Node.js Ver2入門(その1)はじめの一歩
[日本語Alexa] Alexa SDK for Node.js Ver2入門(その2)ハンドラの登録
[日本語Alexa] Alexa SDK for Node.js Ver2入門(その3)レスポンスの作成
[日本語Alexa] Alexa-SDK Ver2(その4)スキル属性
[日本語Alexa] Alexa-SDK Ver2(その5)ダイアログモード
[日本語Alexa] Alexa-SDK Ver2(その6) 所在地情報
[日本語Alexa] Alexa-SDK Ver2(その7) ディスプレイ表示