LoginSignup
12
9

More than 3 years have passed since last update.

「なでしこ」と「ことは」を組み合わせると、自然言語プログラミングが可能か、検証してみた。

Posted at

はじめに

プログラミングに慣れている人ほど、
プログラミング言語は自然言語に近い必要はない
と思っているのではないでしょうか?

プログラミングには、整合性のある規則が重要で、自然言語における曖昧性は混乱の元だと。

たとえば、文字を扱うことを考えてみましょう。
プログラムでは、文字コードの違いはもとより、全角・半角、大文字・小文字の違いでを正確に扱わなければなりません。
プログラムにおいて、どうして曖昧な自然言語が必要なのか!と。

 

さて、 なでしこ という
「日本語で誰でも簡単プログラマ−」 をモットーとしている
プログラミング言語があります。

まずは、このプログラミング言語の紹介を兼ねて、競技プログラミングの問題でコード例を確認してみましょう。

問題: 「二つの正整数 $a, b$ が与えられます。 $a$ と $b$ の積が偶数か奇数か判定してください。」
(AtCoder ABC 086 A - Product)

C++ での解答:

#include <iostream>
using namespace std;

int main() {
    int a, b;
    cin >> a >> b;
    int c = a * b;
    if (c % 2 == 0) cout << "Even" << endl;
    else cout << "Odd" << endl;
}

なでしこ での解答:

尋ねる。入力はそれを「 」で区切る。aは入力[0]のINT。bは入力[1]のINT。aにbを掛けて2で割った余り。もし、それが0ならば。「Even」を表示する。違えば。「Odd」を表示する。ここまで。

※ 本記事では、なでしこのコード(.nako)はコードブロックでは記載せずに、上記のスタイルで記載します。

上記の動作は、 なでしこ3 Web簡易エディタ を利用することで、WEBブラウザ上で簡単に動作を試すことができます。

テキストボックスに、上記の解答の文章を入れ、「実行」ボタンを押し、表示されるダイアログに「1 3」など、半角スペースで区切られた2つの整数を入れると、判定されるのを確認できるでしょう。

なお、 なでしこ では、上記を「です・ます調」に変えた下記でも同様に動作します。

尋ねます。入力はそれを「 」で区切ります。aは入力[0]のINT。bは入力[1]のINT。aにbを掛けて2で割った余り。もし、それが0ならば。「Even」を表示します。違えば。「Odd」を表示します。ここまで。

もうひとつ例を挙げます。

問題: 「$0$ と $1$ のみから成る $3$ 桁の番号 $s$ が与えられます。$1$ が何個含まれるかを求めてください。」
(AtCoder ABC 081 A - Placing Marbles)

C++ での解答:

#include <iostream>
#include <string>
using namespace std;

int main() {
    string s;
    cin >> s;
    int counter = 0;
    if (s[0] == '1') ++counter;
    if (s[1] == '1') ++counter;
    if (s[2] == '1') ++counter;
    cout << counter << endl;
}

なでしこ での解答:

尋ねる。それを「1」で区切る。それの要素数から1を引いて表示する。

本回答では、 それの要素数から「1」を引いて表示する。 という部分で、なでしこの 連文の仕組み を利用して、1つの文で、 引き算表示 という 2つの命令を実行しています。
先の例よりも、より自然言語のような記載に感じると思います。

他の例にも興味がある人は、
AtCoder に登録したら解くべき精選過去問 10 問 を なでしこ で解いてみた - Qiita
の記事を参照して下さい。

 

いかがでしたか?

自然言語に近いプログラミング言語も、
意外と、分かりやすい と感じたのではないでしょうか?

 

しかし、 なでしこ は プログラミング言語、つまり、形式言語がベース です。

なでしこのプログラミング言語の文法は、 日本語の表記に合わせているため、
読むときは、プログラミング経験がなくても意味が分かる ように記載できますが、
プログラムを書くときには、形式言語の文法を意識する必要 があります。

自然言語の文法でプログラミングできるわけではありません。

本記事では、 自然言語の文法でプログラミングできること を、
自然言語プロプログラミング と定義します。

なでしこ自然言語プログラミング として考えた場合の課題を見ていきます。

【補足】
プログラムを書くときには、命令とデータ構造の整理が必要 であり、
プログラミング言語は、それに適した形式をとっているため、
自然言語の文法でプログラムを書けることが、最適とは限りません。

「なでしこの、自然言語プログラミングへの課題」は、
なでしこ自体の課題ではない ことを留意して下さい。

なでしこの、自然言語プログラミングへの課題

変数名のひらがなへの制約

まずは、以下の なでしこのプログラム を見て下さい。

ハローワールドは、「Hello World」。ハローワールドを、表示する。

上記、プログラムは、 "Hello World" と表示するプログラムです。
上記の ハローワールド変数名 です。なでしこでは、 変数名 は日本語(全角)も使用できます。

はろーわーるどは、「Hello World」。はろーわーるどを、表示する。

上記のプログラムも実行すると、 "Hello World" が表示されます。
上記の はろーわーるど変数名 です。 変数名 には平仮名が使えます。

では、以下はどうでしょうか。

こんにちはは、「Hello World」。こんにちはを、表示する。

[文法エラー]undefined(1行目): 『こん』『演算子[eq]』『「Hello World」』がありますが文が解決していません。『代入』や『表示』などと一緒に使ってください。

こんにちは変数名 として解釈されていません。
こんにちは「は」助詞 なので、日本語としてもおかしい。という指摘が入りそうですね。

では、以下はどうでしょうか。

かには、「赤」。かにを、表示する。

[文法エラー]undefined(1行目): 『「赤」』がありますが使い方が分かりません。

かに変数名 として解釈されていません。
上記を えび に変えると、 "赤" と表示されます。

また、以下のような 修飾されている 名詞 も、うまく 変数名 として解釈されません。

ボブの所持金は、「100円」。ボブの所持金を、表示する。

[文法エラー]undefined(1行目): 『ボブ』『演算子[eq]』がありますが文が解決していません。『代入』や『表示』などと一緒に使ってください。

この動きを確認するため、
なでしこv3 のGITHUB からコードを確認してみましょう。

nako_josi_list.js には、以下の 助詞 の一覧を定義したコードがあります。

const josiList = [
  'について', 'くらい', 'なのか', 'までを', 'までの',
  'とは', 'から', 'まで', 'だけ', 'より', 'ほど', 'など',
  'いて', 'えて', 'きて', 'けて', 'して', 'って', 'にて', 'みて',
  'めて', 'ねて', 'では', 'には', 'は~',
  'は', 'を', 'に', 'へ', 'で', 'と', 'が', 'の'
]

const tararebaJosiList = [
  'でなければ', 'ならば', 'なら', 'たら', 'れば'
]

上記のリストが 助詞 として判定されるため、
かに のような、上記の文字が 2文字目以降にある 変数名 は、使用できなそうです。

これは、プログラミング言語 なでしこ として、仕様 だと思います。

ほとんどのプログラミング言語では、予約語 が存在し、
予約語変数名 としては使えません。
なでしこでは、助詞予約語 として考えることができそうです。

変数名 として、ひらがなを避けるという対応で、
この問題は、プログラミング言語 なでしこ
としては気にならないものになるでしょう。

 

しかし、上記により、修飾されている 名詞 を変数名として扱えないことを含めて、
自然言語プログラミングとしては、課題 となるでしょう。

文章構造への制限

11に7を4で割った余りを掛けて表示する。

これは、$33$ と表示されます。
$11 * (7 \bmod 4)$ と正しく解釈されていることが分かります。

しかし、

11を7を4で割った余りで割った余りを表示する。

これは、$1$ と表示されます。
$11 \bmod (7 \bmod 4)$ と解釈してほしいところ、
$7 \bmod (11 \bmod 4)$ と解釈されてしまっているからです。

これは、読点で、以下のように区切っても結果は変わりません。

11を、7を4で割った余りで、割った余りを表示する。

これを正しく解釈させるには、

11を(7を4で割った余り)で割った余りを表示する。

と小括弧で囲む必要があります。

これは、 なでしこの基本 に記載がある通り、
命令文の基本的な文系が、

引数+助詞、引数+助詞、 ... 関数。

という、形なので、 助詞が異なる関数の組み合わせ の解釈はうまくいくが、
助詞が同じ関数の組み合わせ の解釈が難しいためと思われます。

ただし、これも、プログラミング言語 なでしこ として、仕様 だと思います。

ほとんどの プログラミング言語では、記号を活用して文形を決定 します。
なでしこでも、組合せが必要な文形は明示的に () を使用すべきでしょう。
(読点は、区切りを明示的に示したい場合に使います。)

また、なでしこでは、それを使用して、直前の関数の結果を得られるので、
文を短くした方が、可読性も高く、修正もしやすくなります。

 

しかし、上記のように、組み合わされた文の結果が、自然言語の解釈と異なるのは、
自然言語プログラミングとしては、課題 となるでしょう。

COTOHA APIについて

現在、キャンペーン が行われている、COTOHA API
自然言語処理や音声処理を簡単に行えるAPIを提供してくれるサービス です。

このサービスの中に、構文解析API が存在します。

このAPIを使うことで、以下が可能となります。

  • 入力された文を文節・形態素に分解
  • 形態素情報の品詞を取得
  • 文節間の係り受け関係を取得
  • 形態素間の係り受け関係を取得

