- 投稿日:2020-09-09T22:40:46+09:00
社内ネットワークからIBM Watsonにつながらない
はじめに
社内ProxyのおかげでIBM WatsonのAPIが通らかったので対処法を記載します。
(今回はWatson Discoveryです)結論
node-sdkのところに書いてありました。
Use behind a corporate proxynpm install tunnel前提条件
- Node v10.16.3
- npm v6.9.0
- IBM CloudのWatsonDiscoveryのサービスが既にあり、collectionが作成済みであること
Discoveryについてはこちらの記事が参考になると思います。
Discoveryってなんぞ?という方はまずこちらの記事を見ることをおすすめします。
【2019/2月 全面更新!】Watson Discovery Serviceが日本語対応したので、触ってみた【SDUやってみた】編社内ProxyのせいでAPIが通らないのなら、Nodeやnpmもインストールできないのでは・・・?
というご意見はごもっともですが、Node,npmはすでに実行できるという前提でお願いします。今回はあくまでIBM Watsonに焦点を当てます。まずはWatsonのAPIを実行してみる
好きなディレクトリにて
npm init何か聞かれますがすべてEnterでOK
Watson APIs Node.js SDK のインストール
Discoveryのインスタンスをクリックすると次のような画面が出ると思います。
その画面のAPIリファレンス→Nodeタブへ移ると記載があります
npm install ibm-watson@^5.7.0APIの実行
まずは簡単に今あるCollectionのリストを表示してみましょう。
APIリファレンスでは左側のMethods>Collection>List collectionのところに記載があります。
丁寧に右側にExample requestがあり、クリックでコピーができるようになっているのでコピーしましょう。
実行するファイルは適当にcollection.js
にします。collection.jsconst DiscoveryV1 = require('ibm-watson/discovery/v1'); const { IamAuthenticator } = require('ibm-watson/auth'); const discovery = new DiscoveryV1({ version: '2019-04-30', authenticator: new IamAuthenticator({ apikey: '{apikey}',//自身の情報に書き換え }), serviceUrl: '{url}',//自身の情報に書き換え }); const listCollectionsParams = { environmentId: '{environment_id}',//自身の情報に書き換え }; discovery.listCollections(listCollectionsParams) .then(listCollectionsResponse => { console.log(JSON.stringify(listCollectionsResponse, null, 2)); }) .catch(err => { console.log('error:', err); });しかし、このままでは実行してもエラーが出てしまいます。
どこのDiscoveryのどのインスタンスに対してAPIを呼ぶかが記載されていないからです。
上記ソースコードの「//自身の情報に書き換え」をそれぞれ書き換えます(中括弧もいらない)書き換えた後に実行
node collection.jsそうすると無事エラーが返ってきます。
error: { Error: connect ETIMEDOUT 23.41.93.168:443 at RequestWrapper.formatError (xxxxxxxxxx\node_modules\ibm-cloud-sdk-core\lib\request-wrapper.js:224:21) at C:xxxxxxxxxxxxxxx\node_modules\ibm-cloud-sdk-core\lib\request-wrapper.js:212:25 at process._tickCallback (internal/process/next_tick.js:68:7) message: 'connect ETIMEDOUT 23.41.93.168:443', statusText: 'ETIMEDOUT', body: 'Response not received - no connection was made to the service.' }これでAPIを実行する環境ができました。
対処方法
node-sdkのところに書いてありました。
Use behind a corporate proxy
パッケージの「tunnel」を使ってね、とのこと
早速インストールnpm install tunnelそしてgithubのExampleをみつつ
collection.jsconst DiscoveryV1 = require("ibm-watson/discovery/v1"); const { IamAuthenticator } = require("ibm-watson/auth"); const tunnel = require("tunnel"); // https Agent const httpsAgent = tunnel.httpsOverHttp({ proxy: { host: "xxxxxx.com", //httpとかいらない port: 8080, }, }); const discovery = new DiscoveryV1({ version: "2019-04-30", authenticator: new IamAuthenticator({ apikey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx", httpsAgent, proxy: false, }), httpsAgent, proxy: false, serviceUrl: "https://xxxxxxxxxxxxxx", }); const listCollectionsParams = { environmentId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx", }; discovery .listCollections(listCollectionsParams) .then((listCollectionsResponse) => { console.log(JSON.stringify(listCollectionsResponse, null, 2)); }) .catch((err) => { console.log("error:", err); });そして実行すると・・・
※idなどの情報はマスクしました{ "status": 200, "statusText": "OK", "headers": { "content-type": "application/json;charset=utf-8", "access-control-allow-origin": "*", "access-control-allow-methods": "GET, PUT, POST, DELETE, OPTIONS", "cache-control": "no-cache, no-store, no-transform, max-age=0", "pragma": "no-cache", "expires": "0", "x-content-type-options": "nosniff", "x-xss-protection": "1", "content-security-policy": "default-src 'none'", "access-control-allow-headers": "Origin, Content-Type, Content-Length, Accept, X-Watson-Authorization-Token, X-WDC-PL-OPT-OUT, X-Watson-UserInfo, X-Watson-Learning-Opt-Out, X-Watson-Metadata", "strict-transport-security": "max-age=31536000; includeSubDomains;", "x-global-transaction-id": "4085110c3d0bc216af495b254da2ab8b", "x-dp-watson-tran-id": "4085110c3d0bc216af495b254da2ab8b", "content-length": "340", "x-edgeconnect-midmile-rtt": "165", "x-edgeconnect-origin-mex-latency": "222", "date": "Wed, 09 Sep 2020 13:27:25 GMT", "connection": "close" }, "result": { "collections": [ { "collection_id": "xxxxxxxxxxxxxxxxxxxx", "name": "test", "configuration_id": "xxxxxxxxxxxxxxxxxxxxx", "language": "ja", "status": "active", "description": null, "created": "2020-09-09T12:55:36.283Z", "updated": "2020-09-09T12:55:36.283Z" } ] } }めでたしめでたし
終わりに
私が困っているようなことなんて、とっくにほかのだれかが困っていて解決策を出してくれているのできちんと公式のドキュメントをしっかり見よう!!
英語翻訳バンザイDiscoveryのQueries>Query a collectionではまったとこ
クエリーの返り値フィールドを指定できるパラメーターの書き方がややこしくてはまった。
複数なのでリストかと思っていたら全然違かった。
正しくは下記const queryParams = { environmentId: '{environment_id}', collectionId: '{collection_id}', _return:'id,metadata.name' //ここ };
- 投稿日:2020-09-09T21:09:34+09:00
MATERIAL-UIのMultiple SelectでChipから選択を解除する
はじめに
React + Typescript + MaterialUIでフロントエンドを作成していたときに実現したいことができなかったので参考程度に投稿します。
実現できなかった原因
Chipの×ボタンを押してもSelectのメニューが出てきてしまう
実現したいこと
MaterialUIの公式ドキュメントに書かれていた「Multiple Select(Chip)」でChipの×ボタンからも選択を解除したい!
結論
Chipコンポーネントに以下プロパティを追加
onMouseDown={(event) => {event.stopPropagation();}}前提条件
- typescript,React,MaterialUiについてある程度の知識がある。
- create-react-appでアプリケーションが作成済み
npx create-react-app {your_app_name} --template typescript
- material-uiがインストール済み
npm install @material-ui/coreMultiple SelectのChipを動かしてみる
まずは何も考えずコードをすべてコピーして
App.tsx
に貼り付ける。
次にChip以外のところは邪魔なのでChipに関連するものだけを残す。
そうするとコードは以下のようになります。App.tsximport React from 'react'; import { createStyles, makeStyles, useTheme, Theme } from '@material-ui/core/styles'; import Input from '@material-ui/core/Input'; import InputLabel from '@material-ui/core/InputLabel'; import MenuItem from '@material-ui/core/MenuItem'; import FormControl from '@material-ui/core/FormControl'; import Select from '@material-ui/core/Select'; import Chip from '@material-ui/core/Chip'; const useStyles = makeStyles((theme: Theme) => createStyles({ formControl: { margin: theme.spacing(1), minWidth: 120, maxWidth: 300, }, chips: { display: 'flex', flexWrap: 'wrap', }, chip: { margin: 2, }, noLabel: { marginTop: theme.spacing(3), }, }), ); const ITEM_HEIGHT = 48; const ITEM_PADDING_TOP = 8; const MenuProps = { PaperProps: { style: { maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, width: 250, }, }, }; const names = [ 'Oliver Hansen', 'Van Henry', 'April Tucker', 'Ralph Hubbard', 'Omar Alexander', 'Carlos Abbott', 'Miriam Wagner', 'Bradley Wilkerson', 'Virginia Andrews', 'Kelly Snyder', ]; function getStyles(name: string, personName: string[], theme: Theme) { return { fontWeight: personName.indexOf(name) === -1 ? theme.typography.fontWeightRegular : theme.typography.fontWeightMedium, }; } export default function MultipleSelect() { const classes = useStyles(); const theme = useTheme(); const [personName, setPersonName] = React.useState<string[]>([]); const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => { setPersonName(event.target.value as string[]); }; return ( <div> <FormControl className={classes.formControl}> <InputLabel id="demo-mutiple-chip-label">Chip</InputLabel> <Select labelId="demo-mutiple-chip-label" id="demo-mutiple-chip" multiple value={personName} onChange={handleChange} input={<Input id="select-multiple-chip" />} renderValue={(selected) => ( <div className={classes.chips}> {(selected as string[]).map((value) => ( <Chip key={value} label={value} className={classes.chip} /> ))} </div> )} MenuProps={MenuProps} > {names.map((name) => ( <MenuItem key={name} value={name} style={getStyles(name, personName, theme)}> {name} </MenuItem> ))} </Select> </FormControl> </div> ); }そしてこれを
npm start
してhttp://localhost:3000
でみると
よさそう!※拡大してますChipの×ボタンを表示する
Chipに×ボタンを表示するにはChipコンポーネントに
onDelete
プロパティを追加すればいい。Apptsx<Chip key={value} label={value} className={classes.chip} onDelete={() => { alert(value) }} /> #ここを追加今回は×ボタンをがクリックされたことを確認するために、alertでクリックしたChipのラベルを表示するようにした。
そして再度npm start
あれ...?何度押しても×ボタンがクリックできずにSelectのメニューが出てきてしまう。
対処法
Chipコンポーネントに以下プロパティを追加
App.tsx<Chip key={value} label={value} className={classes.chip} onDelete={() => { alert(value) }} onMouseDown={(event) => {event.stopPropagation()}} #ここを追加 />今度はきちんとクリックされてアラートが出ていますね。
Chip削除の処理を追加する
先ほど入れたalertの処理をChip削除(personName内の名前を削除)する処理に変える。
新しく
chipDelete
という関数を用意するconst chipDelete = (name: string) => { setPersonName(personName.filter(value => value !== name)) }そうすると
App.tsx
のfunction MultipleSelect()
は以下のようになるApp.tsxexport default function MultipleSelect() { const classes = useStyles(); const theme = useTheme(); const [personName, setPersonName] = React.useState<string[]>([]); const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => { setPersonName(event.target.value as string[]); }; const chipDelete = (name: string) => { setPersonName(personName.filter(value => value !== name)) }; return ( <div> <FormControl className={classes.formControl}> <InputLabel id="demo-mutiple-chip-label">Chip</InputLabel> <Select labelId="demo-mutiple-chip-label" id="demo-mutiple-chip" multiple value={personName} onChange={handleChange} input={<Input id="select-multiple-chip" />} renderValue={(selected) => ( <div className={classes.chips}> {(selected as string[]).map((value) => ( <Chip key={value} label={value} className={classes.chip} onDelete={(event) => chipDelete(value)} onMouseDown={(event) => { event.stopPropagation(); }} /> ))} </div> )} MenuProps={MenuProps} > {names.map((name) => ( <MenuItem key={name} value={name} style={getStyles(name, personName, theme)}> {name} </MenuItem> ))} </Select> </FormControl> </div> ); }うまくいった!!!!
終わりに
今思うとこんなにすっとできてしまうなんて。。。
ただ単にコピペするだけではなくてどのような処理が行われていて、どんなオプションのプロパティがあるのかを確認しないといけないですね。
- 投稿日:2020-09-09T15:43:48+09:00
デプロイコマンドを実行する際に対話形式で確認
デプロイのコマンドを実行する際に、環境ごとで確認をするためのスクリプトを書きます。
指定された入力があれば、そのあとの処理を続けて実行、指定と違えばそこで終了します。package.json{ "scripts": { "deploy": "sh confirmation.sh STG && node deploy" } }confirmation.sh#!/bin/bash ENV_NAME=$1 function ConfirmExecution() { echo "${ENV_NAME}環境のスクリプトを実行しますか?" echo "実行する場合は${ENV_NAME}と入力してください" read input if [ -z $input ]; then ConfirmExecution elif [ $input = ${ENV_NAME} ]; then echo "スクリプトを実行します。" else echo "スクリプトを終了します。" exit 1 fi } ConfirmExecutiondeploy.jsconsole.log('deploy!!')実行
$ npm run deploy
- 投稿日:2020-09-09T12:03:49+09:00
いまさらNode.js入門
記事概要
環境構築まわりは誰か出来る人ひとりがやってしまえば
触る必要がない(むしろ触らないほうが良いくらい)ので
なかなか知識が付きにくい。
という事で少し勉強してみた事をまとめてみました!
Node.js とは
そもそもNode.jsとは何ぞやというところですが、
めちゃくちゃ簡単に言うと、サーバーサイドで動くJavaScript環境の事です。と言葉で聞いてもピンと来ないので、実際にどういう事か
次のスライドで見てみましょう。
前のスライドでやっている事
test
を出力するスクリプトを作成- スクリプトファイルをダブルクリックで実行しようとするとエラーが出る
- Node.jsを使用してコマンドラインから作成したスクリプトを実行すると
test
が出力される
正確には違うかもしれませんが、
これがサーバーサイドで動くってことかぁ
となんとな~く感じでいただけたかと思います。では次に、Node.jsでよく使用される
npm
について見ていきましょう。
npm とは
npmはパッケージ管理システムの一種です。
npmには凄い人たちが作った、
凄いモジュールが沢山あります。
Node.jsではそれらのモジュールをインストールする事で
使用することができるのですが、そのインストールの際
npmを使用します。
モジュールは依存関係を持っている事が多く、
モジュールAを使用するためにはモジュールBが必要で
そのモジュールBを使用するためにはモジュールCが~…
みたいなことが起きます
npmはこのような事を解決して
モジュールAをインストールする。と実行するだけで
そのモジュールを実行するために必要なモジュールを
併せてインストールしてきてくれます
モジュールの使用方法
実際にモジュールを使用してみましょう。
まずは、今回作業するディレクトリを作成してください。
今回、私はデスクトップにtest
という
ディレクトリを作成して説明を行っていきます。※ Node.jsはインストール済みを想定しています。
※ ターミナルはgit bashを使用しています。
まずは該当のディレクトリに移動して、
下記コマンドを打ってみましょう。npm init色々と入力を求められますが、すべてEnterで大丈夫です。
するとpackage.json
というファイルができるので
このファイルを開いてみましょう。
package.json
の中の"scripts"
に
"test"
というscriptがあるかと思います。
この部分を下記の様に書き換えてみましょう。"scripts": { "test": "echo hello world !" }
そして、下記コマンドを実行すると「hello world !」が
表示されるはずです!npm run test
これが基本的な
npm-script
の実行方法です。
npm-script
を実行する際はpackage.json
がある
ディレクトリでnpm run [スクリプト名]
で
実行が可能です!次はnpmでモジュールをインストールして
そのモジュールを使用したスクリプトを
作成していきましょう。
今回は例としてonchangeモジュールを
使用してみましょう。このモジュールはファイルの変更を
検知してくれるという機能になります。
下記コマンドでインストールを行いましょう。npm install onchange
インストールが完了したのを確認したら
package.json
を見てみましょう。すると、
"dependencies"
という項目に"onchange"
が
追加されていると思います。これがあることで、
npm install
というコマンドを打つだけで、
onchange
モジュールをインストールしてきてくれます。
つまり、複数人で開発を行う際
package.json
が
共有されていれば、その階層でnpm install
を行うだけで
"dependencies"
にあるモジュール群を全てインストール
してきてくれるという仕組みです!必要なモジュールをひとりひとりが一個ずつ
インストールする必要が無いのは、このおかげです。
それではインストールしてきた
onchange
モジュールを
実際に使ってみましょう。詳細な使用方法は
公式にあるので、そちらを参考にしてください。まずは今回使用しているディレクトリの中に、
src
というディレクトリを作成してください。
次に、
package.json
を開いて先ほど編集した
"scripts"
の"test"
の箇所を下記の様に
変更してください。"scripts": { "test": "onchange src/* -- echo {{changed}}" }
先ほどのスクリプトでやっている事としては
下記のような感じです。onchange [監視するファイル] -- [変更があったときに実行されるスクリプト]今回は、
src
ディレクトリ内で、変更があったら
変更があったファイル名を出力する。
という単純なものです。途中で使われている
{{changed}}
というのも、
onchange
モジュールの機能の一つで、
変更が検知されたファイルの
ファイル名を教えてくれます。
では、実際にスクリプトを実行してみましょう。
npm run test
すると、監視モードになると思うので、
作成したsrc
ディレクトリに適当なファイルを作成したり
編集したりしてみてください。
変更したファイル名が出力されたら成功です
以上がNode.jsの簡単な説明になります!
今回使用したonchange
モジュールは使い方次第で
とても便利に使用できるので、とてもおすすめです。今までNodeを使ってるけど、どんなものなのか
あまり理解してなかったので、同じような人の
助けになれば幸いです
お わ り
- 投稿日:2020-09-09T00:26:41+09:00
JSエコシステムぶらり探訪(2): Node.jsとCommonJS modules
JSエコシステムの進化を語るにはNode.jsを避けて通ることはできません。Node.jsと、それ自身の持つモジュール機能について歴史的な背景を踏まえつつ説明します。
Node.js
Node.jsは非同期I/Oを備えたサーバーサイドJavaScriptのための実行環境として2009年に登場しました。1 現在はサーバーサイドJavaScriptだけではなく、JavaScriptのビルド環境として無くてはならないものになっています。
要するにNode.jsは、PerlスクリプトやRubyスクリプトと同じようにJavaScriptのコードを実行するための環境です。
main.jsconsole.log("Hello, world!");$ node main.js Hello, world!CommonJS modules (CJS)
CommonJS modulesはNode.jsで主に使われているモジュール形式です。CommonJSという規格群の一部として2009年に登場し、生まれて間もないNode.jsのモジュールシステムもすぐにCommonJSに適合しました。 2
後述するESM (ES Modules) で置き換えられつつあり、CJSのほうが伝統的な形式といえます。
util.jsvar private_value = 42; // exports.square = ... でもよい module.exports.square = function(x) { return x * x; };main.jsvar util = require('./util'); console.log(util.square(2)); // => 4 console.log(util.private_value); // => undefinedまた、
module.exports
に別の値を代入することもできます。この場合はexports
にだけ代入しても有効ではないので注意する必要があります。square.js// module.exportsへの代入は必須。exportsにも代入しておくと後々便利 module.exports = exports = function(x) { return x * x; }; // JavaScriptの関数はそれ自体オブジェクトなので、プロパティーに代入できる exports.version = "0.1.0";上の例では、
require
は関数を返します。main.jsvar square = require('./square'); console.log(square(2)); // => 4 console.log(square.version); // => "0.1.0"Node.jsにおいては最初に呼ばれるJavaScriptファイルもモジュールです。
main.jsvar x = 42; // global is Node.js equivalent of windows // (There's also globalThis usable in both environments) console.log(global.x); // => undefinedNode.jsのCJSの仕組み
CJSのrequireはNode.js (など、それぞれの処理系) が提供するプリミティブ関数ですが、その動作は比較的シンプルに理解できます。つまり、ファイルを発見して読み取り、関数に包んで
eval
する処理と考えることができます。3実際にevalされるときは以下のような関数の本体として扱われます。(つまり、
exports
,require
,module
,__filename
,__dirname
という5つのローカル変数があらかじめ存在するものとして扱われます。)(function(exports, require, module, __filename, __dirname) { // ファイルの中身 });
module
とexports
はあらかじめ以下のように初期化されたものと考えることができます。var module = {}, exports = {}; module.exports = exports; // その他、moduleの様々なプロパティーを設定
require
はmodule
オブジェクトをキャッシュしておき、eval終了後にmodule.exports
の値を戻り値として返すと考えることができます。これによりmodule.exports
とexports
の関係についても説明がつきます。モジュールの副作用とオブジェクトの同一性
以下のようなモジュールを考えます。
module1.jsconsole.log("Hello, world!"); var counter = 1; exports.fresh = function() { return counter++; };このモジュールには以下の特徴があります。
- モジュールのトップレベル処理に副作用がある。
- モジュールが状態を持っている。
このような場合、モジュールの同一性を気にする必要が出てきます。全く同じ内容の
module2.js
をコピーとして作成し、以下のようにmain.js
から呼び出してみます。main.jsvar module1 = require('./module1'); // => Hello, world! var module2 = require('./module2'); // => Hello, world! console.log(module1.fresh()); // => 1 console.log(module1.fresh()); // => 2 console.log(module2.fresh()); // => 1 console.log(module2.fresh()); // => 2 console.log(module1.fresh === module2.fresh); // => false両方の
console.log
が実行され、fresh
は別々にカウントされ、fresh
のオブジェクトとしての同一性もfalse
になりました。Node.jsでは、パスが同じものは同一モジュールになります4。先ほどの
main.js
を書き換えて./module1
を2回インポートするようにしてみます。main.jsvar module1a = require('./module1'); // => Hello, world! var module1b = require('./module1'); // (no output) console.log(module1a.fresh()); // => 1 console.log(module1a.fresh()); // => 2 console.log(module1b.fresh()); // => 3 console.log(module1b.fresh()); // => 4 console.log(module1a.fresh === module1b.fresh); // => true
console.log
は1回しか実行されず、fresh
は同じカウンタを使うようになり、2つのfresh
関数はオブジェクトとしても同一になりました。冠頭形モジュール
CJSのインポートは単なる
require
という関数であり、どこでも呼び出すことができます。これは一見すると便利で妥当な設計に見えますが、実際はNode.js以外の環境にモジュールシステムを移植するにあたってこの「どこでも呼び出せる」という性質が邪魔になってきます。そこで、以降で解説するモジュールシステムの理解を助けるために、本稿独自の用語として「(CJSの)冠頭形モジュール」という概念を導入します5。
定義. あるCommonJSモジュールが冠頭形である (is a prenex-form module) とは、以下を満たすことである。
- そのモジュールファイルはヘッダ部と本体に分けられる。 (ヘッダ部に続いて本体が来るものとする)
- ヘッダ部の各文は以下のいずれかの形式である。
var <変数名> = require(<文字列リテラル>);
let <変数名> = require(<文字列リテラル>);
const <変数名> = require(<文字列リテラル>);
require(<文字列リテラル>);
- 本体では
require
関数は使われていない。冠頭形であれば、モジュールファイルの中身を実際にevalしなくても、あらかじめ依存先モジュールを決定することができます。
冠頭形ではないものの例としては以下のようなものがあります6。
- 条件つきインポート
require
の引数が動的に決まるようなインポート- 当該モジュール読み込み時ではなく、あとで必要になってから行うインポート
まとめ
特に重要なのが以下の点です。
- Node.jsによって「ブラウザー以外のJavaScript実行環境」が大きな地位を獲得した。
- Node.jsによって、JavaScriptに優れたモジュールシステムがもたらされた。
このことがJavaScriptに2つの大きな課題をもたらしました:
- Webブラウザーもモジュールシステムの恩恵を受けられるようにすること。
- Node.jsとWebブラウザーの間のコードの相互運用性を高めること。
これらの課題がJavaScriptバンドラーの誕生、そして各種の新しいモジュールシステムの提案へとつながっていくと考えられます。が、次回はその前に、Node.jsのパッケージシステムについて扱います。
Wikipediaの記述によると、それ以前にもサーバーサイドJavaScriptの技術自体は存在していたようです。 ↩
根拠を探す余裕がなかったのでこのように書きましたが、実際のところNode.jsの初期のモジュールシステムをベースにしてCommonJSが生まれた可能性が高いと思います。 ↩
他に、ファイルの読み取りが同期的に行われる点、複数回requireしたときにキャッシュする仕組み、巡回参照の処理などを考える必要がある ↩
シンボリックリンクについては次回言及予定 ↩
これについて、より広く使われている名称があれば教えていただけるとありがたいです。 ↩
バンドラーによっては、ここに挙げたような例をうまく処理できてしまう場合もありますが、それでも一般的な場合を全てカバーするのは困難です。 ↩