43

패턴 (f .) . g에 따라 정의 된 많은 기능을 보아 왔습니다. 예 :(f.)는 무엇입니까? 하스켈에서의 평균은?

countWhere = (length .) . filter 
duplicate = (concat .) . replicate 
concatMap = (concat .) . map 

이것은 무엇을 의미합니까?

+4

(F입니다.).g는 원저자의 코드에 대한 훌륭한 위장 된 의견의 일부일 수도 있습니다. – Marton

+1

그게 무슨 뜻인지 잘 모르겠습니다. –

+0

이것은 저자가 영리 해지고 읽을 수없는 코드를 작성하는 것을 의미합니다. ;) – tibbe

답변

82

도트 연산자 ((.))는 function composition 연산자입니다. 당신이 그것을 볼 수 있듯이

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

유형 b -> c의 기능과 유형 a -> b의 다른 기능을 소요하고 처음으로 두 번째 함수의 결과를 적용 유형 a -> c의 기능 (예를 반환합니다 다음과 같이 정의된다 기능).

함수 합성 연산자는 매우 유용합니다. 이 함수를 사용하면 한 함수의 출력을 다른 함수의 입력으로 파이프 할 수 있습니다.

main = interact (\x -> unlines (reverse (lines x))) 

을하지 가독성 다음과 같이 예를 들어, 당신은 하스켈에서 tac 프로그램을 작성할 수 있습니다. 다음과 같이 당신이 그것을 쓸 수 있지만 기능 성분을 사용하여 :

main = interact (unlines . reverse . lines) 

당신이 함수의 합성을 볼 수 있듯이 매우 유용하지만 당신은 모든 곳에서 사용할 수 없습니다. 예를 들어 사용자가 기능 할 수없는 파이프 조성물을 사용 lengthfilter 출력 : filter 유형의 (a -> Bool) -> [a] -> [a] 때문에

countWhere = length . filter -- this is not allowed 

이 허용되지 않는 이유이다. a -> b과 비교하면 a(a -> Bool)이고 b[a] -> [a]입니다. 하스켈은 lengthb -> c (즉, ([a] -> [a]) -> c)이 될 것으로 예상하므로 형식이 일치하지 않습니다. 실제로는 [a] -> Int 유형입니다.

해결책은 아주 간단합니다 :

countWhere f = length . filter f 

그러나 어떤 사람들은 그 여분 매달려 f을 좋아하지 않는다.

countWhere = (length .) . filter 

을 그들은이를 얻는 방법 : 그들은 다음과 같이 pointfree 스타일 countWhere를 작성하는 것을 선호? 고려 :

countWhere f xs = length (filter f xs) 

-- But `f x y` is `(f x) y`. Hence: 

countWhere f xs = length ((filter f) xs) 

-- But `\x -> f (g x)` is `f . g`. Hence: 

countWhere f = length . (filter f) 

-- But `f . g` is `(f .) g`. Hence: 

countWhere f = (length .) (filter f) 

-- But `\x -> f (g x)` is `f . g`. Hence: 

countWhere = (length .) . filter 

당신이 (f .) . g을 볼 수 있듯이 단순히 \x y -> f (g x y)입니다. 이 개념은 실제로 반복 될 수 있습니다.

f . g    --> \x -> f (g x) 
(f .) . g   --> \x y -> f (g x y) 
((f .) .) . g  --> \x y z -> f (g x y z) 
(((f .) .) .) . g --> \w x y z -> f (g w x y z) 

꽤 좋지는 않지만 끝내줍니다.두 가지 기능을 감안할 때 당신은 또한 당신의 자신의 기능 구성 사업자를 작성할 수 있습니다

f .: g = (f .) . g 
f .:: g = ((f .) .) . g 
f .::: g = (((f .) .) .) . g 

을 대신 다음과 같이 countWhere을 쓸 수있는 (.:) 연산자를 사용 :

countWhere = length .: filter 

을 흥미롭게도이 점 자유로운 스타일로 (.:)을 쓸 수 있지만 잘 :

f .: g = (f .) . g 

-- But `f . g` is `(.) f g`. Hence: 

f .: g = (.) (f .) g 

-- But `\x -> f x` is `f`. Hence: 

(f .:) = (.) (f .) 

-- But `(f .)` is `((.) f)`. Hence: 

(f .:) = (.) ((.) f) 

-- But `\x -> f (g x)` is `f . g`. Hence: 

(.:) = (.) . (.) 

