누군가 Haskell의 트래버스 기능을 설명 할 수 있습니까?


99

에서 traverse기능을 시도하고 실패했습니다 Data.Traversable. 나는 그 요점을 볼 수 없다. 내가 명령형 배경에서 왔기 때문에 누군가 명령형 루프 측면에서 나에게 설명해 주시겠습니까? 의사 코드를 많이 주시면 감사하겠습니다. 감사.


1
The Essence of the Iterator Pattern 기사 단계별 트래버스 개념을 구축하므로 도움이 될 수 있습니다. 일부 고급 개념은 비록 존재
재키

답변:


121

traversefmap데이터 구조를 재 구축하는 동안 효과를 실행할 수 있다는 점을 제외 하면과 동일 합니다.

Data.Traversable문서 에서 예제를 살펴보십시오 .

 data Tree a = Empty | Leaf a | Node (Tree a) a (Tree a)

Functor인스턴스 Tree는 다음과 같습니다.

instance Functor Tree where
  fmap f Empty        = Empty
  fmap f (Leaf x)     = Leaf (f x)
  fmap f (Node l k r) = Node (fmap f l) (f k) (fmap f r)

f모든 값에 적용하여 전체 트리 를 다시 작성합니다.

instance Traversable Tree where
    traverse f Empty        = pure Empty
    traverse f (Leaf x)     = Leaf <$> f x
    traverse f (Node l k r) = Node <$> traverse f l <*> f k <*> traverse f r

Traversable생성자가 실용적 스타일이라고를 제외하고 인스턴스는 거의 동일합니다. 이것은 우리가 트리를 재건하는 동안 (부작용) 효과를 가질 수 있음을 의미합니다. Applicative는 효과가 이전 결과에 의존 할 수 없다는 점을 제외하면 모나드와 거의 동일합니다. 이 예에서 이는 예를 들어 왼쪽 브랜치를 재 구축 한 결과에 따라 노드의 오른쪽 브랜치에 다른 작업을 수행 할 수 없음을 의미합니다.

역사적인 이유로, Traversable클래스는의 모나드 버전이 들어 traverse라는를 mapM. 모든 의도와 목적 mapM에 대해 동일 합니다. 나중에 야 슈퍼 클래스가 traverse되었기 때문에 별도의 메서드로 존재합니다 .ApplicativeMonad

이를 불순한 언어로 구현하면 부작용을 방지 할 수있는 방법이 없기 때문에과 fmap동일 traverse합니다. 데이터 구조를 재귀 적으로 탐색해야하므로 루프로 구현할 수 없습니다. 다음은 Javascript에서 수행하는 방법에 대한 작은 예입니다.

Node.prototype.traverse = function (f) {
  return new Node(this.l.traverse(f), f(this.k), this.r.traverse(f));
}

이렇게 구현하면 언어가 허용하는 효과로 제한됩니다. 비결정론 (응용 모델의 목록 인스턴스)을 원하고 언어에 내장되지 않은 경우 운이 좋지 않습니다.


11
'효과'라는 용어는 무엇을 의미합니까?
missingfaktor 2011 년

24
@missingfaktor : Functor파라 메트릭이 아닌 부품 의 구조 정보를 의미합니다 . 의 상태 값 State, Maybe및의 실패 Either,의 요소 수 []및의 임의의 외부 부작용입니다 IO. 나는 그것을 일반적인 용어 ( Monoid"empty"와 "append"를 사용 하는 함수 처럼, 개념이 처음에 제안하는 것보다 더 일반적 임) 로 신경 쓰지 않지만 꽤 일반적이고 충분히 목적을 수행한다.
CA 맥캔

@CA McCann : 알겠습니다. 대답 해줘서 고마워!
missingfaktor 2011 년

1
"나는 당신이 이것을해서는 안된다고 확신한다 ...]." 확실히 아닙니다. 효과를 ap이전 결과 에 의존 하게 만드는 것만큼이나 불쾌 할 것 입니다. 나는 그에 따라 그 발언을 다시 말 하였다.
duplode

