読者です 読者をやめる 読者になる 読者になる

Minecraftとタートルと僕

PCゲームMinecraftのMOD「ComputerCraft」の情報を集めたニッチなブログです。

FS API を使いこなそう(7): 【補足】関数を定義する関数を定義する(基礎編)

チュートリアル Lua ComputerCraft Minecraft API 関数型プログラミング

移動履歴を残すプログラム

かなり間が開きましたが、前回の続きを進めましょう。

まず最初に、前回は重要な部分しかお見せしなかったプログラムの完全版を掲載します。

これはAPIプログラムですので、以下のようにpastebinコマンドを使ってこのファイルをダウンロードし、"logging"のような名前で保存しましょう。

> pastebin get JB7ikE7R logging

そして自分のプログラムの中で、os.loadAPI("logging")のように記述するとこのAPIの各種関数が使えるようになります。

Logging APIの仕様説明

使い方はこのファイルの中に書いていますが、念のためAPIの仕様を簡単に説明します。

  • Turtle移動系関数: 普通にturtle.forward()などして、成功したらturtle.forward()、失敗したら-- turtle.forward()とログファイルに出力。
    • logging.forward()
    • logging.back()
    • logging.up()
    • logging.down()
    • logging.turnRight()
    • logging.turnLeft()
  • ログファイルに強制的にメッセージ出力
    • logging.write( message )
  • ログファイル名を変える。デフォルトは「mylog」
    • logging.renameLogfile( newname )
    • 変えた後のファイル名と同じ名前のファイルが存在したら、そのファイルの末尾にログを追記していく。

ソースコード

http://pastebin.com/JB7ikE7R

問題点

このプログラム。気持ち悪くありません?

僕は書いていて、とても気持ち悪くてイライラしました。

それは前回省略した部分です。

forward()やback()など合計6つのTurtle移動系関数の定義部分にほとんど同じことを書いているのですよね。

これをなんとかしたいなぁ・・・と。

そこで、「Luaの関数はファーストクラスオブジェクトである」という特徴を使って、「関数を定義する関数」を書いてみましょう。

ファーストクラスオブジェクト? なにそれ? という方は以下の記事をどうぞ。

この過去の記事で述べたように、Luaの関数(Function)は関数型という基本的なデータ型で実装されています。

簡単に言うと、関数それ自体を他の型(たとえば数値、文字列など)と同じように基本レベルで扱うことができます。

たとえば、関数それ自体を他の変数に代入したり、関数の引数として関数を与えたり、関数の返値として関数を返したりなどできるわけです。

関数がファーストクラスオブジェクトであることは関数型プログラム言語には必須の条件なのですが、Luaはその条件を満たしているわけですね。 Luaの力ってすごい!

それでは、すばらしいLuaの特徴を生かして、レッツ関数型プログラミング!

まずは基本、関数型でいろいろ遊んでみよう

それではタートルのターミナル画面上でluaと打ち込んで、Lua Interactive modeを起動しましょう。

そしておもむろに次のように2つのコマンドを打ち込んでみましょう。

lua> turtle.forward()
(タートルが一歩前進する)
lua> turtle.forward
function: 7d736c2d

f:id:hevohevo:20140608113228j:plain:w450

普段、関数を使うときは前者の書き方ですね。つまり、関数名をその末尾にかっこ"()"をつけて入力することで、その「関数を実行」しているわけです。 それに対して、後者の方、末尾のかっこをはずして関数名だけ入力すると、「関数それ自体」を示します。

なお、「function: e2ca055」は特に名前がついていない無名関数であることを示しています。 "e2ca055"は識別のためのIDみたいなものです。僕の環境ではこのようなIDですが、皆さんの環境では違うかもしれません。

つまりどういうことだってばよ

関数を別の変数に代入する

これを利用すると、次のようなことができるのです。

## turtle.forwardという変数の中にはe2ca055というIDがついた無名関数が入っています。
lua> turtle.forward
function: e2ca055

## この無名関数を変数fwdに代入してあげましょう。
lua> fwd = turtle.forward

## ほら、変数fwdには、IDがe2ca055の無名関数が入りました。
lua> fwd
function: e2ca055

## 末尾に()をつけて命令すると、この関数を実行します。つまり前に進みます。
lua> fwd()

つまり、既存の関数の名前を自分の好きなものに変えることができるわけですね。

長い関数名は入力するのが面倒ですが、これで自分好みの短い名前に簡単に変えることができるわけです。ただしやりすぎるとわけがわからなくなりますが><

関数それ自体を関数の引数として与える

次はLua interactive modeではなく、プログラムです。

function myDrop(func, num)
  if func(num) then
     print("Dropped!")
  else
     print("Failed!")
  end
end

myDrop(turtle.drop, 10) -- 内部的に turtle.drop(10)を実行してその結果をprint
myDrop(turtle.dropDown, 10)
myDrop(turtle.dropUp, 10)
myDrop(turtle.dropUp) -- 内部的に turtle.dropUp()を実行してその結果をprint

ここで定義した関数myDrop()は、引数として「drop系の関数それ自体」と「放出する個数」を必要とします。そして与えられたdrop系関数を実行してその実行結果を表示します。 なお、放出する個数は関数実行時に省略可能です。

タートルの関数群の中でもdrop系やsuck系などは、前や上や下に対して実行できるためにそれぞれ関数が分かれています。いちいち、全ての関数について再定義するのは面倒ですよね。

そこで、上記のように関数を引数として与えるようにすることで、汎用型関数を作ることができるわけです。

これらの合わせ技でさらに便利に!

これら二つを応用すると、いろいろなシチュエーションに対応できるプログラムが作れます。

たとえばタートルの内部インベントリのアイテムを全てチェストに仕舞うという処理をよく使います。

しかしチェストが必ず真下にあるとは限りませんよね?

格納用チェストが真上や正面にあるときには、プログラム中の該当部分すべてを書き換えないといけません。

でも次のようなプログラムなら、プログラム行頭の設定部分を書き換えるだけで、チェストが正面にあろうが真上にあろうがこのプログラムだけで柔軟に対応できます。

-- ######################## 
-- # Config
tmpDrop = turtle.drop
lastDrop = turtle.dropDown
mysuck = turtle.suckUp

-- ########################
-- # Functions
function dropAll(func)
  for i=1,16 do
    if turtle.getItemCount(i) > 0 then
      turtle.select(i)
      func()
    end
  end
end

-- ########################
-- # Main

-- アイテムをチェストからとってきて行動スタート!
mysuck()

・・・
(途中、色々な処理を書いて)
-- たとえば、処理途中で一時的にアイテムをドロップするなど
dropAll(tmpDrop)
・・・

-- 終了処理のために完成品を放出
dropAll(lastDrop)

関数を返値として返す関数

この記事もかなり長くなってきたので、続きは次回!

今度こそは、関数を定義する関数を定義しますね!