에서 traverse기능을 시도하고 실패했습니다 Data.Traversable. 나는 그 요점을 볼 수 없다. 내가 명령형 배경에서 왔기 때문에 누군가 명령형 루프 측면에서 나에게 설명해 주시겠습니까? 의사 코드를 많이 주시면 감사하겠습니다. 감사.
답변:
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));
}
이렇게 구현하면 언어가 허용하는 효과로 제한됩니다. 비결정론 (응용 모델의 목록 인스턴스)을 원하고 언어에 내장되지 않은 경우 운이 좋지 않습니다.
Functor파라 메트릭이 아닌 부품 의 구조 정보를 의미합니다 . 의 상태 값 State, Maybe및의 실패 Either,의 요소 수 []및의 임의의 외부 부작용입니다 IO. 나는 그것을 일반적인 용어 ( Monoid"empty"와 "append"를 사용 하는 함수 처럼, 개념이 처음에 제안하는 것보다 더 일반적 임) 로 신경 쓰지 않지만 꽤 일반적이고 충분히 목적을 수행한다.
ap이전 결과 에 의존 하게 만드는 것만큼이나 불쾌 할 것 입니다. 나는 그에 따라 그 발언을 다시 말 하였다.
traverses를 사물로 만드는 함수가 주어지면 내부의 사물을 "내부"사물 Traversable로 바꿉니다 .TraversableApplicativeApplicative
하자의 사용 Maybe등 Applicative과 같은 및 목록 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]]
fac n = length $ traverse rep [1..n]
liftA2 (,)하는보다 일반적인 형식보다 수행하는 것과 비슷 traverse합니다.
나는 다음과 같이 정의 할 수 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다른 유형의 컨테이너에 사용하거나 다른 응용 프로그램을 사용 하면 상황이 더 흥미로워 집니다.
sequenceA . fmap목록은 다음과 동일 sequence . map하지 않습니까?
IO 유형 은 부작용을 표현하는 데 사용될 수 있습니다. (2) IO우연히 모나드는 매우 편리합니다. 모나드는 본질적으로 부작용과 관련이 없습니다. 또한 일반적인 의미에서 "부작용"보다 더 넓은 "효과"의 의미가 있다는 점에 유의해야합니다. 순수한 계산을 포함하는 것입니다. 이 마지막 요점에 대해서는 "효과적"이란 정확히 무엇을 의미하는지 참조하십시오 .
그것은 종류 등의 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_버전이 있으며 둘 다 함수의 반환 값을 무시하고 약간 더 빠릅니다.
traverse 인 루프. 그 구현은 순회 할 데이터 구조에 따라 다릅니다. 즉 목록, 나무, 수도 Maybe, SeqA에 대한 루프 또는 재귀 함수 같은 것을 통해 통과되는 일반적인 방법이있다 (영향력), 또는 아무것도. 배열은 for-loop, list는 while-loop, 트리는 재귀 적이거나 while-loop와 스택의 조합을 가질 것입니다. 그러나 기능적 언어에서는 이러한 성가신 루프 명령이 필요하지 않습니다. 루프의 내부 부분 (함수 형태)을 데이터 구조와보다 직접적이고 덜 장황하게 결합합니다.
Traversabletypeclass를 사용하면 알고리즘을보다 독립적이고 다용도로 작성할 수 있습니다. 그러나 내 경험에 따르면 Traversable일반적으로 기존 데이터 구조에 알고리즘을 붙이는 데만 사용됩니다. 자격을 갖춘 다른 데이터 유형에 대해 유사한 함수를 작성할 필요가없는 것도 매우 좋습니다.