お手軽に(APIを叩くだけで)、
形態素解析 + 係り受け解析 までしてくれる、
優れものです。

なでしこ feat. ことは の検証

形態素解析や係り受け解析は、自然言語をコンピュータで扱いやすくするために用いられることが多く、
自然言語プログラミング を作成するためには、これをお手軽に利用できる
COTOHA API(以下、「ことは」) は非常に強力 です。

また、なでしこ(v3) 形式言語ベースではありますが、自然言語向きの構文を扱え、
長年のノウハウにより、プログラムに必要な命令もそろっています。

前置きが長くなりましたが、本記事では、なでしこ(v3) をベースとして利用しつつ、
COTOHA APIの構文解析ベースのプログラム言語 を作成し、

自然言語プログラミングという観点で見た場合の、なでしこの課題を解決できるか

を検証していきます。

「なでしこ」 と 「ことは」 の構文解析の比較

実装に入る前に、まずは「なでしこ」と「ことは」の構文解析を比較してみます。

まずは

「Hello World」を、表示する。

というプログラムについて、解析結果を比較してみます。

 

まずは、なでしこ の構文解析からです。

なでしこ ではコマンドラインでの実行(cnako3)において、「-D」オプションをつけて実行すると、
以下のように、 LEX(字句解析)AST(抽象構文木)の情報を出力できます。

※ 以下jsonのハイライトのため、一部コードブロックを分割します。

$ node src/cnako3.js -D -e "「Hello World」を、表示する。"
--- lex ---
[
  {
    "type": "string",
    "value": "Hello World",
    "josi": "を",
    "line": 0
  },
  {
    "type": "func",
    "value": "表示",
    "josi": "",
    "line": 0,
    "meta": {
      "type": "func",
      "josi": [
        [
          "を",
          "と"
        ]
      ],
      "return_none": true
    }
  },
  {
    "type": "eol",
    "value": "",
    "line": 0,
    "josi": ""
  },
  {
    "type": "eol",
    "value": "",
    "line": 1,
    "josi": ""
  },
  {
    "type": "eol",
    "line": 1,
    "josi": "",
    "value": "---"
  },
  {
    "type": "eof",
    "line": 1,
    "josi": "",
    "value": ""
  }
]
--- ast ---
{
  "type": "block",
  "block": [
    {
      "type": "func",
      "name": "表示",
      "args": [
        {
          "type": "string",
          "value": "Hello World",
          "josi": "を",
          "line": 0
        }
      ],
      "josi": "",
      "line": 0
    },
    {
      "type": "eol",
      "value": "",
      "line": 0,
      "josi": ""
    },
    {
      "type": "eol",
      "value": "",
      "line": 1,
      "josi": ""
    },
    {
      "type": "eol",
      "line": 1,
      "josi": "",
      "value": "---"
    }
  ],
  "line": 0
}
--- generate ---
const __v0 = this.__v0 = this.__varslist[0];
const __v1 = this.__v1 = this.__varslist[1];
__v0.line=0;// プラグインの初期化
__v0["!PluginSystem:初期化"](__self);
__v0["!PluginNode:初期化"](__self);
__vars["それ"] = '';
__v0["表示"]("Hello World",__self);
; __v0.line=0;//
; __v0.line=1;//
; __v0.line=1;// ---

Hello World

特筆すべきは、 AST(抽象構文木)上args(引数)josi(助詞) が含まれている点です。
なでしこ では、プログラムを書くときに、 その関数の助詞 を確認します。
たとえば 「引」関数 は、

(AからBを)引

という構文であり、

3から1を引いて表示する。

というプログラムの実行結果は「2」となりますが、この順番を変えて、

1を3から引いて表示する。

というプログラムも、正しい文であり、「2」と表示されます。

つまり、 なでしこ では、常に助詞によるキーワード引数 を想定していると解釈できます。
 

次は ことは の構文解析です。

ことは は RESTful API で、使用言語は問いませんが、今回はnode.jsを使います。
node.jsでの取得スクリプトとその結果を示します。

cotoha_request.js
const request = require('request');

const DEVELOPER_API_BASE_URL   = "https://api.ce-cotoha.com/api/dev/";
const ACCESS_TOKEN_PUBLISH_URL = "https://api.ce-cotoha.com/v1/oauth/accesstokens";
const CLIENT_ID     = "ココニアイデイカク";
const CLIENT_SECRET = "ココニシークレットカク";

const main = async () => {
  let accessToken = await getAccessToken();
  let parse = await getParse(accessToken, process.argv[2]);
  console.log(JSON.stringify(parse, null, '  '));
}

const getAccessToken = () => {
  return new Promise((resolve, reject) => {
    request(
      {
        url: ACCESS_TOKEN_PUBLISH_URL,
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        json: {
          grantType: "client_credentials",
          clientId: CLIENT_ID,
          clientSecret: CLIENT_SECRET,
        },
      },
      (error, response, body) => {
        if (!error && (response.statusCode === 200 || response.statusCode === 201)) {
          if (typeof body !== 'object') body = JSON.parse(body);
          resolve(body.access_token);
        } else {
          if (error) {
            console.log(`request fail. error: ${error}`);
          } else {
            console.log(`request fail. response.statusCode: ${response.statusCode}, ${body}`);
          }
          reject(body);
        }
      },
    );
  });
}

const getParse = (accessToken, document) => {
  return new Promise((resolve, reject) => {
    request(
      {
        url: `${DEVELOPER_API_BASE_URL}nlp/v1/parse`,
        method: 'POST',
        headers: { 'Content-Type': 'application/json;charset=UTF-8', Authorization: `Bearer ${accessToken}`},
        json: { sentence: document },
      },
      (error, response, body) => {
        if (!error && (response.statusCode === 200 || response.statusCode === 201)) {
          if (typeof body !== 'object') body = JSON.parse(body);
          if (body.status === 0) {
            resolve(body.result);
          } else {
            console.log(`request fail. error: ${body.message}`);
            reject(body);
          }
        } else {
          if (error) {
            console.log(`request fail. error: ${error}`);
          } else {
            msg = (typeof body !== 'object') ? body : JSON.stringify(body);
            console.log(`request fail. response.statusCode: ${response.statusCode}, ${msg}`);
          }
          reject(body);
        }
      }
    );
  });
}
$ node cotoha_request.js "「Hello World」、を表示する"
[
  {
    "chunk_info": {
      "id": 0,
      "head": 1,
      "dep": "D",
      "chunk_head": 1,
      "chunk_func": 4,
      "links": []
    },
    "tokens": [
      {
        "id": 0,
        "form": "「",
        "kana": "",
        "lemma": "「",
        "pos": "括弧",
        "features": [
          "開括弧"
        ],
        "attributes": {}
      },
      {
        "id": 1,
        "form": "hello world",
        "kana": "ハローワールド",
        "lemma": "hello world",
        "pos": "名詞",
        "features": [],
        "dependency_labels": [
          {
            "token_id": 0,
            "label": "punct"
          },
          {
            "token_id": 2,
            "label": "punct"
          },
          {
            "token_id": 3,
            "label": "punct"
          },
          {
            "token_id": 4,
            "label": "case"
          }
        ],
        "attributes": {}
      },
      {
        "id": 2,
        "form": "」",
        "kana": "",
        "lemma": "」",
        "pos": "括弧",
        "features": [
          "閉括弧"
        ],
        "attributes": {}
      },
      {
        "id": 3,
        "form": "、",
        "kana": "",
        "lemma": "、",
        "pos": "読点",
        "features": [],
        "attributes": {}
      },
      {
        "id": 4,
        "form": "を",
        "kana": "ヲ",
        "lemma": "を",
        "pos": "格助詞",
        "features": [
          "連用"
        ],
        "attributes": {}
      }
    ]
  },
  {
    "chunk_info": {
      "id": 1,
      "head": -1,
      "dep": "O",
      "chunk_head": 0,
      "chunk_func": 1,
      "links": [
        {
          "link": 0,
          "label": "object"
        }
      ],
      "predicate": []
    },
    "tokens": [
      {
        "id": 5,
        "form": "表示",
        "kana": "ヒョウジ",
        "lemma": "表示",
        "pos": "名詞",
        "features": [
          "動作"
        ],
        "dependency_labels": [
          {
            "token_id": 1,
            "label": "dobj"
          },
          {
            "token_id": 6,
            "label": "aux"
          }
        ],
        "attributes": {}
      },
      {
        "id": 6,
        "form": "する",
        "kana": "スル",
        "lemma": "する",
        "pos": "動詞接尾辞",
        "features": [
          "終止"
        ],
        "attributes": {}
      }
    ]
  }
]

特筆すべきは、構文解析の結果が、 chunk(文節)token(形態素) の2段階の構造になっていることです。
chunk(文節) は、 links(掛かり元情報の配列) として、 掛かり元のchunk(文節)の情報を保持し、
token(形態素) は、 dependency_labels(依存先情報の配列) として、 依存先のtoken(形態素)の情報 を保持しています。

 

上記の、 なでしこことは の解析結果をもう少し詳しく比較します。
※ 以下は解析結果を必要か所に絞って記載します。

なでしこ は、 lex(字句解析) の段階で、以下のように2つの文節に区切り、助詞は文節内のプロパティとして表現されています。

なでしこのlex
[
  { "type": "string", "value": "Hello World", "josi": "を" },
  { "type": "func", "value": "表示", "josi": "" }
]

