web-dev-qa-db-ja.com

Haskellのドット演算子:さらに説明が必要

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

sumEuler = sum . (map euler) . mkList

ソースコード全体を以下に示します。

私の理解

ドット演算子は、2つの関数summap 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
77
cbrulak

簡単に言えば、_._は数学のように関数合成です:

_f (g x) = (f . g) x
_

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

_sumEuler x = sum (map euler (mkList x))
_

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

それでも混乱している場合は、_._をUNIXパイプのようなものに関連付けると役立つ場合があります。 fの出力がgの入力になり、その出力が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

127
jrockway

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

a . b

aおよびbは関数です新しい関数引数に対してbを実行し、それらの結果に対してaを実行します。あなたのコード

sumEuler = sum . (map euler) . mkList

以下とまったく同じです。

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

しかし、うまくいけば読みやすくなります。 map eulerの周りに括弧があるのは、3つの関数が構成されていることを明確にするためです:sum map eulerおよびmkList -map eulerは単一の関数です。

23
Jesse Rusak

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

Num a => [a] -> a

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

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

だから私たちは

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

IntNumのインスタンスであることに注意してください。

21
Chris Conway

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

mapは、リストに関数を適用する組み込み関数です。関数を括弧で囲むことにより、関数は引数として扱われます。この用語は currying です。調べてみてください。

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

合計(マップオイラー)。 mkListは、基本的にすべてをまとめた派手な方法です。私のHaskellは少し錆びていますが、最後の機能を自分で組み立てることができますか?

11
John Leidegren

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

4
Andy Mikula

Haskellのドット演算子

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

sumEuler = sum . (map euler) . mkList

短い答え

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

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

またはラムダなし

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

ドット(。)は関数の構成を示すためです。

より長い答え

まず、eulermapへの部分適用を単純化しましょう。

map_euler = map euler
sumEuler = sum . map_euler . mkList

これでドットができました。これらのドットは何を示していますか?

ソース から:

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

したがって、(.)compose演算子 です。

構成する

数学では、関数の構成、f(x)およびg(x)、つまりf(g(x))を次のように記述できます。

(f∘g)(x)

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

Haskellでは、f∘g、またはfをgで構成すると、次のように記述できます。

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))
2
Aaron Hall