마찬가지로 우리가 얻을 :

(.::) = (.) . (.) . (.) 
(.:::) = (.) . (.) . (.) . (.) 

당신이 (.:), (.::)(.:::)(.)의 정당한 권력은 있습니다 볼 수있는 (즉, 그들은 iterated functions(.)입니다). 수학 번호는 : 수학 함수에 대한 유사

x^0 = 1 
x^n = x * x^(n - 1) 

:

f .^ 0 = id 
f .^ n = f . (f .^ (n - 1)) 

f 경우는 (.)이다 :이 문서의 끝 부분에 가까운 우리에게 가져다

(.) .^ 1 = (.) 
(.) .^ 2 = (.:) 
(.) .^ 3 = (.::) 
(.) .^ 4 = (.:::) 

.

mf = compose map filter 
: 당신이 지금과 같이 mf을 쓸 수

compose f g = (. f) . (.) . g 

compose f g = ((. f) . (.)) . g 

compose f g = (.) ((. f) . (.)) g 

compose f = (.) ((. f) . (.)) 

compose f = (.) ((. (.)) (. f)) 

compose f = ((.) . (. (.))) (. f) 

compose f = ((.) . (. (.))) (flip (.) f) 

compose f = ((.) . (. (.))) ((flip (.)) f) 

compose = ((.) . (. (.))) . (flip (.)) 

compose을 사용하여 다음과 같이

mf a b c = filter a (map b c) 

mf a b c = filter a ((map b) c) 

mf a b = filter a . (map b) 

mf a b = (filter a .) (map b) 

mf a = (filter a .) . map 

mf a = (. map) (filter a .) 

mf a = (. map) ((filter a) .) 

mf a = (. map) ((.) (filter a)) 

mf a = ((. map) . (.)) (filter a) 

mf = ((. map) . (.)) . filter 

mf = (. map) . (.) . filter 

우리는 더이 문제를 단순화 할 수 있습니다 : 최종 도전의이 pointfree 스타일에 다음 함수를 작성하자

네, 약간 못 생겼지 만 정말 멋진 생각이기도합니다. 이제 \x y z -> f x (g y z) 형식의 함수를 compose f g으로 쓸 수 있으며 매우 깔끔합니다.

+2

'(.)^i '형식의 표현식은 형식이 올바르지 않으므로 실제 하스켈이 아닙니다. –

+1

참. 그러나 나는 수학 함수의 경우와 유사하게 "_을 쓰고 수학적 설명이므로 숫자 대신 함수에'^'를 사용하는 것이 좋습니다. 그럼에도 불구하고 연산자를 두 개를 구별하기 위해'.^'로 바꿀 것입니다. –

+0

나는 수학에서'(.)^i'도보고 놀랐습니다. 아마도 그러한 유형의 공식 프레임 워크는 종속 유형 이론에 존재합니다. 그것은 흥미로울 것이다. –

11

이것은 맛의 문제이지만, 나는 그러한 스타일이 불쾌하다고 생각합니다. 먼저 의미가 무엇인지 설명하고 선호하는 대안을 제안합니다.

어떤 조작자에 대해서도 (f . g) x = f (g x)(f ?) x = f ? x을 알아야합니다. ?. 이로부터 우리는

countWhere p = ((length .) . filter) p 
       = (length .) (filter p) 
       = length . filter p 

그렇게

countWhere p xs = length (filter p xs) 

내가 함수를 사용하는 것을 선호한다는 추론 그리고 countWhere = length .: filter.:

(.:) :: (r -> z) -> (a -> b -> r) -> a -> b -> z 
(f .: g) x y = f (g x y) 

이라고 할 수 있습니다. 개인적으로 나는 이것이 훨씬 더 명확하다는 것을 안다.

(.: 너무 Data.Composition 아마 다른 장소에 정의되어 있습니다.)

+0

'(. :) = fmap fmap fmap'으로'(. :)를 정의 할 수도 있습니다. 그것은 당신이 어떤 functor를 위해 사용할 수 있기 때문에 더 일반적입니다. 예를 들어'(* 2). : Just [1..5]'를 할 수 있습니다. 물론'(. :) :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)'의 올바른 형태의 서명을 주어야합니다. –

+6

@AaditMShah이 경우,'<$$> = fmap과 같은 것을 선호합니다. '(. :)'는'(->) r'을 전문으로하고''fmap''은'(->) r' 펑터에 있기 때문에'fmap'을 사용합니다. – kqr