AST(抽象構文木) では、各文節間の関係が整理されています。

なでしこのAST
{
  "type": "block", "block": [
    { "type": "func", "name": "表示", "args": [
      { "type": "string", "value": "Hello World", "josi": "を" }
    ],}
  ]
}

一方で ことはchunk(文節)token(形態素) の2段階となり、付与されている情報も多くなります。

ことは
[
  {
    "chunk_info": { "id": 0, "head": 1, "dep": "D", "links": [
    ]},
    "tokens": [
      { "id": 0, "form": "「", "lemma": "「", "pos": "括弧", "features": [ "開括弧" ], "attributes": {} },
      { "id": 1, "form": "hello world", "lemma": "hello world", "pos": "名詞", "features": [], "attributes": {},  "dependency_labels": [
          { "token_id": 0, "label": "punct" },
          { "token_id": 2, "label": "punct" },
          { "token_id": 3, "label": "punct" },
          { "token_id": 4, "label": "case" }
        ],
      }, 
      { "id": 2, "form": "」", "lemma": "」", "pos": "括弧", "features": [ "閉括弧" ], "attributes": {} },
      { "id": 3, "form": "、", "lemma": "、", "pos": "読点", "features": [], "attributes": {} },
      { "id": 4, "form": "を", "kana": "ヲ", "lemma": "を", "pos": "格助詞", "features": [ "連用" ], "attributes": {} }
    ]
  },
  {
    "chunk_info": { "id": 1, "head": -1, "dep": "O", "links": [
      { "link": 0, "label": "object" }
    ]},
    "tokens": [
      { "id": 5, "form": "表示", "lemma": "表示", "pos": "名詞", "features": [ "動作" ], "attributes": {}, "dependency_labels": [
        { "token_id": 1, "label": "dobj" },
        { "token_id": 6, "label": "aux" }
        ]
      },
      { "id": 6, "form": "する", "lemma": "する", "pos": "動詞接尾辞", "features": [ "終止" ], "attributes": {}
      }
    ]
  }
]

ことは 側の chunk(文節) と、 なでしこ 側の抽象構文木の要素単位は一致しています。
ただ、 「表示」する関数の引数 を決めるための方法は、 なでしこ では 助詞を用いており、この情報も ことは 側は保持しています。
しかし、ことはが掛かり受けを解析し、かつ、labelとして意味役割ラベル を付与していることを考えると、 助詞を見ずに、chunk(文節)のlinks(掛かり元情報の配列)を使用 するほうが、自然言語に対応しやすいでしょう。