2
"적용적인 것은 효과가 이전 결과에 의존 할 수 없다는 점을 제외하면 모나드와 거의 동일합니다." ...이 라인을 통해 많은 것을 클릭했습니다. 감사합니다!
agam

58

traverses를 사물로 만드는 함수가 주어지면 내부의 사물을 "내부"사물 Traversable로 바꿉니다 .TraversableApplicativeApplicative

하자의 사용 MaybeApplicative과 같은 및 목록 Traversable. 먼저 변환 함수가 필요합니다.

half x = if even x then Just (x `div` 2) else Nothing

따라서 숫자가 짝수이면 절반 (a 내부 Just)을 얻습니다 Nothing. 그렇지 않으면 . 모든 것이 "잘"되면 다음과 같이 보입니다.

traverse half [2,4..10]
--Just [1,2,3,4,5]

그러나...

traverse half [1..10]
-- Nothing

그 이유는 <*>함수가 결과를 작성하는 데 사용되며 인수 중 하나 Nothing가이면 Nothing다시 반환되기 때문입니다.

다른 예시:

rep x = replicate x x

이 함수 x는 내용 x(예 : rep 3=) 이있는 길이 목록을 생성합니다 [3,3,3]. 결과는 traverse rep [1..3]무엇입니까?

우리는의 부분적인 결과를 얻을 수 [1], [2,2][3,3,3]사용 rep. 이제 목록의 의미가 아니라 Applicatives"모든 조합을"있다, 예를 들면 (+) <$> [10,20] <*> [3,4]이다 [13,14,23,24].

의 "모든 조합" [1]과는 [2,2]두 번이다 [1,2]. 모든 두 배의 조합 [1,2]과는 [3,3,3]여섯 번입니다 [1,2,3]. 그래서 우리는 :

traverse rep [1..3]
--[[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]

1
최종 결과는 나에게 이것을 생각 나게 합니다 .
hugomg 2011 년

3
@missingno : 네, 놓친fac n = length $ traverse rep [1..n]
Landei

1
실제로는 "List-encoding-programmer"아래에 있습니다 (하지만 목록 이해력 사용). 그 웹 사이트는 포괄적입니다 :)
hugomg

1
@missingno : 흠, 정확히 똑같지 는 않습니다 ... 둘 다 목록 모나드의 데카르트 제품 동작에 의존하고 있지만 사이트는 한 번에 두 개만 사용하므로 .NET을 사용 liftA2 (,)하는보다 일반적인 형식보다 수행하는 것과 비슷 traverse합니다.
CA McCann 2011 년

42

나는 다음과 같이 정의 할 수 sequenceA있듯이 으로 이해하는 것이 가장 쉽다고 생각합니다 traverse.

traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f

sequenceA 구조의 요소를 왼쪽에서 오른쪽으로 함께 시퀀스하여 결과를 포함하는 동일한 모양의 구조를 반환합니다.

sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)
sequenceA = traverse id

sequenceA두 펑터의 순서를 반대로하는 것으로 생각할 수도 있습니다 . 예를 들어 작업 목록에서 결과 목록을 반환하는 작업으로 이동합니다.

따라서 traverse일부 구조를 취하고 구조의 f모든 요소를 ​​일부 응용 프로그램으로 변환하는 데 적용한 다음 해당 응용 프로그램의 효과를 왼쪽에서 오른쪽으로 시퀀스하여 결과를 포함하는 동일한 모양의 구조를 반환합니다.

또한 Foldable관련 기능을 정의하는와 비교할 수도 있습니다 traverse_.

traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f ()

그래서 Foldable와 의 주요 차이점 Traversable은 후자는 구조의 모양을 보존 할 수있게 해주는 반면 전자는 결과를 다른 값으로 접어야한다는 것입니다.


사용법의 간단한 예는 목록을 순회 가능한 구조 IO로 사용하는 것입니다.

λ> import Data.Traversable
λ> let qs = ["name", "quest", "favorite color"]
λ> traverse (\thing -> putStrLn ("What is your " ++ thing ++ "?") *> getLine) qs
What is your name?
Sir Lancelot
What is your quest?
to seek the holy grail
What is your favorite color?
blue
["Sir Lancelot","to seek the holy grail","blue"]

