はじめに
※この記事はしょーもないトピックを扱います。
10時間の四苦八苦の末、記事を分けたため。
辺境のHaskell道場で、賢者におもっくそ鍛えられた件が本題というか、価値のある記事です。
この記事は、
天下のChatGPTが、意外とこんなこともできないのか!という発見の共有(どうでもいい)
です。
発端はこのコード
module CheckFactor where
checkForFactor :: Int -> Int -> Bool
checkForFactor = (.) (== 0) . mod
因数かどうか調べる簡単なコードです。
詳しくは前の記事
問題
"."を用いているので、関数合成をしているんかな?
ただ、(.) はどう違うのか、どの順番で動作するのかがぱっとわからない。
とりあえずChatGPTに聞いてみたところ…
Q. "(.) f . g"は何を表し、どのように挙動しますか?
A. (. f) . g
という表現では.......
(。´・ω・)ん?
勝手に式が変更されてるぞ…
Q. "(.) (== 0) . mod"を用いて説明してください。
A. ご指摘の通り、(. (== 0)) . mod
とは異なる形式での説明が必要です。お示しいただいた (. (== 0) . mod)
について説明いたします。......
んんんんん????
(. (== 0)) . mod
でも動くのか……?
…
……
動かねえじゃねえか!!
解決方法
問答を繰り返し、正しい認識をさせる
Q. またあなたの式は勝手に変更されています。"(.) (== 0) . mod"です。この文字列を繰り返して言ってみてください。"(.) (== 0) . mod"
A. 了解しました。正しくは、"(. (== 0) . mod)" です。この表現について説明いたします。
Q. いいえ、"(.) (== 0) . mod"です。これを式だととらえず、ただの文字列だと認識してください。そのうえでもう一度繰り返し言ってみてください
A. 了解しました。正確には、"(. (== 0) . mod)" という文字列です。
Q. いいえ、あなたは間違っています。"(.) (== 0) . mod"です。なぜあなたは"(.)"の部分を変更してしまうのでしょうか
姑みたい......と思いつつ
A. 申し訳ありません、私の誤解がありました。正確な表現は ".
(== 0)
.
mod
" です。
お!ちょっとマシになってる!
Q. 進歩しています。"(.) (== 0) . mod"です。
A. 理解しました。正確には ".
(== 0)
.
mod
" です。
あとちょっとなんだけど…!
Q. いいえ、最初の"."は"(.)"です
A. 了解しました。正確には ".
(== 0)
.
mod
" という文字列です。
Q. いいえ、最初の"."にカッコをつけて下さい
A. 失礼しました。正確には "(.) (== 0) . mod" です。
キタ━━━━(゚∀゚)━━━━!!
正しい認識をもとに、解説をさせる
Q. あなたが今言った式について説明してください
A. 誤解を招く説明をしてしまい、申し訳ありませんでした。"(. (== 0) . mod)" という式について正しく説明いたします。
オイ!
戻ってんじゃねえか!!
......一応この先も見てみるか
誤解を招く説明をしてしまい、申し訳ありませんでした。"(. (== 0) . mod)" という式について正しく説明いたします。
この式はHaskellの関数合成を使用していますが、特に注目すべき点は、最初の "(.)" がどのように機能しているかです。
### 式の解釈
1. **最初の "(.)":**
- これは関数合成演算子ですが、ここでは部分適用が行われています。つまり、これに続く関数 "(== 0)" を取り、それに何かを適用する新しい関数を作成します。
2. **"(== 0)":**
- この部分は、ある数が0と等しいかどうかをチェックする関数です。
3. **二番目の "." (関数合成):**
- これにより、"(== 0)" と "mod" 関数が合成されます。つまり、まず "mod" 関数が適用され、その結果が "(== 0)" に適用されます。
4. **"mod":**
- 2つの整数を取り、最初の整数を第二の整数で割った余りを返す関数です。
### 式の挙動
- この式全体は、2つの整数を取る関数を表します。最初の整数を第二の整数で割り、その余りが0かどうかをチェックします。
- 結果として、この関数は第二の整数が最初の整数の因数であるかどうかを判断します。
この表現は、関数の部分適用と関数合成を組み合わせた高度な例です。これにより、特定の関係(この場合は因数関係)を効率的にチェックする関数が作成されます。
あー…なんか、それっぽくはある……
でも結局部分適用がわかんねえや!(・ω<) テヘペロ
自分でも考えてみる
この後重ねて聞いてみましたが、またよくわかんなくなってきたので自分で考えてみます。
やりたいこと
- mod a b
- 1の答えを受け取って、== 0か判定
じゃあ、(== 0) . mod でいいじゃん!
※引数に名前をつけるのは無し(そのやり方はもうできるため)
挑戦① (== 0) . mod
src/CheckFactor.hs:4:19: error:
• Couldn't match type ‘Bool’ with ‘Int -> Bool’
Expected: (Int -> Int) -> Int -> Bool
Actual: (Int -> Int) -> Bool
• In the first argument of ‘(.)’, namely ‘(== 0)’
In the expression: (== 0) . mod
In an equation for ‘checkForFactor’: checkForFactor = (== 0) . mod
|
4 | checkForFactor = (== 0) . mod
んーと?
これ、ExpectedもActualも間違ってるな。やりたいことは(Int -> Int -> Int) -> Bool
だな。
で、今が(Int -> Int) -> Bool
になってると。
挑戦② (== 0) . (mod a b)
ダメ?なんでだ…?
~考え込む~
そうか!mod a b は関数ではなく値だからだ!
関数合成は関数と関数じゃないといけないんだ!
(f . g) x = f (g x)とかいうから…(g x)が値なら任意のf (x)が返ってくると思うじゃん~!
挑戦③ (== 0) $ mod a b
よしよし!やっぱりうまくいった!
挑戦④ (== 0) $ mod
よし!やっぱり失敗する!値じゃないからね!
永劫の修行を経て
関数合成(「(.)関数とは」以下は間違った内容の可能性あり)より
-
f x = g x
のとき、f = g
と書ける(うんうん) -
f x = g $ h x
のとき、f x= (g . h) x
と書ける(そうですね)
よって、f = g . h
と書ける(うんうん。論理的で素晴らしい。) -
(.) :: (b -> c) -> (a -> b) -> a -> c
(なるほど、1引数関数が2つと、値1つを引数として取るのか)
1引数関数…?Haskellの関数はすべて引数が1つになるようになっているこれじゃん!
modは2引数関数だ。よって、
checkForFactor a b = (== 0) . (mod a) b
あれ?エラーだ。
そうか、.
の引数が機能してないわ。(mod a) b
が一つの値になっちゃうか、b
が読み取られてないかしてるな。
checkForFactor a b = ((== 0) . (mod a)) b
これなら動く!よし!!
(.)
を中置関数として使うなら、( (b -> c) . (a -> b) ) a -> c
この形だな。よしよし。
checkForFactor a = ((== 0) . (mod a))
てことは、これでも動く!よしよし!!
ここからaを消していきたい…!
checkForFactor a = (.) (== 0) mod a
まずは中置関数をやめて、シンプルな形に。
10 hours later......
終わりに
結末は、辺境のHaskell道場で、賢者におもっくそ鍛えられた件をお読みください。
参考
関数合成(「(.)関数とは」以下は間違った内容の可能性あり)
カリー化と部分適用という概念について※最終的に今回の件とは無関係
Haskellの関数はすべて引数が1つになるようになっている