以上の比較から、本プログラムについては、以下の変換をすることで、 ことは 側のレスポンスを AST((抽象構文木) に変換できそうです。

  • なでしこの構文解析と同じように、括弧で囲まれた部分は文字列リテラル と扱う。
  • tokensのfeaturesに 動作 が含まれている部分の chunk(文節) を関数 とする。
  • 関数の引数は、chunk(文節) の links(掛かり元情報の配列) から求め、 labelをキーワード引数相当の扱い とする。
  • 「表示」関数の「を」助詞の場合、 labelは object(動作・変化の影響を受ける対象)

 

では、本ルールで他のプログラムも変換できるか確認していきます。

変数と代入を伴うプログラム

ハローワールドは、「Hello World」。ハローワールドを、表示する。

なでしこのlex
[
  { "type": "word", "value": "ハローワールド", "josi": "", },
  { "type": "eq", },
  { "type": "string", "value": "Hello World", "josi": "", },
  { "type": "word", "value": "ハローワールド", "josi": "を", },
  { "type": "func", "value": "表示", "josi": "" },
]
なでしこのast
{
  "type": "block", "block": [
    {
      "type": "let",
      "name": { "type": "word", "value": "ハローワールド", "josi": "" } ,
      "value": { "type": "string", "value": "Hello World", "josi": "" }
    },
    { "type": "func", "name": "表示", "args": [
        { "type": "word", "value": "ハローワールド", "josi": "を" }
    ],}
  ],
}
ことは
[
  {
    "chunk_info": { "id": 0, "head": 3, "dep": "D", "links": [
    ]},
    "tokens": [
      { "id": 0, "form": "ハローワールド", "lemma": "ハローワールド", "pos": "名詞", "features": [], "attributes": {}, "dependency_labels": [
          { "token_id": 1, "label": "case" },
          { "token_id": 2, "label": "punct" }
        ],
      },
      { "id": 1, "form": "は", "lemma": "は", "pos": "連用助詞", "features": [], "attributes": {}
      },
      { "id": 2, "form": "、", "lemma": "、", "pos": "読点", "features": [], "attributes": {}
      }
    ]
  },
  {
    "chunk_info": { "id": 1, "head": 2, "dep": "P", "links": [
    ]},
    "tokens": [
      { "id": 3, "form": "「", "lemma": "「",  "pos": "括弧", "attributes": {}, "features": [ "開括弧" ]},
      { "id": 4, "form": "hello world", "lemma": "hello world", "pos": "名詞", "attributes": {}, "features": [], "dependency_labels": [
          { "token_id": 3, "label": "punct" },
          { "token_id": 5, "label": "punct" },
          { "token_id": 6, "label": "punct" }
        ]
      },
      { "id": 5, "form": "」", "lemma": "」", "pos": "括弧", "features": [ "閉括弧" ], "attributes": {} },
      { "id": 6, "form": "。", "lemma": "。", "pos": "句点", "features": [], "attributes": {} }
    ]
  },
  {
    "chunk_info": { "id": 2, "head": 3, "dep": "D", "links": [
      { "link": 1, "label": "other" }
    ]},
    "tokens": [
      { "id": 7, "form": "ハロー", "lemma": "ハロー", "pos": "名詞", "features": [], "attributes": {} },
      { "id": 8,  "form": "ワールド", "lemma": "ワールド", "pos": "名詞", "features": [], "attributes": {}, "dependency_labels": [
          { "token_id": 4, "label": "nmod" },
          { "token_id": 7, "label": "compound" },
          { "token_id": 9, "label": "case" },
          { "token_id": 10, "label": "punct" }
        ]
      },
      { "id": 9, "form": "を", "lemma": "を", "pos": "格助詞", "features": [ "連用" ], "attributes": {} },
      { "id": 10, "form": "、", "lemma": "、", "pos": "読点", "features": [], "attributes": {} }
    ]
  },
  {
    "chunk_info": { "id": 3, "head": -1, "dep": "O", "links": [
      { "link": 0, "label": "agent" },
      { "link": 2, "label": "object" }
    ]},
    "tokens": [
      { "id": 11, "form": "表示", "lemma": "表示", "pos": "名詞", "features": [ "動作" ], "attributes": {}, "dependency_labels": [
          { "token_id": 0, "label": "nsubj" },
          { "token_id": 8, "label": "dobj" },
          { "token_id": 12, "label": "aux" },
          { "token_id": 13, "label": "punct" }
        ]
      },
      { "id": 12, "form": "する", "lemma": "する", "pos": "動詞接尾辞", "features": [ "終止" ], "attributes": {} },
      { "id": 13, "form": "。", "lemma": "。", "pos": "句点", "features": [], "attributes": {}}
    ]
  }
]

残念ながら、最初の代入で chunk(文節) の links(掛かり元情報の配列) から求められない結果となりました。ただし、よく見ると文をまたがって掛かり元情報が設定されています。

形式言語のプログラムでは、 文をまたがって、引数や代入を行うことはなく、変数を利用して文の関連を持たせる ため、 自然言語プログラミング場合、ことは には文単位で構文解析をさせる のを試してみましょう。

ハローワールドは、「Hello World」。

ことは
[
  {
    "chunk_info": { "id": 0, "head": 1, "dep": "D", "links": [
    ]},
    "tokens": [
      { "id": 0, "form": "ハローワールド", "lemma": "ハローワールド", "pos": "名詞",  "features": [], "attributes": {}, "dependency_labels":
        [
          { "token_id": 1, "label": "case" },
          { "token_id": 2, "label": "punct" }
        ]
      },
      { "id": 1, "form": "は", "lemma": "は", "pos": "連用助詞", "features": [], "attributes": {} },
      { "id": 2, "form": "、", "lemma": "、", "pos": "読点", "features": [], "attributes": {} }
    ]
  },
  {
    "chunk_info": { "id": 1, "head": -1, "dep": "O",  "links": [
        { "link": 0, "label": "agent" }
      ]
    },
    "tokens": [
      { "id": 3, "form": "「", "lemma": "「", "pos": "括弧", "features": [ "開括弧" ], "attributes": {} },
      { "id": 4, "form": "hello world", "lemma": "hello world", "pos": "名詞", "features": [], "attributes": {}, "dependency_labels":
        [
          { "token_id": 0, "label": "nsubj" },
          { "token_id": 3, "label": "punct" },
          { "token_id": 5, "label": "punct" },
          { "token_id": 6, "label": "punct" }
        ]
      },
      { "id": 5, "form": "」", "kana": "", "lemma": "」", "pos": "括弧", "features": [ "閉括弧" ], "attributes": {} },
      { "id": 6, "form": "。", "kana": "", "lemma": "。", "pos": "句点", "features": [], "attributes": {} }
    ]
  }
]

期待している通りに chunk(文節) の links(掛かり元情報の配列) が設定されました。

この結果から以下のルールが必要そうです。

  • リテラルや関数として判定されなかったchunkのうち、pos(品詞) が 名詞 のtoken の部分を 変数として扱う。
  • label が agent(有意動作を引き起こす主体)単体である場合 代入文として扱う。

また、文単位に区切ってAPI呼出をしたことについては、現状では、ことは の構文解析APIには、文単位で区切って与えたほうが正しく解釈してくれるようです。
(引数も documento ではなく、 sentence(構文対象文)としているので、使い方としても正しそうです。)
そこで、今回は ことはのAPIを実行するスクリプトを以下のように修正し、 を文区切りとして、文毎にAPIを呼出し、結果を、chunksプロパティに保持する配列としてマージします。
(もちろん、検証用途でなければ、リテラル中の に対応するなどが必要で、文区切りは ではなく、きちんと方式を決める必要があります。)

cotoha_request.js
const request = require('request');
const fs = require('fs');

const DEVELOPER_API_BASE_URL   = "https://api.ce-cotoha.com/api/dev/";
const ACCESS_TOKEN_PUBLISH_URL = "https://api.ce-cotoha.com/v1/oauth/accesstokens";
const CLIENT_ID     = "ココニアイデイカク";
const CLIENT_SECRET = "ココニシークレットカク";

const main = async () => {
  let accessToken = await getAccessToken();
  document = fs.readFileSync(process.argv[2], 'utf-8');
  let ret = [];
  let sentences = document.split('').map(s => s.trim()).filter(s => s);
  for (let i = 0; i < sentences.length; i++) {
    let parse = await getParse(accessToken, sentences[i]);
    ret.push({ id: i, chunks: parse });
    await sleep(1000);
  };
  fs.writeFileSync(`${process.argv[2]}.kotoha.json`, JSON.stringify(ret, null, '  '));
}

const sleep = delay  => new Promise(resolve => setTimeout(resolve, delay));

const getAccessToken = () => {
  return new Promise((resolve, reject) => {
    request(
      {
        url: ACCESS_TOKEN_PUBLISH_URL,
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        json: {
          grantType: "client_credentials",
          clientId: CLIENT_ID,
          clientSecret: CLIENT_SECRET,
        },
      },
      (error, response, body) => {
        if (!error && (response.statusCode === 200 || response.statusCode === 201)) {
          if (typeof body !== 'object') body = JSON.parse(body);
          resolve(body.access_token);
        } else {
          if (error) {
            console.log(`request fail. error: ${error}`);
          } else {
            console.log(`request fail. response.statusCode: ${response.statusCode}, ${body}`);
          }
          reject(body);
        }
      },
    );
  });
}

const getParse = (accessToken, sentence) => {
  return new Promise((resolve, reject) => {
    request(
      {
        url: `${DEVELOPER_API_BASE_URL}nlp/v1/parse`,
        method: 'POST',
        headers: { 'Content-Type': 'application/json;charset=UTF-8', Authorization: `Bearer ${accessToken}`},
        json: { sentence: sentence },
      },
      (error, response, body) => {
        if (!error && (response.statusCode === 200 || response.statusCode === 201)) {
          if (typeof body !== 'object') body = JSON.parse(body);
          if (body.status === 0) {
            resolve(body.result);
          } else {
            console.log(`request fail. error: ${body.message}`);
            reject(body);
          }
        } else {
          if (error) {
            console.log(`request fail. error: ${error}`);
          } else {
            msg = (typeof body !== 'object') ? body : JSON.stringify(body);
            console.log(`request fail. response.statusCode: ${response.statusCode}, ${msg}`);
          }
          reject(body);
        }
      }
    );
  });
}

main();

では、これを用いて再度以下の文を試してみます。

ハローワールドは、「Hello World」。ハローワールドを、表示する。

ことは のレスポンスを確認(クリック)
ことは
[
  {
    "id": 0,
    "chunks": [
      {
        "chunk_info": {
          "id": 0,
          "head": 1,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 0,
            "form": "ハローワールド",
            "kana": "ハローワールド",
            "lemma": "ハローワールド",
            "pos": "名詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 1,
                "label": "case"
              },
              {
                "token_id": 2,
                "label": "punct"
              }
            ],
            "attributes": {}
          },
          {
            "id": 1,
            "form": "は",
            "kana": "ハ",
            "lemma": "は",
            "pos": "連用助詞",
            "features": [],
            "attributes": {}
          },
          {
            "id": 2,
            "form": "、",
            "kana": "",
            "lemma": "、",
            "pos": "読点",
            "features": [],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 1,
          "head": -1,
          "dep": "O",
          "chunk_head": 1,
          "chunk_func": 1,
          "links": [
            {
              "link": 0,
              "label": "agent"
            }
          ],
          "predicate": []
        },
        "tokens": [
          {
            "id": 3,
            "form": "「",
            "kana": "",
            "lemma": "「",
            "pos": "括弧",
            "features": [
              "開括弧"
            ],
            "attributes": {}
          },
          {
            "id": 4,
            "form": "hello world",
            "kana": "ハローワールド",
            "lemma": "hello world",
            "pos": "名詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 0,
                "label": "nsubj"
              },
              {
                "token_id": 3,
                "label": "punct"
              },
              {
                "token_id": 5,
                "label": "punct"
              }
            ],
            "attributes": {}
          },
          {
            "id": 5,
            "form": "」",
            "kana": "",
            "lemma": "」",
            "pos": "括弧",
            "features": [
              "閉括弧"
            ],
            "attributes": {}
          }
        ]
      }
    ]
  },
  {
    "id": 1,
    "chunks": [
      {
        "chunk_info": {
          "id": 0,
          "head": 1,
          "dep": "D",
          "chunk_head": 1,
          "chunk_func": 2,
          "links": []
        },
        "tokens": [
          {
            "id": 0,
            "form": "ハロー",
            "kana": "ハロー",
            "lemma": "ハロー",
            "pos": "名詞",
            "features": [],
            "attributes": {}
          },
          {
            "id": 1,
            "form": "ワールド",
            "kana": "ワールド",
            "lemma": "ワールド",
            "pos": "名詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 0,
                "label": "compound"
              },
              {
                "token_id": 2,
                "label": "case"
              },
              {
                "token_id": 3,
                "label": "punct"
              }
            ],
            "attributes": {}
          },
          {
            "id": 2,
            "form": "を",
            "kana": "ヲ",
            "lemma": "を",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          },
          {
            "id": 3,
            "form": "、",
            "kana": "",
            "lemma": "、",
            "pos": "読点",
            "features": [],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 1,
          "head": -1,
          "dep": "O",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 0,
              "label": "object"
            }
          ],
          "predicate": []
        },
        "tokens": [
          {
            "id": 4,
            "form": "表示",
            "kana": "ヒョウジ",
            "lemma": "表示",
            "pos": "名詞",
            "features": [
              "動作"
            ],
            "dependency_labels": [
              {
                "token_id": 1,
                "label": "dobj"
              },
              {
                "token_id": 5,
                "label": "aux"
              }
            ],
            "attributes": {}
          },
          {
            "id": 5,
            "form": "する",
            "kana": "スル",
            "lemma": "する",
            "pos": "動詞接尾辞",
            "features": [
              "終止"
            ],
            "attributes": {}
          }
        ]
      }
    ]
  }
]

前半部(1文目)は、今までのルールで問題なさそうですが、
後半部(2文目)は、「ハローワールド」が token(形態素)では、「ハロー」と「ワールド」に分かれていて、ともに品詞が「名詞」です。
細かいですが、前のルールの

  • リテラルや関数として判定されなかったchunkのうち、pos(品詞) が 名詞 のtoken の部分を 変数として扱う。

は、以下のように修正する必要がありそうです。

  • リテラルや関数として判定されなかったchunkのうち、pos(品詞) が 名詞 のtoken の部分を 変数として扱う。名詞が複数ある場合は、連続している名詞を連結する。

次に、これを用いて なでしこ では文法エラーになるような他の文を試してみます。

こんにちはは、「Hello World」。こんにちはを、表示する。

