Haskellのドット演算子:詳細な説明が必要


87

私はこのHaskellコードでドット演算子が何をしているのかを理解しようとしています:

sumEuler = sum . (map euler) . mkList

ソースコード全体は以下のとおりです。

私の理解

ドット演算子は、2つの関数sumとの結果map eulerおよびの結果をmkList入力として受け取ります。

しかし、sum関数ではなく、関数の引数ですよね?では、ここで何が起こっているのでしょうか?

また、何をし(map euler)ているのですか?

コード

mkList :: Int -> [Int]
mkList n = [1..n-1]

euler :: Int -> Int
euler n = length (filter (relprime n) (mkList n))

sumEuler :: Int -> Int
sumEuler = sum . (map euler) . mkList

回答:


139

簡単に言えば、.数学と同じように、関数の合成です。

f (g x) = (f . g) x

あなたの場合、sumEuler次のように定義することもできる新しい関数を作成しています。

sumEuler x = sum (map euler (mkList x))

この例のスタイルは「ポイントフリー」スタイルと呼ばれ、関数の引数は省略されています。これにより、多くの場合、コードがより明確になります。(最初に見たときは理解するのが難しいかもしれませんが、しばらくすると慣れます。これは一般的なHaskellのイディオムです。)

それでも混乱する場合は.、UNIXパイプのようなものに関連することが役立つ場合があります 。場合fの出力となりg、その出力となり、S入力'hの入力を、次のようなコマンドラインでそれを記述しますf < x | g | h。Haskellでは.、UNIXのように機能します|が、「逆方向」です- h . g . f $ x。この表記は、たとえばリストを処理するときに非常に役立つと思います。のような扱いにくい構造の代わりにmap (\x -> x * 2 + 10) [1..10]、と書くことができます(+10) . (*2) <$> [1..10]。(そして、その関数を単一の値にのみ適用したい場合、それは(+10) . (*2) $ 10一貫性があります!)

Haskell wikiには、もう少し詳細な記事があります:http//www.haskell.org/haskellwiki/Pointfree


1
ちっぽけなこと:最初のコードスニペットは実際には有効なHaskellではありません。
swiftsNamesake 2017年

2
@SwiftsNamesake Haskellに堪能でない私たちにとって、単一の等号はここでは意味がないということですか?(つまり、スニペットは「f (g x)= (f . g) x」でフォーマットされている必要がありますか?)または他の何か?
user234461 2018

1
@ user234461その通りです。==有効な標準のHaskellが必要な場合は、代わりに必要になります。
SwiftsNamesake 2018

上の小さなスニペットはただの金です。ここにある他の回答と同様に正しいですが、そのスニペットは私の頭の中で直観的に直接クリックしただけなので、残りの回答を読む必要はありませんでした。
タリックウェリング

24

。演算子は関数を構成します。例えば、

a . b

abが関数である場合引数に対してbを実行し、次にそれらの結果に対してaを実行する新しい関数です。あなたのコード

sumEuler = sum . (map euler) . mkList

とまったく同じです:

sumEuler myArgument = sum (map euler (mkList myArgument))

しかし、うまくいけば読みやすくなります。周りに括弧がある理由地図オイラーは、それが構成されている3つの機能があること、それが明確になるためである:サム地図オイラーmkList -地図オイラーは、単一の関数です。


24

sumHaskell Preludeの関数であり、の引数ではありませんsumEuler。タイプがあります

Num a => [a] -> a

関数合成演算子に. はタイプがあります

(b -> c) -> (a -> b) -> a -> c

だから私たちは持っています

           euler           ::  Int -> Int
       map                 :: (a   -> b  ) -> [a  ] -> [b  ]
      (map euler)          ::                 [Int] -> [Int]
                    mkList ::          Int -> [Int]
      (map euler) . mkList ::          Int ->          [Int]
sum                        :: Num a =>                 [a  ] -> a
sum . (map euler) . mkList ::          Int ->                   Int

それIntは確かに型Numクラスのインスタンスであることに注意してください。


11

。関数の合成には演算子が使用されます。数学と同じように、関数f(x)とg(x)fが必要な場合。gはf(g(x))になります。

mapは、関数をリストに適用する組み込み関数です。関数を括弧で囲むことにより、関数は引数として扱われます。これの用語はカリー化です。あなたはそれを調べなければなりません。

つまり、2つの引数を持つ関数を取り、引数オイラーを適用します。(マップオイラー)そうですか?その結果、引数を1つだけ取る新しい関数が作成されます。

合計。(マップオイラー)。mkListは基本的に、これらすべてをまとめる素晴らしい方法です。私のHaskellは少し錆びていますが、最後の機能を自分で組み合わせることができるかもしれません。


6

Haskellのドット演算子

私はこのHaskellコードでドット演算子が何をしているのかを理解しようとしています:

sumEuler = sum . (map euler) . mkList

簡潔な答え

ドットのない同等のコード、それはただ

sumEuler = \x -> sum ((map euler) (mkList x))

またはラムダなし

sumEuler x = sum ((map euler) (mkList x))

ドット(。)は関数の合成を示しているためです。

長い答え

まず、eulertoの部分適用を単純化しましょうmap

map_euler = map euler
sumEuler = sum . map_euler . mkList

今、私たちはただ点を持っています。これらの点は何を示していますか?

ソースから:

(.)    :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)

したがって(.)作成演算子です。

作曲

数学では、関数の合成f(x)とg(x)、つまりf(g(x))を次のように書くことができます。

(f∘g)(x)

これは「fはgで構成されている」と読むことができます。

したがって、Haskellでは、f∘g、またはgで構成されたfは次のように書くことができます。

f . g

合成は結合法則です。つまり、合成演算子を使用して記述されたf(g(h(x)))は、あいまいさなしに括弧を省略できます。

つまり、(f∘g)∘hはf∘(g∘h)と同等であるため、f∘g∘hと簡単に書くことができます。

振り返る

以前の簡略化に戻ります。これは次のとおりです。

sumEuler = sum . map_euler . mkList

これsumEulerは、これらの関数の適用されていない構成であることを意味します。

sumEuler = \x -> sum (map_euler (mkList x))

4

ドット演算子は、左側の関数(sum)を右側の関数の出力に適用します。あなたの場合、いくつかの関数を連鎖させています-の結果をmkList(map euler)渡し、次にその結果をに渡しsumます。 このサイトには、いくつかの概念の良い紹介があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.