이 예제는 다소 흥미롭지 않지만 traverse다른 유형의 컨테이너에 사용하거나 다른 응용 프로그램을 사용 하면 상황이 더 흥미로워 집니다.


트래버스는 단순히 더 일반적인 형태의 mapM일까요? 사실, sequenceA . fmap목록은 다음과 동일 sequence . map하지 않습니까?
Raskell

'시퀀싱 부작용'이란 무엇을 의미합니까? 귀하의 답변에서 '부작용'은 무엇입니까-부작용은 모나드에서만 가능하다고 생각했습니다. 문안 인사.
Marek

1
@Marek "나는 단지 부작용이 모나드에서만 가능하다고 생각했습니다."-연결은 그것보다 훨씬 느슨합니다. (1) IO 유형 은 부작용을 표현하는 데 사용될 수 있습니다. (2) IO우연히 모나드는 매우 편리합니다. 모나드는 본질적으로 부작용과 관련이 없습니다. 또한 일반적인 의미에서 "부작용"보다 더 넓은 "효과"의 의미가 있다는 점에 유의해야합니다. 순수한 계산을 포함하는 것입니다. 이 마지막 요점에 대해서는 "효과적"이란 정확히 무엇을 의미하는지 참조하십시오 .
duplode

(그런데 @hammar, 위의 설명에 요약 된 이유 때문에이 답변에서 "부작용"을 "효과"로 자유롭게 변경할 수 있습니다.)
duplode

17

그것은 종류 등의 fmap당신은 또한 결과 유형을 변경하는 매퍼 기능, 내부 효과를 실행할 수있는 것을 제외하고.

데이터베이스에서 사용자 ID를 나타내는 정수 목록을 상상해보십시오 [1, 2, 3].. 당신이 원하는 경우 fmap사용자 이름에 이러한 사용자 ID, 당신은 전통을 사용할 수 없습니다 fmap(- 사용하여,이 경우 효과를 필요로하는 함수 내에서 사용자가 사용자 이름을 읽을 수있는 데이터베이스에 액세스해야하기 때문에, IO모나드를).

의 서명 traverse은 다음과 같습니다.

traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)

를 사용하면 traverse효과를 낼 수 있으므로 사용자 ID를 사용자 이름에 매핑하는 코드는 다음과 같습니다.

mapUserIDsToUsernames :: (Num -> IO String) -> [Num] -> IO [String]
mapUserIDsToUsernames fn ids = traverse fn ids

다음과 같은 함수도 있습니다 mapM.

mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)

의 모든 사용은로 mapM바꿀 수 traverse있지만 그 반대는 아닙니다. mapM모나드에서만 작동하지만 traverse더 일반적입니다.

효과를 얻고 유용한 값을 반환하지 않으려는 경우 이러한 함수의 traverse_mapM_버전이 있으며 둘 다 함수의 반환 값을 무시하고 약간 더 빠릅니다.



7

traverse 루프. 그 구현은 순회 할 데이터 구조에 따라 다릅니다. 즉 목록, 나무, 수도 Maybe, SeqA에 대한 루프 또는 재귀 함수 같은 것을 통해 통과되는 일반적인 방법이있다 (영향력), 또는 아무것도. 배열은 for-loop, list는 while-loop, 트리는 재귀 적이거나 while-loop와 스택의 조합을 가질 것입니다. 그러나 기능적 언어에서는 이러한 성가신 루프 명령이 필요하지 않습니다. 루프의 내부 부분 (함수 형태)을 데이터 구조와보다 직접적이고 덜 장황하게 결합합니다.

Traversabletypeclass를 사용하면 알고리즘을보다 독립적이고 다용도로 작성할 수 있습니다. 그러나 내 경험에 따르면 Traversable일반적으로 기존 데이터 구조에 알고리즘을 붙이는 데만 사용됩니다. 자격을 갖춘 다른 데이터 유형에 대해 유사한 함수를 작성할 필요가없는 것도 매우 좋습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.