ことは のレスポンスを確認(クリック)
ことは
[
  {
    "id": 0,
    "chunks": [
      {
        "chunk_info": {
          "id": 0,
          "head": 1,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 0,
            "form": "こんにちは",
            "kana": "コンニチハ",
            "lemma": "こんにちは",
            "pos": "独立詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 1,
                "label": "case"
              },
              {
                "token_id": 2,
                "label": "punct"
              }
            ],
            "attributes": {}
          },
          {
            "id": 1,
            "form": "は",
            "kana": "ハ",
            "lemma": "は",
            "pos": "連用助詞",
            "features": [],
            "attributes": {}
          },
          {
            "id": 2,
            "form": "、",
            "kana": "",
            "lemma": "、",
            "pos": "読点",
            "features": [],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 1,
          "head": -1,
          "dep": "O",
          "chunk_head": 1,
          "chunk_func": 1,
          "links": [
            {
              "link": 0,
              "label": "agent"
            }
          ],
          "predicate": []
        },
        "tokens": [
          {
            "id": 3,
            "form": "「",
            "kana": "",
            "lemma": "「",
            "pos": "括弧",
            "features": [
              "開括弧"
            ],
            "attributes": {}
          },
          {
            "id": 4,
            "form": "hello world",
            "kana": "ハローワールド",
            "lemma": "hello world",
            "pos": "名詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 0,
                "label": "nsubj"
              },
              {
                "token_id": 3,
                "label": "punct"
              },
              {
                "token_id": 5,
                "label": "punct"
              }
            ],
            "attributes": {}
          },
          {
            "id": 5,
            "form": "」",
            "kana": "",
            "lemma": "」",
            "pos": "括弧",
            "features": [
              "閉括弧"
            ],
            "attributes": {}
          }
        ]
      }
    ]
  },
  {
    "id": 1,
    "chunks": [
      {
        "chunk_info": {
          "id": 0,
          "head": 1,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 0,
            "form": "こんにちは",
            "kana": "コンニチハ",
            "lemma": "こんにちは",
            "pos": "独立詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 1,
                "label": "case"
              },
              {
                "token_id": 2,
                "label": "punct"
              }
            ],
            "attributes": {}
          },
          {
            "id": 1,
            "form": "を",
            "kana": "ヲ",
            "lemma": "を",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          },
          {
            "id": 2,
            "form": "、",
            "kana": "",
            "lemma": "、",
            "pos": "読点",
            "features": [],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 1,
          "head": -1,
          "dep": "O",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 0,
              "label": "object"
            }
          ],
          "predicate": []
        },
        "tokens": [
          {
            "id": 3,
            "form": "表示",
            "kana": "ヒョウジ",
            "lemma": "表示",
            "pos": "名詞",
            "features": [
              "動作"
            ],
            "dependency_labels": [
              {
                "token_id": 0,
                "label": "dobj"
              },
              {
                "token_id": 4,
                "label": "aux"
              }
            ],
            "attributes": {}
          },
          {
            "id": 4,
            "form": "する",
            "kana": "スル",
            "lemma": "する",
            "pos": "動詞接尾辞",
            "features": [
              "終止"
            ],
            "attributes": {}
          }
        ]
      }
    ]
  }
]

単語の区切り、係り受け含めて 問題なく 解釈できそうです。
ただし、 こんにちは の品詞が 独立詞 であることを考慮すると、

  • リテラルや関数として判定されなかったchunkのうち、pos(品詞) が 名詞 のtoken の部分を 変数として扱う。名詞が複数ある場合は、連続している名詞を連結する。

  • リテラルや関数として判定されなかったchunkのうち、pos(品詞) が 名詞 または 独立詞のtoken の部分を 変数として扱う。名詞 または 独立詞 が複数ある場合は、連続している名詞 または 独立詞を連結する。

に修正する必要があります。

かには、「赤」。かにを、表示する。

ことは のレスポンスを確認(クリック)
ことは
[
  {
    "id": 0,
    "chunks": [
      {
        "chunk_info": {
          "id": 0,
          "head": 1,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 0,
            "form": "か",
            "kana": "カ",
            "lemma": "か",
            "pos": "判定詞",
            "features": [
              "名詞"
            ],
            "dependency_labels": [
              {
                "token_id": 1,
                "label": "dep"
              },
              {
                "token_id": 2,
                "label": "punct"
              }
            ],
            "attributes": {}
          },
          {
            "id": 1,
            "form": "には",
            "kana": "ニハ",
            "lemma": "には",
            "pos": "連用助詞",
            "features": [],
            "attributes": {}
          },
          {
            "id": 2,
            "form": "、",
            "kana": "",
            "lemma": "、",
            "pos": "読点",
            "features": [],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 1,
          "head": -1,
          "dep": "O",
          "chunk_head": 1,
          "chunk_func": 1,
          "links": [
            {
              "link": 0,
              "label": "other"
            }
          ]
        },
        "tokens": [
          {
            "id": 3,
            "form": "「",
            "kana": "",
            "lemma": "「",
            "pos": "括弧",
            "features": [
              "開括弧"
            ],
            "attributes": {}
          },
          {
            "id": 4,
            "form": "赤",
            "kana": "アカ",
            "lemma": "赤",
            "pos": "名詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 0,
                "label": "dep"
              },
              {
                "token_id": 3,
                "label": "punct"
              },
              {
                "token_id": 5,
                "label": "punct"
              }
            ],
            "attributes": {}
          },
          {
            "id": 5,
            "form": "」",
            "kana": "",
            "lemma": "」",
            "pos": "括弧",
            "features": [
              "閉括弧"
            ],
            "attributes": {}
          }
        ]
      }
    ]
  },
  {
    "id": 1,
    "chunks": [
      {
        "chunk_info": {
          "id": 0,
          "head": 1,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 0,
            "form": "かに",
            "kana": "カニ",
            "lemma": "蟹",
            "pos": "名詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 1,
                "label": "case"
              },
              {
                "token_id": 2,
                "label": "punct"
              }
            ],
            "attributes": {}
          },
          {
            "id": 1,
            "form": "を",
            "kana": "ヲ",
            "lemma": "を",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          },
          {
            "id": 2,
            "form": "、",
            "kana": "",
            "lemma": "、",
            "pos": "読点",
            "features": [],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 1,
          "head": -1,
          "dep": "O",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 0,
              "label": "object"
            }
          ],
          "predicate": []
        },
        "tokens": [
          {
            "id": 3,
            "form": "表示",
            "kana": "ヒョウジ",
            "lemma": "表示",
            "pos": "名詞",
            "features": [
              "動作"
            ],
            "dependency_labels": [
              {
                "token_id": 0,
                "label": "dobj"
              },
              {
                "token_id": 4,
                "label": "aux"
              }
            ],
            "attributes": {}
          },
          {
            "id": 4,
            "form": "する",
            "kana": "スル",
            "lemma": "する",
            "pos": "動詞接尾辞",
            "features": [
              "終止"
            ],
            "attributes": {}
          }
        ]
      }
    ]
  }
]

非常に残念ですが、「かには、赤」を、「か、には、赤」と区切られてしまいました。

これは、構文解析側の問題ではありますが、自然言語プログラミング の精度を上げるためには、プログラミング中で使う変数名も辞書登録が必要 などの工夫が必要ということを示しています。
なお、普通のプログラミング言語では、 変数名をわざわざ定義しなければならない のは不便極まりなく感じますが、 自然言語プログラミング では、変数名の辞書登録にも価値があるのでは?と私は考えています。こちらについては、後程考察します。

ボブの所持金は、「100円」。ボブの所持金を、表示する。

ことは のレスポンスを確認(クリック)
ことは
[
  {
    "id": 0,
    "chunks": [
      {
        "chunk_info": {
          "id": 0,
          "head": 1,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 0,
            "form": "ボブ",
            "kana": "ボブ",
            "lemma": "ボブ",
            "pos": "名詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 1,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 1,
            "form": "の",
            "kana": "ノ",
            "lemma": "の",
            "pos": "格助詞",
            "features": [
              "連体"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 1,
          "head": 2,
          "dep": "D",
          "chunk_head": 1,
          "chunk_func": 2,
          "links": [
            {
              "link": 0,
              "label": "adjectivals"
            }
          ]
        },
        "tokens": [
          {
            "id": 2,
            "form": "所持",
            "kana": "ショジ",
            "lemma": "所持",
            "pos": "名詞",
            "features": [
              "動作"
            ],
            "attributes": {}
          },
          {
            "id": 3,
            "form": "金",
            "kana": "キン",
            "lemma": "金",
            "pos": "名詞接尾辞",
            "features": [
              "名詞"
            ],
            "dependency_labels": [
              {
                "token_id": 0,
                "label": "nmod"
              },
              {
                "token_id": 2,
                "label": "compound"
              },
              {
                "token_id": 4,
                "label": "case"
              },
              {
                "token_id": 5,
                "label": "punct"
              }
            ],
            "attributes": {}
          },
          {
            "id": 4,
            "form": "は",
            "kana": "ハ",
            "lemma": "は",
            "pos": "連用助詞",
            "features": [],
            "attributes": {}
          },
          {
            "id": 5,
            "form": "、",
            "kana": "",
            "lemma": "、",
            "pos": "読点",
            "features": [],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 2,
          "head": -1,
          "dep": "O",
          "chunk_head": 1,
          "chunk_func": 1,
          "links": [
            {
              "link": 1,
              "label": "agent"
            }
          ],
          "predicate": []
        },
        "tokens": [
          {
            "id": 6,
            "form": "「",
            "kana": "",
            "lemma": "「",
            "pos": "括弧",
            "features": [
              "開括弧"
            ],
            "attributes": {}
          },
          {
            "id": 7,
            "form": "100円",
            "kana": "ヒャクエン",
            "lemma": "100円",
            "pos": "名詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 3,
                "label": "nsubj"
              },
              {
                "token_id": 6,
                "label": "punct"
              },
              {
                "token_id": 8,
                "label": "punct"
              }
            ],
            "attributes": {}
          },
          {
            "id": 8,
            "form": "」",
            "kana": "",
            "lemma": "」",
            "pos": "括弧",
            "features": [
              "閉括弧"
            ],
            "attributes": {}
          }
        ]
      }
    ]
  },
  {
    "id": 1,
    "chunks": [
      {
        "chunk_info": {
          "id": 0,
          "head": 1,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 0,
            "form": "ボブ",
            "kana": "ボブ",
            "lemma": "ボブ",
            "pos": "名詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 1,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 1,
            "form": "の",
            "kana": "ノ",
            "lemma": "の",
            "pos": "格助詞",
            "features": [
              "連体"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 1,
          "head": 2,
          "dep": "D",
          "chunk_head": 1,
          "chunk_func": 2,
          "links": [
            {
              "link": 0,
              "label": "adjectivals"
            }
          ]
        },
        "tokens": [
          {
            "id": 2,
            "form": "所持",
            "kana": "ショジ",
            "lemma": "所持",
            "pos": "名詞",
            "features": [
              "動作"
            ],
            "attributes": {}
          },
          {
            "id": 3,
            "form": "金",
            "kana": "キン",
            "lemma": "金",
            "pos": "名詞接尾辞",
            "features": [
              "名詞"
            ],
            "dependency_labels": [
              {
                "token_id": 0,
                "label": "nmod"
              },
              {
                "token_id": 2,
                "label": "compound"
              },
              {
                "token_id": 4,
                "label": "case"
              },
              {
                "token_id": 5,
                "label": "punct"
              }
            ],
            "attributes": {}
          },
          {
            "id": 4,
            "form": "を",
            "kana": "ヲ",
            "lemma": "を",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          },
          {
            "id": 5,
            "form": "、",
            "kana": "",
            "lemma": "、",
            "pos": "読点",
            "features": [],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 2,
          "head": -1,
          "dep": "O",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 1,
              "label": "object"
            }
          ],
          "predicate": []
        },
        "tokens": [
          {
            "id": 6,
            "form": "表示",
            "kana": "ヒョウジ",
            "lemma": "表示",
            "pos": "名詞",
            "features": [
              "動作"
            ],
            "dependency_labels": [
              {
                "token_id": 3,
                "label": "dobj"
              },
              {
                "token_id": 7,
                "label": "aux"
              }
            ],
            "attributes": {}
          },
          {
            "id": 7,
            "form": "する",
            "kana": "スル",
            "lemma": "する",
            "pos": "動詞接尾辞",
            "features": [
              "終止"
            ],
            "attributes": {}
          }
        ]
      }
    ]
  }
]

