에서 traverse
기능을 시도하고 실패했습니다 Data.Traversable
. 나는 그 요점을 볼 수 없다. 내가 명령형 배경에서 왔기 때문에 누군가 명령형 루프 측면에서 나에게 설명해 주시겠습니까? 의사 코드를 많이 주시면 감사하겠습니다. 감사.
답변:
traverse
fmap
데이터 구조를 재 구축하는 동안 효과를 실행할 수 있다는 점을 제외 하면과 동일 합니다.
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
되었기 때문에 별도의 메서드로 존재합니다 .Applicative
Monad
이를 불순한 언어로 구현하면 부작용을 방지 할 수있는 방법이 없기 때문에과 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
이전 결과 에 의존 하게 만드는 것만큼이나 불쾌 할 것 입니다. 나는 그에 따라 그 발언을 다시 말 하였다.
traverse
s를 사물로 만드는 함수가 주어지면 내부의 사물을 "내부"사물 Traversable
로 바꿉니다 .Traversable
Applicative
Applicative
하자의 사용 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
, Seq
A에 대한 루프 또는 재귀 함수 같은 것을 통해 통과되는 일반적인 방법이있다 (영향력), 또는 아무것도. 배열은 for-loop, list는 while-loop, 트리는 재귀 적이거나 while-loop와 스택의 조합을 가질 것입니다. 그러나 기능적 언어에서는 이러한 성가신 루프 명령이 필요하지 않습니다. 루프의 내부 부분 (함수 형태)을 데이터 구조와보다 직접적이고 덜 장황하게 결합합니다.
Traversable
typeclass를 사용하면 알고리즘을보다 독립적이고 다용도로 작성할 수 있습니다. 그러나 내 경험에 따르면 Traversable
일반적으로 기존 데이터 구조에 알고리즘을 붙이는 데만 사용됩니다. 자격을 갖춘 다른 데이터 유형에 대해 유사한 함수를 작성할 필요가없는 것도 매우 좋습니다.