ChatGPTに自然変換について聞いてみよう
昨年後半くらいから、AIによる自然言語処理(NLP)の話題が急速に高まっていて、特にOpenAIのChatGPTは人間らしい応答がすばやく出来ることで評判となっています。
自然言語処理自体は古くからある概念で、漢字かな変換や、自動翻訳などさまざまな分野で取り入れられてきましたが、とうとう実用的なプログラミングそのものすら、プログラミング言語ではなく自然言語で記述出来る様になりつつあるというのは本当にすごいことだと思います。
さて、自然言語処理でプログラミングが出来ること、自然言語、自然とは何か、等といろいろ思索してみたところ、そういえばAIは自然性について理解はしているのだろうかと思いつきました。ここで言う自然性とは数学の一分野における圏論の概念の一つで、自然変換が持つ性質です。自然言語の自然とは直接は関係ないので、これは無理矢理な解釈によるジョークなのですが、もしかすると、何か深いところでに人間が「自然」とみなせるものの共通概念があるのかも知れません。
HaskellやScalaなど圏論をもとにしたプログラミング言語はいくつかありますが、そこまでメジャーではないので、ここでは簡単に自然変換について述べてみます。自然変換は、プログラミングで言うところの、ある「型a」を受け取って、その「型a」のList値を受け取って、「型a」のMaybe値を返すような関数αのことです。(Maybe型というのは、JavaでいうOptional型です)
この「型a」というのは、具体的には、整数型だったり文字列型だったり、なんでも構いません。普通の関数は型が決まらないと記述出来ないのですが、変換αは、型aをパラメータとしたパラメータ多相型(ジェネリクス)な関数として定義されていることになります。
補足1:正確には、ListやMaybeは関手でないといけないので、それぞれ対応するmap関数が定義されている必要があります。このmap関数は、「引数がa型で戻り値がb型の関数f」を受け取って、「引数がList a型(Maybe a型)で戻り値がList b型(Maybe b型 )な関数」を返すような高階関数です。map関数はさらに、恒等関数を恒等関数に写し、かつ、f, gを a -> b型の関数、listをList a型として
(map g) (map f) list = (map (g.f) ) list
を満たしている必要があります。
JavaScriptのArrayは正確にはListではありませんが、Array型のreduceメソッドは、map関数と似た様な機能を持っています。
さてこの場合、この関数αのことを、「ListからMaybeへの変換α」と呼びます。
更に詳しく言うと、この変換αが次の様な性質(これを自然性と言います)を満たす時、これを自然変換αと言います
α ( (map f) list ) = (map f) (α list )
つまり、listに(map f)を適用してから変換αを行った場合と、listを変換αしてから(map f)を適用した場合で、同じ結果になっていれば、「自然変換」と言えるわけです。もう少し正確に言うと、型aを1つ固定したときものは自然変換αのa成分といい、任意のaについて成り立つのが自然変換αです。
自然変換についての説明はここまでです。
前置きが長くなりましたが、ChatGPTに「PureScriptで自然変換を使ったサンプルコードを作って」と質問してみました。すると、もらえた回答が以下の通りです。
module Main where
import Prelude
import Control.Natural (type (~>), trans)
-- MaybeモナドからEitherモナドへの自然変換
maybeToEither :: forall a b. Maybe a -> Either b a
maybeToEither Nothing = Left unit
maybeToEither (Just x) = Right x
-- ListモナドからMaybeモナドへの自然変換
listToMaybe :: forall a. List a -> Maybe a
listToMaybe Nil = Nothing
listToMaybe (Cons x _) = Just x
-- 自然変換を使った例
-- ListをMaybeに変換し、MaybeをEitherに変換する
example :: forall a b. List a -> Either () b
example = trans maybeToEither <<< listToMaybe
-- テスト
test :: Unit -> Either () Int
test _ = example [1, 2, 3]
main :: Effect Unit
main = do
case test unit of
Left _ -> log "Error: List is empty."
Right n -> log $ "The first element is " <> show n
このコードでは、Control.Natural
モジュールを使用して自然変換を定義しています。maybeToEither関数
は、Maybe
モナドからEither
モナドへの変換を行います。listToMaybe関数
は、List
モナドからMaybe
モナドへの変換を行います。そして、example
関数は、これらの自然変換を合成して、List
をEither
に変換する関数を作成します。
最後に、test
関数を使用してexample
関数をテストし、main
関数で結果をログに出力しています。
おお、なんとなくそれっぽいコードが出来ました! 解説もしてくれました。
(モナドとは、関手の一種で、関手であるとともに、更にいくつかの条件がついています)
少し説明を追加すると、このコードは自然変換を合成(垂直合成)して、新しく自然変換を作成しているコードになっています。また、正確にはEitherは関手ではなく2階の高カインド型であり、引数を二つとります。引数の一つ目をUnitととしたEither Unit が関手になります。
とはいえ、実はこれをそのまま自分の環境で動作させようとしたのですが、残念ながら、動きませんでした。そもそも trans という関数はいらないようです。もしかしてバージョンが古い?
ということで、ChatGPTに「最新のPureScriptに対応させて下さい」と尋ねたところ、無常にも
最新のPureScript (version 0.14.3) に対応した自然変換のサンプルコードです。
と回答されてしましました。でも、2023年3月18日現在、PureScriptの最新バージョンは0.15.8のはずです。そこで「最新は0.15.8なんですけど」と話したところ、
申し訳ありません、PureScriptの最新バージョンは0.15.8でした。以下は、最新版に対応した自然変換のサンプルコードです。
と、きちんと謝って訂正してくれました。(この間、もしかして裏ではWeb検索が走って、最新版の情報を確認してくれたのでしょうか? この時点のChatGPTではそうした機能はまだないはずなので、深層学習で最適解の次に確からしい回答を選んだのでしょうか?)
とはいえ残念ながら、コードはほとんど変わらず、手元の環境では動きませんでした。プログラミングに特化したAIではないため、このあたりが今は限界のようです。あとは、PureScriptがマイナー言語であることも影響しているかも知れません。PureScriptのエラーメッセージを渡してさらに問答すれば、正しいコードに手直ししてくれるのかも知れませんが、もちろん、いずれに しても、ここまでで十分にすごいことです。こんな風にちょっと話しかけただけでコードを生成してくれるなんて、、。しかも、どういう機能を実装して欲しいかとかは何も説明していない、あるいみ無茶振りのコードでしたから。
ということで、今回はここで少々手動で修正を行い、動作するようになったコードは以下になります。
module Main where
import Prelude (map, type (~>), Unit, show, unit, ($), (<<<), (<>), (+), pure, bind, discard)
import Data.Maybe (Maybe(..))
import Data.Either (Either(..))
import Data.List (List(..), (:))
import Effect (Effect)
import Effect.Console (log)
-- MaybeモナドからEitherモナドへの自然変換
maybeToEither :: Maybe ~> Either Unit
maybeToEither Nothing = Left unit
maybeToEither (Just x) = Right x
-- ListモナドからMaybeモナドへの自然変換
listToMaybe :: List ~> Maybe
listToMaybe Nil = Nothing
listToMaybe (Cons x _) = Just x
-- 自然変換を使った例
-- ListをMaybeに変換し、MaybeをEitherに変換する
listToEither :: List ~> Either Unit
listToEither = maybeToEither <<< listToMaybe
-- テスト
test :: Effect (Either Unit Int)
test = pure $ listToEither (1:2:3:Nil)
main :: Effect Unit
main = do
result <- test
case result of
Left _ -> log "Error: Array is empty."
Right n -> log $ "The first element is " <> show n
これを実行して
The first element is 1
と表示されれば成功です。
なお、ちょっと分かりにくいかも知れませんが、上のプログラムの自然変換の関数の「~>」は「->」ではないので注意して下さい。「~>」の「~」はハイフンではなくチルダです。PureScriptのライブラリにはせっかく自然変換の演算子~>が定義されていて、ChatGPTでもそれを読み込んでいるのに使われていなかったので使ってみました。内容的には、Maybe ~> Either Unit は forall a. Maybe a -> Either Unit a と同じです。
補足2:ここまで読んで、でも結局この変換(maybeToEither関数やlistToMaybe関数)って、どうやって自然性は満たしているの? と疑問に思われたかもしれません。実は、これらは自動的に自然性を満たしています。Free Theoremという定理があり、パラメータ多相(ジェネリクス)な関数は、自動的に自然性を満たすことが知られています。(引数の型をチェックして、型ごとに別の処理をするなどしてしまうと、もちろんダメです)
Free Theoremは、つまり「List a型のaが具体的になんであるかにかかわらず、それをMaybe a型に変換している関数」であれば、「a型をa型に写すような関数」を「先にmapさせても後からmapさせても同じになる」という、直感的には当然に思える仕組みを、定理として保障しています。
以上、こんな簡単にいろいろコードを出力してくれるのはありがたいですし、特に、プログラミングの勉強を独学していくのにもすごい有効だなと思いました。
また、ChatGPTはどんどん学習していると思うので、いずれ最初から正しいコードを出力出来るかも知れません。それもまた楽しみです。