変数として扱いたい「ボブの所持金」 を 1つの文節(chunk) 扱いにはしていません。
そこで、以下のルールが必要です。

  • 変数のlinksにadjectivals(形容)が付与されている場合、そのchunksの情報を連結する。

また、「所持金」部分の「所持」はfeaturesに動作が含まれていますが、これは関数ではなく変数の一部として扱うべきものになります。そこで、関数の条件は

  • tokensのfeaturesに 動作 が含まれている部分の chunk(文節) を関数 とする。

は、以下のように範囲を限定します。

  • tokensのfeaturesに 動作 が含まれていて、かつ、dependency_labels(依存先情報の配列) に pos(品詞) が 動詞接尾辞 のtokenが含まれている部分の chunk(文節) を関数 とする。

また、所持金の「金」は pos(品詞) が 名詞接尾辞なため、変数にはこれも加えるようにします。

関数呼び出しの引数に関数を記載するプログラム

11に7を4で割った余りを掛けて表示する。

ことは のレスポンスを確認(クリック)
ことは
[
  {
    "id": 0,
    "chunks": [
      {
        "chunk_info": {
          "id": 0,
          "head": 3,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 0,
            "form": "11",
            "kana": "ジューイチ",
            "lemma": "11",
            "pos": "Number",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 1,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 1,
            "form": "に",
            "kana": "ニ",
            "lemma": "に",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 1,
          "head": 3,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 2,
            "form": "7",
            "kana": "ナナ",
            "lemma": "7",
            "pos": "Number",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 3,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 3,
            "form": "を",
            "kana": "ヲ",
            "lemma": "を",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 2,
          "head": 3,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 4,
            "form": "4",
            "kana": "ヨン",
            "lemma": "4",
            "pos": "Number",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 5,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 5,
            "form": "で",
            "kana": "デ",
            "lemma": "で",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 3,
          "head": 4,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 2,
          "links": [
            {
              "link": 0,
              "label": "place"
            },
            {
              "link": 1,
              "label": "object"
            },
            {
              "link": 2,
              "label": "implement"
            }
          ],
          "predicate": [
            "past"
          ]
        },
        "tokens": [
          {
            "id": 6,
            "form": "割",
            "kana": "ワ",
            "lemma": "割る",
            "pos": "動詞語幹",
            "features": [
              "R"
            ],
            "dependency_labels": [
              {
                "token_id": 0,
                "label": "nmod"
              },
              {
                "token_id": 2,
                "label": "dobj"
              },
              {
                "token_id": 4,
                "label": "iobj"
              },
              {
                "token_id": 7,
                "label": "aux"
              },
              {
                "token_id": 8,
                "label": "aux"
              }
            ],
            "attributes": {}
          },
          {
            "id": 7,
            "form": "っ",
            "kana": "ッ",
            "lemma": "っ",
            "pos": "動詞活用語尾",
            "features": [],
            "attributes": {}
          },
          {
            "id": 8,
            "form": "た",
            "kana": "タ",
            "lemma": "た",
            "pos": "動詞接尾辞",
            "features": [
              "連体"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 4,
          "head": 5,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 3,
              "label": "adjectivals"
            }
          ]
        },
        "tokens": [
          {
            "id": 9,
            "form": "余り",
            "kana": "アマリ",
            "lemma": "余り",
            "pos": "補助名詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 6,
                "label": "acl"
              },
              {
                "token_id": 10,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 10,
            "form": "を",
            "kana": "ヲ",
            "lemma": "を",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 5,
          "head": 6,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 4,
              "label": "object"
            }
          ],
          "predicate": []
        },
        "tokens": [
          {
            "id": 11,
            "form": "掛け",
            "kana": "カケ",
            "lemma": "掛ける",
            "pos": "動詞語幹",
            "features": [
              "A"
            ],
            "dependency_labels": [
              {
                "token_id": 9,
                "label": "dobj"
              },
              {
                "token_id": 12,
                "label": "aux"
              }
            ],
            "attributes": {}
          },
          {
            "id": 12,
            "form": "て",
            "kana": "テ",
            "lemma": "て",
            "pos": "動詞接尾辞",
            "features": [
              "接続",
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 6,
          "head": -1,
          "dep": "O",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 5,
              "label": "manner"
            }
          ]
        },
        "tokens": [
          {
            "id": 13,
            "form": "表示",
            "kana": "ヒョウジ",
            "lemma": "表示",
            "pos": "名詞",
            "features": [
              "動作"
            ],
            "dependency_labels": [
              {
                "token_id": 11,
                "label": "advcl"
              },
              {
                "token_id": 14,
                "label": "aux"
              }
            ],
            "attributes": {}
          },
          {
            "id": 14,
            "form": "する",
            "kana": "スル",
            "lemma": "する",
            "pos": "動詞接尾辞",
            "features": [
              "終止"
            ],
            "attributes": {}
          }
        ]
      }
    ]
  }
]

残念ながら、割ったが、11、7、3にかかってしまっています。
これは、読点 を挟んで、

「11に、7を4で割った余りを掛けて表示する。」
「11に、7を4で割った余りを、掛けて表示する。」

としても結果は変わりませんでした。

なお、

 7を4で割った余りを、11に掛けて表示する。

ことは のレスポンスを確認(クリック)
ことは
[
  {
    "id": 0,
    "chunks": [
      {
        "chunk_info": {
          "id": 0,
          "head": 2,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 0,
            "form": "7",
            "kana": "ナナ",
            "lemma": "7",
            "pos": "Number",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 1,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 1,
            "form": "を",
            "kana": "ヲ",
            "lemma": "を",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 1,
          "head": 2,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 2,
            "form": "4",
            "kana": "ヨン",
            "lemma": "4",
            "pos": "Number",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 3,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 3,
            "form": "で",
            "kana": "デ",
            "lemma": "で",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 2,
          "head": 3,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 2,
          "links": [
            {
              "link": 0,
              "label": "object"
            },
            {
              "link": 1,
              "label": "implement"
            }
          ],
          "predicate": [
            "past"
          ]
        },
        "tokens": [
          {
            "id": 4,
            "form": "割",
            "kana": "ワ",
            "lemma": "割る",
            "pos": "動詞語幹",
            "features": [
              "R"
            ],
            "dependency_labels": [
              {
                "token_id": 0,
                "label": "dobj"
              },
              {
                "token_id": 2,
                "label": "iobj"
              },
              {
                "token_id": 5,
                "label": "aux"
              },
              {
                "token_id": 6,
                "label": "aux"
              }
            ],
            "attributes": {}
          },
          {
            "id": 5,
            "form": "っ",
            "kana": "ッ",
            "lemma": "っ",
            "pos": "動詞活用語尾",
            "features": [],
            "attributes": {}
          },
          {
            "id": 6,
            "form": "た",
            "kana": "タ",
            "lemma": "た",
            "pos": "動詞接尾辞",
            "features": [
              "連体"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 3,
          "head": 5,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 2,
              "label": "adjectivals"
            }
          ]
        },
        "tokens": [
          {
            "id": 7,
            "form": "余り",
            "kana": "アマリ",
            "lemma": "余り",
            "pos": "補助名詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 4,
                "label": "acl"
              },
              {
                "token_id": 8,
                "label": "case"
              },
              {
                "token_id": 9,
                "label": "punct"
              }
            ],
            "attributes": {}
          },
          {
            "id": 8,
            "form": "を",
            "kana": "ヲ",
            "lemma": "を",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          },
          {
            "id": 9,
            "form": "、",
            "kana": "",
            "lemma": "、",
            "pos": "読点",
            "features": [],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 4,
          "head": 5,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 10,
            "form": "11",
            "kana": "ジューイチ",
            "lemma": "11",
            "pos": "Number",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 11,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 11,
            "form": "に",
            "kana": "ニ",
            "lemma": "に",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 5,
          "head": 6,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 3,
              "label": "object"
            },
            {
              "link": 4,
              "label": "goal"
            }
          ],
          "predicate": []
        },
        "tokens": [
          {
            "id": 12,
            "form": "掛け",
            "kana": "カケ",
            "lemma": "掛ける",
            "pos": "動詞語幹",
            "features": [
              "A"
            ],
            "dependency_labels": [
              {
                "token_id": 7,
                "label": "dobj"
              },
              {
                "token_id": 10,
                "label": "nmod"
              },
              {
                "token_id": 13,
                "label": "aux"
              }
            ],
            "attributes": {}
          },
          {
            "id": 13,
            "form": "て",
            "kana": "テ",
            "lemma": "て",
            "pos": "動詞接尾辞",
            "features": [
              "接続",
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 6,
          "head": -1,
          "dep": "O",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 5,
              "label": "manner"
            }
          ]
        },
        "tokens": [
          {
            "id": 14,
            "form": "表示",
            "kana": "ヒョウジ",
            "lemma": "表示",
            "pos": "名詞",
            "features": [
              "動作"
            ],
            "dependency_labels": [
              {
                "token_id": 12,
                "label": "advcl"
              },
              {
                "token_id": 15,
                "label": "aux"
              }
            ],
            "attributes": {}
          },
          {
            "id": 15,
            "form": "する",
            "kana": "スル",
            "lemma": "する",
            "pos": "動詞接尾辞",
            "features": [
              "終止"
            ],
            "attributes": {}
          }
        ]
      }
    ]
  }
]

