エッセイ

プログラミング翻訳サービス開発で技術的に考えたこととその気づき

この記事はジーズアカデミー Advent Calendar 2019の22日目の記事です。

プログラミング翻訳サービス、ProgLearn

みなさん、こんにちは。どんぶラッコです。

上にも記載しましたが、この記事は起業家養成スクール G’s Academy のアドベントカレンダーです。私はTokyoのLAB6期生として半年間お世話になりました。

このスクールでは、自分が作成したサービスを発表するGlobal Geek Auditionという機会があるのですが、その時に、私、面白いサービス(自分で言った!)を発表しました。

それは、プログラミングの翻訳サービスです。

…どういうこと?

はい、その疑問ごもっともです。

では、実際にご覧ください。

画面の左側にJavaScriptを入力してEnterキーを押すと…

\ッターン/

パソコンを使う学生のイラスト(男子)

さあ、右側にご注目です。

翻訳されてるーーー!

こんなサービスを粛々と作っています。夢が広がりますね。

このサービスを説明するとギークな方々から「どうやって作ってるの?」という質問が飛んできます。

この翻訳ロジックを作る部分、実は結構苦労したんです。

ということで、今回は

  • プロトタイプNo.1 の翻訳ロジック
  • No.1 の反省と プロトタイプNo.2

についてご紹介します!

プロトタイプ No.1

一番最初は自分でなんちゃってMVCフレームワークを作ってアプリを構築しました。

翻訳についてはGrammerModel内に定義しています。

そして肝心の翻訳ロジックですが、

  • 空白 or 改行でsplit、;で一区切りとみなし、多重配列を作成する
  • 多重配列の先頭を予約語とみなし、ルールを定義する

というものでした。

なので、例えば

let i = 0;

という文言があったなら、

['let', 'i', '=', 0, ';']

という形に分解してから、先頭の予約語(let)に基づいて翻訳を実行する、というものでした。

ちなみに、関数名が和訳されているのは、関数名を単語ごとに分解してGoogle翻訳にぶん投げています。

No.1 の反省と プロトタイプNo.2

さて、上記のようにゴリ押しで実装を進めていた私ですが、実装ロジックを改めて作り直すことにしました。

その理由は、上記のロジックでは立ち行かないケースが多数出てきてしまったためです。

例えば、for文。

for文は

for (let i =0; i<10; i++){
  ...
}

のように記述しますが、先ほどの

  • 空白 or 改行でsplit、;で一区切りとみなし、多重配列を作成する
  • 多重配列の先頭を予約語とみなし、ルールを定義する

に基づいて分解を実施すると

[ ['for', '(', 'let', 'i', '=', 0, ';'], ['i', '<', 10, ';'], ...]

このように、for文がバラバラになってしまいます。

そこで、

  • 接頭辞がforだった場合、 () を一つの配列で扱う

という例外設定を追加しなければなりません。

しかし同様の事象はif文でも関数でも同様に発生してしまいます。

つまり、例外設定が多く生まれてしまうということは、ルールとして機能していないということに他なりません。

また、さらに更に悪いことに、splitの条件が”半角スペースがあること”としています。

つまり、

i=0

のような書き方をされてしまった場合、

['i=0']

と数式の状態のまま配列化されてしまいます。

そこで、

  • JavaScriptを意味ごとの塊で分類出来ること

を条件に、新たなルール探しを模索していました。

そんな私の前に現れたのが、Babelライブラリです。

この技術の詳細については、別のAdvent Calendarでも書かせてもらいました。

BabelはBabel Toolingという形でBabelにまつわる機能を提供しています。

その中にある、parseという機能がまさに私が追い求めていた機能を提供していました。

例えばこのプログラム

function isSendSalt(person){
  return person==='上杉謙信'
}

if(isSendSalt('上杉謙信')){
  console.log('武田信玄「ありがとう」')
}

これを、Babelではこのように分解してくれます。

Node {
  type: 'File',
  start: 0,
  end: 111,
  loc:
   SourceLocation {
     start: Position { line: 1, column: 0 },
     end: Position { line: 7, column: 1 } },
  errors: [],
  program:
   Node {
     type: 'Program',
     start: 0,
     end: 111,
     loc: SourceLocation { start: [Position], end: [Position] },
     sourceType: 'script',
     interpreter: null,
     body: [ 
      Node {
        type: 'FunctionDeclaration',
        start: 0,
        end: 55,
        loc: SourceLocation { start: [Position], end: [Position] },
        id:
        Node {
          type: 'Identifier',
          start: 9,
          end: 19,
          loc: [SourceLocation],
          name: 'isSendSalt' },
        generator: false,
        async: false,
        params: [ [Node] ],
        body:
        Node {
          type: 'BlockStatement',
          start: 27,
          end: 55,
          loc: [SourceLocation],
          body: [Array],
          directives: [] } },
    Node {
      type: 'IfStatement',
      start: 57,
      end: 111,
      loc: SourceLocation { start: [Position], end: [Position] },
      test:
      Node {
        type: 'CallExpression',
        start: 60,
        end: 78,
        loc: [SourceLocation],
        callee: [Node],
        arguments: [Array] },
      consequent:
      Node {
        type: 'BlockStatement',
        start: 79,
        end: 111,
        loc: [SourceLocation],
        body: [Array],
        directives: [] },
      alternate: null 
    } ],  
  directives: [] },
  comments: [] }

この情報を基に、ルール付けを実施しました。

    switch (type) {
      case 'BinaryExpression': {
        resultObj = toNLang.binaryExpression(props)
        break
      }
      case 'ExpressionStatement': {
        resultObj = toNLang.expressionStatement(props)
        break
      }
      case 'FunctionDeclaration': {
        resultObj = toNLang.functionDeclaration(props)
        break
      }
      case 'ForStatement' : {
        resultObj = toNLang.forStatement(props)
        break
      }
      case 'IfStatement': {
        resultObj = toNLang.ifStatement(props)
        break
      }
      case 'ReturnStatement': {
        resultObj = toNLang.returnStatement(props)
        break
      }
      case 'VariableDeclaration': {
        resultObj = toNLang.variableDeclaration(props)
        break
      }
      ....
    }

プログラム自体の可読性も上がっています。


まずは作ってみる

一見良さそうに見えるNo.2の方針ですが、これもまだ不完全であると考えています。

それは、結局ルールの個別設定になってしまっている点です。なので、この部分のルール化・改良化に取り組もうと思っています。

ロジックを考えながら実感したのは、まずは動いてみることの重要性。

一度作ってみると、新しい発見や気づきを得ることができました。今回の場合も1つ目のプロトタイプを作っていなかったら、翻訳ルールの穴に気づくことができなかったと思います。

その意味で、高速でプロトタイプを作り、実験をするという精神を持って、これからもプロダクト開発に携わっていきたいと思います!

ABOUT ME
どんぶラッコ
ECコンサルタント、システムエンジニア経験を経て、ProgLearnのシステム開発を担当。

\面白いと思ったら/

記事のシェア & Twitter のフォロー をお願いします!

@proglearn

COMMENT

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です