Haskellのドット演算子:さらに説明が必要
-
08-07-2019 - |
質問
このHaskellコードでドット演算子が何をしているのかを理解しようとしています:
sumEuler = sum . (map euler) . mkList
ソースコード全体は以下のとおりです。
私の理解
ドット演算子は、2つの関数sum
とmap euler
の結果およびmkList
の結果を入力として取ります。
しかし、(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
解決
簡単に言えば、.
は数学のように関数合成です:
f (g x) = (f . g) x
あなたの場合、新しい関数sumEuler
を作成しています。これは、次のように定義することもできます。
sumEuler x = sum (map euler (mkList x))
この例のスタイルは、<!> quot; point-free <!> quot;と呼ばれます。スタイル-関数の引数は省略されます。これにより、多くの場合、コードがより明確になります。 (初めて見たときは理解しにくいかもしれませんが、しばらくすると慣れるでしょう。Haskellの一般的なイディオムです。)
まだ混乱している場合は、f
をUNIXパイプのようなものに関連付けると役立つ場合があります。 g
の出力がh
の入力になり、その出力がf < x | g | h
の入力になる場合、コマンドラインに|
のように記述します。 Haskellでは、h . g . f $ x
はUNIX map (\x -> x * 2 + 10) [1..10]
と同様に機能しますが、<!> quot; backwards <!> quot; -(+10) . (*2) <$> [1..10]
。たとえば、リストを処理するときに、この表記法が非常に役立つことがわかります。 (+10) . (*2) $ 10
のような扱いにくい構成の代わりに、単に<=>と書くことができます。 (そして、その関数を単一の値にのみ適用したい場合、それは<=>です。一貫しています!)
Haskell wikiには、さらに詳細な記事があります: http://www.haskell.org/ haskellwiki / Pointfree
他のヒント
演算子は関数を構成します。たとえば、
a . b
a と b が関数である場合は、引数に対してbを実行し、それらの結果に対してaを実行する新しい関数です。あなたのコード
sumEuler = sum . (map euler) . mkList
は次とまったく同じです
sumEuler myArgument = sum (map euler (mkList myArgument))
しかし、うまくいけば読みやすくなります。 map euler の周りに括弧があるのは、 sum 、 map euler および mkList - map euler は単一の関数です。
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
Int
はNum
のインスタンスであることに注意してください。
演算子は関数の構成に使用されます。数学のように、関数f(x)とg(x)fが必要な場合。 gはf(g(x))になります。
mapは、リストに関数を適用する組み込み関数です。関数を括弧で囲むことにより、関数は引数として扱われます。この用語は、カリーです。調べてください。
は、2つの引数を持つ関数を受け取り、引数オイラーを適用するということです。 (マップオイラー)そうですか?結果は新しい関数になり、引数は1つだけになります。
sum。 (マップオイラー)。 mkListは基本的に、それらすべてをまとめる素晴らしい方法です。私のHaskellは少しさびていますが、最後の機能を自分で組み立てることができますか?
ドット演算子は、左側の関数(sum
)を右側の関数の出力に適用します。あなたの場合、あなたはいくつかの関数を連鎖させています-mkList
の結果を(map euler)
に渡し、その結果を<=>に渡します。
このサイトには、いくつかの概念が詳しく紹介されています。
Haskellのドット演算子
このHaskellコードでドット演算子が何をしているのかを理解しようとしています:
sumEuler = sum . (map euler) . mkList
簡単な回答
ドットなしの同等のコード、それはただ
sumEuler = \x -> sum ((map euler) (mkList x))
またはラムダなし
sumEuler x = sum ((map euler) (mkList x))
ドット(。)は関数の構成を示すため。
長い回答
最初に、euler
から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 <!>#8728; g)(x)
これは読むことができます<!> quot; fはg <!> quot;で構成されます。
つまり、Haskellでは、f <!>#8728; gまたはfをgで構成すると、次のように記述できます。
f . g
構成は結合的です。つまり、構成演算子で記述されたf(g(h(x)))は、あいまいさなしに括弧を省略できます。
つまり、(f <!>#8728; g)<!>#8728; hはf <!>#8728と同等です。 (g <!>#8728; h)、単にf <!>#8728;と書くことができます。 g <!>#8728; h。
戻る
以前の単純化に戻って、これ:
sumEuler = sum . map_euler . mkList
は、単にsumEuler
がこれらの関数の適用されていない構成であることを意味します。
sumEuler = \x -> sum (map_euler (mkList x))