上記のとおり順番を変えると、期待していた通りの結果が得られます。分かりにくい表現で掛かり元が期待していない挙動をするのは、 自然言語の構文解析 上仕方のない部分であり、辞書登録等で回避できる部分とも考えられます。

また、ここでは「掛ける」や「割った余り」を関数として解析すること、「割った余り」が別chunkになっていることと、「~て表示する」と表示の助詞が違うため、以下の3点のルールが必要です。

  • tokensのposが 動詞語幹 が含まれている部分の chunk(文節) を関数 とする。
  • tokensのposが 補助名詞 の場合、dependency_labels が acl(連体修飾節。ただしamodに該当する場合を除く。また「てからの」「ながらの」等の接続表現。)の場合、それを連結したものを関数とする。
  • なでしこの「表示」関数の「て」助詞の場合、 labelは manner(動作・変化のやり方)

また、7や4といった数字をリテラルとして認識する必要があるため、
1. 文字列リテラル、関数、変数以外で、tokensのpos(品詞) がNumber のものについては、数値として処理する

11を7を4で割った余りで割った余りを表示する。

ことは のレスポンスを確認(クリック)
ことは
[
  {
    "id": 0,
    "chunks": [
      {
        "chunk_info": {
          "id": 0,
          "head": 1,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 0,
            "form": "11",
            "kana": "ジューイチ",
            "lemma": "11",
            "pos": "Number",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 1,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 1,
            "form": "を",
            "kana": "ヲ",
            "lemma": "を",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 1,
          "head": 3,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 0,
              "label": "object"
            }
          ],
          "predicate": []
        },
        "tokens": [
          {
            "id": 2,
            "form": "7",
            "kana": "ナナ",
            "lemma": "7",
            "pos": "Number",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 0,
                "label": "dobj"
              },
              {
                "token_id": 3,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 3,
            "form": "を",
            "kana": "ヲ",
            "lemma": "を",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 2,
          "head": 3,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": []
        },
        "tokens": [
          {
            "id": 4,
            "form": "4",
            "kana": "ヨン",
            "lemma": "4",
            "pos": "Number",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 5,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 5,
            "form": "で",
            "kana": "デ",
            "lemma": "で",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 3,
          "head": 4,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 2,
          "links": [
            {
              "link": 1,
              "label": "object"
            },
            {
              "link": 2,
              "label": "implement"
            }
          ],
          "predicate": [
            "past"
          ]
        },
        "tokens": [
          {
            "id": 6,
            "form": "割",
            "kana": "ワ",
            "lemma": "割る",
            "pos": "動詞語幹",
            "features": [
              "R"
            ],
            "dependency_labels": [
              {
                "token_id": 2,
                "label": "ccomp"
              },
              {
                "token_id": 4,
                "label": "iobj"
              },
              {
                "token_id": 7,
                "label": "aux"
              },
              {
                "token_id": 8,
                "label": "aux"
              }
            ],
            "attributes": {}
          },
          {
            "id": 7,
            "form": "っ",
            "kana": "ッ",
            "lemma": "っ",
            "pos": "動詞活用語尾",
            "features": [],
            "attributes": {}
          },
          {
            "id": 8,
            "form": "た",
            "kana": "タ",
            "lemma": "た",
            "pos": "動詞接尾辞",
            "features": [
              "連体"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 4,
          "head": 5,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 3,
              "label": "adjectivals"
            }
          ]
        },
        "tokens": [
          {
            "id": 9,
            "form": "余り",
            "kana": "アマリ",
            "lemma": "余り",
            "pos": "補助名詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 6,
                "label": "acl"
              },
              {
                "token_id": 10,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 10,
            "form": "で",
            "kana": "デ",
            "lemma": "で",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 5,
          "head": 6,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 2,
          "links": [
            {
              "link": 4,
              "label": "condition"
            }
          ],
          "predicate": [
            "past"
          ]
        },
        "tokens": [
          {
            "id": 11,
            "form": "割",
            "kana": "ワ",
            "lemma": "割る",
            "pos": "動詞語幹",
            "features": [
              "R"
            ],
            "dependency_labels": [
              {
                "token_id": 9,
                "label": "nmod"
              },
              {
                "token_id": 12,
                "label": "aux"
              },
              {
                "token_id": 13,
                "label": "aux"
              }
            ],
            "attributes": {}
          },
          {
            "id": 12,
            "form": "っ",
            "kana": "ッ",
            "lemma": "っ",
            "pos": "動詞活用語尾",
            "features": [],
            "attributes": {}
          },
          {
            "id": 13,
            "form": "た",
            "kana": "タ",
            "lemma": "た",
            "pos": "動詞接尾辞",
            "features": [
              "連体"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 6,
          "head": 7,
          "dep": "D",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 5,
              "label": "adjectivals"
            }
          ]
        },
        "tokens": [
          {
            "id": 14,
            "form": "余り",
            "kana": "アマリ",
            "lemma": "余り",
            "pos": "補助名詞",
            "features": [],
            "dependency_labels": [
              {
                "token_id": 11,
                "label": "acl"
              },
              {
                "token_id": 15,
                "label": "case"
              }
            ],
            "attributes": {}
          },
          {
            "id": 15,
            "form": "を",
            "kana": "ヲ",
            "lemma": "を",
            "pos": "格助詞",
            "features": [
              "連用"
            ],
            "attributes": {}
          }
        ]
      },
      {
        "chunk_info": {
          "id": 7,
          "head": -1,
          "dep": "O",
          "chunk_head": 0,
          "chunk_func": 1,
          "links": [
            {
              "link": 6,
              "label": "object"
            }
          ],
          "predicate": []
        },
        "tokens": [
          {
            "id": 16,
            "form": "表示",
            "kana": "ヒョウジ",
            "lemma": "表示",
            "pos": "名詞",
            "features": [
              "動作"
            ],
            "dependency_labels": [
              {
                "token_id": 14,
                "label": "dobj"
              },
              {
                "token_id": 17,
                "label": "aux"
              }
            ],
            "attributes": {}
          },
          {
            "id": 17,
            "form": "する",
            "kana": "スル",
            "lemma": "する",
            "pos": "動詞接尾辞",
            "features": [
              "終止"
            ],
            "attributes": {}
          }
        ]
      }
    ]
  }
]

これも、期待している通りには解析されません。また、前の例と同様に、読点 を挟んでも解析結果は変わりませんでした。
これは、日本語としても不自然であるため今回は対象外とします。

実装

さて、まだまだ検討する余地はありますが、上記で想定通りに解析された文章を 自然言語プログラム として動くように実装します。

※ 関数呼び出しの引数に関数を記載するプログラムの章で解析した内容は、実装できていません。

実装方針

cotoha apiを利用した解析は、非同期処理なので、リクエスト送付を なでしこ に組み込む場合は、非同期処理に合わせた なでしこ の修正が必要になります。

今回は、なでしこ のCUI用のモジュールに、上記の ことは で解析した結果を受け取るオプションを追加する方針とします。

実装

元のソース: なでしこのGITHUB(86fb5a5d84) を用い、CUI用のモジュール(cnako3)で、ことは のレスポンスのjsonを受け取って実行できる形式とします。

本記事の最初の投稿の修正の差分のコミットはこちら

一部を抜粋すると、

オプションに、以下のように -C もしくは --cotoha を指定できるようにし、

.option('-C, --cotoha', 'ことはのレスポンスを使用する')

token間の連結の処理は、lexerにcotoha用の以下の処理を作成し

  setInputCotoha (code, isFirst, line) {
    // 暫定でlineは全て0を入れる
    const json = JSON.parse(code)
    for (const sentence of json) {
      let resultSentence = [];
      for (const chunk of sentence.chunks) {
        if (chunk.tokens.find ( token => token.features.includes('開括弧'))) {
          // 開括弧から閉括弧までを文字列リテラルとする。
          const values = []
          let stringFlg = false
          for (const token of chunk.tokens) {
            if (stringFlg) {
              if (token.features.includes('閉括弧')) {
                break
              } else {
                values.push(token.form)
              }
            } else {
              if (token.features.includes('開括弧')) {
                stringFlg = true
              }
            }
          }
          resultSentence.push({ id: chunk.chunk_info.id, type: "string", value: values.join(''), links: chunk.chunk_info.links, line: 0 })
        } else {
          let token = chunk.tokens.find ( token => token.pos === '動詞語幹');
          if (token) {
            resultSentence.push({ id: chunk.chunk_info.id, type: "func", value: token.lemma, links: chunk.chunk_info.links, line: 0 })
            continue
          }
          token = chunk.tokens.find ( token => token.features.includes('動作'));
          if (token) {
            if (token.dependency_labels && token.dependency_labels.some( d => {
              const dependency_token = chunk.tokens.find( token => token.id === d.token_id)
              return dependency_token && dependency_token.pos === '動詞接尾辞'
            })) {
              resultSentence.push({ id: chunk.chunk_info.id, type: "func", value: token.lemma, links: chunk.chunk_info.links, line: 0 })
              continue
            }
          }
          token = chunk.tokens.find ( token => ['名詞', '独立詞'].includes(token.pos))
          if (token) {
            if (chunk.chunk_info.head >= 0) {
              const head_chunk = sentence.chunks.find(tmp_chunk => tmp_chunk.chunk_info.id === chunk.chunk_info.head)
              if (head_chunk) {
                let link = head_chunk.chunk_info.links.find(link => link.link === chunk.chunk_info.id)
                if (link && link.label === "adjectivals") {
                  continue
                }
              }
            }
            let value = ''
            chunk.chunk_info.links.filter( link => link.label === 'adjectivals').forEach( link => {
              const joinChunk = sentence.chunks.find( chunk => chunk.chunk_info.id === link.link)
              value += joinChunk.tokens.map( token => token.form ).join('')
            })
            for (const token of chunk.tokens) {
              if (['名詞', '独立詞','名詞接尾辞'].includes(token.pos)) {
                value += token.form
              } else {
                if (value) {
                  break;
                }
              }
            }
            resultSentence.push({ id: chunk.chunk_info.id, type: "word", value: value, links: chunk.chunk_info.links, line: 0 })
            continue
          }
          token = chunk.tokens.find ( token => ['Number'].includes(token.pos))
          if (token) {
            resultSentence.push({ id: chunk.chunk_info.id, type: "number", value: Number(token.lemma), links: chunk.chunk_info.links, line: 0 })
            continue
          }
        }
      }
      this.result.push(resultSentence)
    }
    return this.result
  }

chunk間の処理はparserにcotoha用の以下の処理を作成しました。

  parseCotoha (tokens) {
    this.reset()
    this.tokens = tokens
    let ret = {
      type: "block",
      block: [],
    }
    for (const token of tokens) {
      for (const chunk of token) {
        if (["string", "word", "number"].includes(chunk.type)) {
          if (chunk.links) {
            let link = chunk.links.find( link => link.label === "agent")
            if (link) {
              let arg = token.find(chunk => chunk.id === link.link)
              if (arg) {
                ret.block.push({
                  type: "let",
                  name: { type: arg.type, value: arg.value, josi: "", line: 0 },
                  value: { type: chunk.type, value: chunk.value, josi: "", line: 0 }
                })
              }
            }
          }
        } else if (chunk.type === "func") {
          ret.block.push({
            type: "func", name: chunk.value, line: 0, args: chunk.links.map(link => {
              let arg = token.find(chunk => chunk.id === link.link)
              return { type: arg.type, value: arg.value, label: link.label }
            })
          })
        }
      }
    }
    return ret
  }

実行結果

変数と代入を伴うプログラミングのサンプルのうち、
構文解析段階でうまくいかなかった「かには、「赤」。かにを、表示する。」
以外は実行できました。

なでしこ では文法エラーとなる

ボブの所持金は、「100円」。ボブの所持金を、表示する。

のデバッグオプション付きの実行結果を下記に示します。

$ node src/cnako3.js -D --cotoha ../bobu.kotohajson
--- lex ---
[
  [
    {
      "id": 1,
      "type": "word",
      "value": "ボブの所持金",
      "links": [
        {
          "link": 0,
          "label": "adjectivals"
        }
      ],
      "line": 0
    },
    {
      "id": 2,
      "type": "string",
      "value": "100円",
      "links": [
        {
          "link": 1,
          "label": "agent"
        }
      ],
      "line": 0
    }
  ],
  [
    {
      "id": 1,
      "type": "word",
      "value": "ボブの所持金",
      "links": [
        {
          "link": 0,
          "label": "adjectivals"
        }
      ],
      "line": 0
    },
    {
      "id": 2,
      "type": "func",
      "value": "表示",
      "links": [
        {
          "link": 1,
          "label": "object"
        }
      ],
      "line": 0
    }
  ]
]
--- ast ---
{
  "type": "block",
  "block": [
    {
      "type": "let",
      "name": {
        "type": "word",
        "value": "ボブの所持金",
        "josi": "",
        "line": 0
      },
      "value": {
        "type": "string",
        "value": "100円",
        "josi": "",
        "line": 0
      }
    },
    {
      "type": "func",
      "name": "表示",
      "line": 0,
      "args": [
        {
          "type": "word",
          "value": "ボブの所持金",
          "label": "object"
        }
      ]
    }
  ]
}
--- generate ---
const __v0 = this.__v0 = this.__varslist[0];
const __v1 = this.__v1 = this.__varslist[1];
__v0.line=0;// プラグインの初期化
__v0["!PluginSystem:初期化"](__self);
__v0["!PluginNode:初期化"](__self);
__vars["それ"] = '';
;__vars["ボブの所持金"]="100円";
__v0["表示"](__vars["ボブの所持金"],__self);

100円

さいごに

結果として、日本語プログラミングとしての なでしこ をベースに、 ことは の構文解析結果と比較しながら、自然言語プログラミングのルールを決める というプロトタイピング的なアプローチとなりましたが、そのおかげで、短期間で 自然言語プログラミングの課題や、可能性を検証できた と思っています。

今後の拡張の可能性

今回は、変数定義、関数呼び出しに対応し、 なでしこ の組み込み関数を利用して実装しました。

しかし記事公開のタイミング上、labelにて、キーワード引数相当の処理を入れることはできていません。

また、一般のプログラミング言語の機能と比較すると、制御文や関数・クラス定義などができていません。

ここからは、筆者の私見にはなりますが、制御文や関数・クラス定義は自然言語との相性はあまりよくないと考えています。
なでしこの制御文も、自然言語プログラミングのスタイルとしては、違和感があります。

近年は、ダックタイピング、オブジェクト志向、関数志向のプログラミングスタイルも馴染んできています。

これらから、構文解析や係受け解析と相性のいい機能を追加することが大事だと思っています。

また、自然言語プログラミングでは、辞書登録機能が鍵になります。これは、構文解析、係受け解析を行うパーサー側の仕組みと合わせて、新しいプログラミング言語の仕組みとして設計する必要があります。

自然言語プログラミングはどこで活用できるか?

今の、なでしこなどのプログラミング言語は、初心者向け、可読性、教育用の用途が多いと思います。

また、他のプログラミング言語は、データ加工や分析の汎用ツールとしての利用もありますが、一番の利用は、構造化を必要とするエンタープライズのシステム作成、構築でしょう。

自然言語プログラミングは、これらとは違う領域で真価を発揮する可能性が秘めていると考えています。

1つは、自然言語、つまり記号などを前提としないプログラミングスタイルは、音声入力と相性がよいでしょう。
プログラミングの構造化が重要視それない、競技プログラミングの領域などで、音声入力によるプログラミングが実現するかもしれません。

また、構文解析の結果を用いているので、辞書登録部分を他言語対応することで、翻訳技術等と組み合わせれば、他言語の自然言語への対応が可能と考えられます。
そのため、アルゴリズムを技術者によらず広く分かりやすく記述する方法として、普及する可能もあります。

最後に、既存資産の活用です。COTOHA APIのように、自然言語プログラミングが簡単に使えるインターフェースが出てくると、プログラムとは関係ないドキュメントと、自然言語プログラミングを組み合わせた利用ができることでしょう。

謝辞

当初は、自然言語プログラミングが可能な言語を作って、「AtCoder に登録したら解くべき精選過去問 10 問 」を解きたい という高いハードルを持って、まずは なでしこ を調べて説くところから始めました。

結果として、プログラム言語の難しさや、構文解析の難しさに直面し、なでしこことは の理解に多くの時間を使ってしまい、当初の目標の1割程度にしか到達できなかったと感じています。

そんな中、中途半端でも記事公開まで至れたのは キャンペーン のおかげです。

また、記事を書く上で、 @sizumitaさんの 高校生が作った日本語プログラミング言語、曖昧な構文でも動くんだが - Qiita にも触発されました。

ここで合わせて、御礼申し上げます。

今後、この続きを検証できるモチベーションが保てないとは思っていますが、LGTMしていただければ、本記事の見直しや、拡張、別のアプローチでの自然言語プログラミングの検証等のモチベーションにつながります。

12
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9