development

화살은 무엇이며 어떻게 사용할 수 있습니까?

big-blog 2020. 11. 20. 09:11
반응형

화살은 무엇이며 어떻게 사용할 수 있습니까?


화살 의 의미를 배우려고했지만 이해하지 못했습니다.

Wikibooks 튜토리얼을 사용했습니다. 위키 북의 문제는 주로 이미 그 주제를 이해하고있는 사람을 위해 쓰여진 것 같다는 점이라고 생각합니다.

누군가 화살이 무엇이며 어떻게 사용할 수 있는지 설명 할 수 있습니까?


튜토리얼은 모르겠지만 구체적인 예제를 보면 화살표를 이해하는 것이 가장 쉽다고 생각합니다. 나는 화살을 사용하는 방법을 학습 한 가장 큰 문제는 실제로하는 방법을 보여 튜토리얼이나 예제의 것도 아니었다 사용 을 작성하는 데 얼마나, 화살표. 이를 염두에두고 여기에 내 미니 자습서가 있습니다. 두 가지 화살표 인 함수와 사용자 정의 화살표 유형을 살펴 보겠습니다 MyArr.

-- type representing a computation
data MyArr b c = MyArr (b -> (c,MyArr b c))

1) 화살표는 지정된 유형의 입력에서 지정된 유형의 출력까지 계산합니다. 화살표 유형 클래스는 화살표 유형, 입력 유형 및 출력 유형의 세 가지 유형 인수를 사용합니다. 화살표 인스턴스에 대한 인스턴스 헤드를 살펴보면 다음과 같습니다.

instance Arrow (->) b c where
instance Arrow MyArr b c where

화살표 ( (->)또는 MyArr)는 계산의 추상화입니다.

함수의 b -> c경우 b는 입력이고 c출력입니다.
A에 대한 MyArr b c, b상기 입력하고 c출력한다.

2) 실제로 화살표 계산을 실행하려면 화살표 유형에 맞는 함수를 사용합니다. 함수의 경우 단순히 함수를 인수에 적용하면됩니다. 다른 화살표의 경우 별도의 함수가 필요합니다 ( 모나드의 runIdentity경우 runState, 등).

-- run a function arrow
runF :: (b -> c) -> b -> c
runF = id

-- run a MyArr arrow, discarding the remaining computation
runMyArr :: MyArr b c -> b -> c
runMyArr (MyArr step) = fst . step

3) 화살표는 입력 목록을 처리하는 데 자주 사용됩니다. 기능의 경우 병렬로 수행 할 수 있지만 특정 단계에서 출력되는 일부 화살표의 경우 이전 입력에 따라 달라집니다 (예 : 입력의 누계 유지).

-- run a function arrow over multiple inputs
runFList :: (b -> c) -> [b] -> [c]
runFList f = map f

-- run a MyArr over multiple inputs.
-- Each step of the computation gives the next step to use
runMyArrList :: MyArr b c -> [b] -> [c]
runMyArrList _ [] = []
runMyArrList (MyArr step) (b:bs) = let (this, step') = step b
                                   in this : runMyArrList step' bs

이것이 Arrows가 유용한 이유 중 하나입니다. 프로그래머에게 상태를 노출하지 않고 암시 적으로 상태를 사용할 수있는 계산 모델을 제공합니다. 프로그래머는 화살표 계산을 사용하고이를 결합하여 정교한 시스템을 만들 수 있습니다.

다음은 수신 한 입력 수를 유지하는 MyArr입니다.

-- count the number of inputs received:
count :: MyArr b Int
count = count' 0
  where
    count' n = MyArr (\_ -> (n+1, count' (n+1)))

이제 함수 runMyArrList count는 목록 길이 n을 입력으로 취하고 1에서 n까지의 Int 목록을 반환합니다.

우리는 여전히 어떤 "화살표"함수, 즉 Arrow 클래스 메서드 나 그것들과 관련하여 작성된 함수를 사용하지 않았습니다.

4) 위 코드의 대부분은 각 Arrow 인스턴스 [1]에 고유합니다. Control.Arrow(및 Control.Category)의 모든 것은 새 화살표를 만들기 위해 화살표를 구성하는 것입니다. Category가 별도의 클래스가 아닌 Arrow의 일부라고 가정하면 :

-- combine two arrows in sequence
>>> :: Arrow a => a b c -> a c d -> a b d

-- the function arrow instance
-- >>> :: (b -> c) -> (c -> d) -> (b -> d)
-- this is just flip (.)

-- MyArr instance
-- >>> :: MyArr b c -> MyArr c d -> MyArr b d

>>>함수는 두 개의 화살표를 사용하고 첫 번째의 출력을 두 번째에 대한 입력으로 사용합니다.

일반적으로 "팬 아웃"이라고하는 또 다른 연산자가 있습니다.

-- &&& applies two arrows to a single input in parallel
&&& :: Arrow a => a b c -> a b c' -> a b (c,c')

-- function instance type
-- &&& :: (b -> c) -> (b -> c') -> (b -> (c,c'))

-- MyArr instance type
-- &&& :: MyArr b c -> MyArr b c' -> MyArr b (c,c')

-- first and second omitted for brevity, see the accepted answer from KennyTM's link
-- for further details.

Control.Arrow계산을 결합하는 수단을 제공 하므로 다음 은 한 가지 예입니다.

-- function that, given an input n, returns "n+1" and "n*2"
calc1 :: Int -> (Int,Int)
calc1 = (+1) &&& (*2)

calc1복잡한 폴드에서 유용한 기능 이나 예를 들어 포인터에서 작동하는 기능을 자주 발견했습니다 .

Monad형 클래스는 사용하는 단일 새로운 모나드 모나드 연산으로 계산을 조합하는 수단을 우리에게 제공하는 >>=기능. 유사하게, Arrow클래스 몇 원시 함수를 사용하여 단일 연산으로 새로운 arrowized arrowized 계산을 조합하는 수단을 우리에게 제공 ( first, arr,와 ***,와 >>>idControl.Category부터). 모나드와 유사하게 "화살은 무엇을하나요?"라는 질문입니다. 일반적으로 대답 할 수 없습니다. 화살표에 따라 다릅니다.

불행히도 나는 야생에서 화살 인스턴스의 많은 예를 알지 못합니다. 기능과 FRP가 가장 일반적인 응용 프로그램 인 것 같습니다. HXT는 떠오르는 유일한 다른 중요한 용도입니다.

[1] count. 의 모든 인스턴스에 대해 동일한 작업을 수행하는 count 함수를 작성할 수 있습니다 ArrowLoop.


Stack Overflow에 대한 귀하의 기록을 한눈에 살펴보면 다른 표준 유형 클래스, 특히 Functor및에 익숙하다고 가정 Monoid하고 간단한 비유로 시작합니다.

의 단일 작업은 Functor입니다 fmap. 이는 map목록에 대한 일반화 된 버전 역할 을합니다. 이것은 타입 클래스의 거의 모든 목적입니다. "매핑 할 수있는 것"을 정의합니다. 따라서 어떤 의미 Functor에서 목록의 특정 측면의 일반화를 나타냅니다.

에 대한 작업 Monoid은 빈 목록 및의 일반화 된 버전이며 (++)"아이덴티티 값인 특정 사물과 연관 적으로 결합 할 수있는 사물"을 정의합니다. 목록은 그 설명에 맞는 가장 단순한 것이며 Monoid목록의 해당 측면의 일반화를 나타냅니다.

위의 두 가지와 같은 방식으로, Category유형 클래스 에 대한 연산 id및의 일반화 된 버전이며 (.)"두 유형을 특정 방향으로 연결하는 것, 즉 머리에서 꼬리까지 연결할 수있는 것"을 정의합니다. 따라서 이것은 함수 의 해당 측면의 일반화를 나타냅니다 . 특히 일반화에 포함되지 않는 것은 카레 또는 기능 응용입니다.

Arrow형 클래스는 오프의 빌드 Category,하지만 기본 개념은 동일합니다 Arrow의 함수처럼 작성하고는 모든 유형의 정의 된 "정체성 화살표를"가지고 것들입니다. Arrow클래스 자체에 정의 된 추가 연산 은 임의의 함수를에 리프트하는 Arrow방법과 튜플 사이의 단일 화살표로 "병렬로"두 개의 화살표를 결합하는 방법을 정의합니다 .

따라서 여기서 염두에 두어야 할 첫 번째 것은 s를 구축Arrow 하는 표현식 이 본질적으로 정교한 함수 구성이라는 것 입니다. 콤비 같이 (***)하고 (>>>)있는 동안, "pointfree"스타일을 쓰기위한 proc표기법이 일을 배선하면서 일시적 입력에 이름과 출력을 할당하는 방법을 제공합니다.

여기서 주목해야 할 유용한 점은 Arrows가 s의 "다음 단계"로 설명되기도 하지만 Monad실제로는별로 의미있는 관계가 없다는 것입니다. 어떤 경우 든 MonadKleisli 화살표로 작업 할 수 있습니다.이 화살표는 a -> m b. (<=<)연산자는 Control.Monad이들에 대한 화살표 구성입니다. 반면에 Arrows는 클래스를 Monad포함하지 않는 한 a를 얻지 못합니다 ArrowApply. 따라서 직접적인 연결이 없습니다.

여기서 중요한 차이점은 Monads는 계산을 시퀀스하고 단계별로 수행하는 데 사용할 수 있지만 s는 Arrow일반 함수와 마찬가지로 어떤 의미에서 "영원한"이라는 것입니다. 에 의해 연결되는 추가 기계 및 기능을 포함 할 수 (.)있지만 작업을 축적하는 것이 아니라 파이프 라인을 구축하는 것과 비슷합니다.

다른 관련 타입 클래스는 화살표와 결합 할 수있는 바와 같이, 화살표에 부가 기능을 추가 할 Either뿐만 아니라 (,).


의 내가 가장 좋아하는 예 Arrow입니다 상태 스트림 트랜스 듀서 이 같은 것을 보면 :

data StreamTrans a b = StreamTrans (a -> (b, StreamTrans a b))

StreamTrans화살표 출력과 자신의 "업데이트"버전에 대한 입력 값을 변환한다; 이것이 stateful과 다른 점을 고려하십시오 Monad.

Arrow위의 유형에 대한 인스턴스 및 관련 유형 클래스를 작성 하는 것은 작동 방식을 이해하는 좋은 연습 일 수 있습니다!

나는 또한 이전 에 도움이 될만한 비슷한 답변을 썼습니다 .


Haskell의 화살표는 문헌을 기반으로 표시되는 것보다 훨씬 간단하다는 점을 추가하고 싶습니다. 그것들은 단순히 기능의 추상화입니다.

이것이 실제로 얼마나 유용한 지 알아 보려면, 구성하고 싶은 함수가 많고 그중 일부는 순수하고 일부는 모나 딕이라는 것을 고려하십시오. 예를 들어, f :: a -> b, g :: b -> m1 c,와 h :: c -> m2 d.

Knowing each of the types involved, I could build a composition by hand, but the output type of the composition would have to reflect the intermediate monad types (in the above case, m1 (m2 d)). What if I just wanted to treat the functions as if they were just a -> b, b -> c, and c -> d? That is, I want to abstract away the presence of monads and reason only about the underlying types. I can use arrows to do exactly this.

Here is an arrow which abstracts away the presence of IO for functions in the IO monad, such that I can compose them with pure functions without the composing code needing to know that IO is involved. We start by defining an IOArrow to wrap IO functions:

data IOArrow a b = IOArrow { runIOArrow :: a -> IO b }

instance Category IOArrow where
  id = IOArrow return
  IOArrow f . IOArrow g = IOArrow $ f <=< g

instance Arrow IOArrow where
  arr f = IOArrow $ return . f
  first (IOArrow f) = IOArrow $ \(a, c) -> do
    x <- f a
    return (x, c)

Then I make some simple functions that I want to compose:

foo :: Int -> String
foo = show

bar :: String -> IO Int
bar = return . read

And use them:

main :: IO ()
main = do
  let f = arr (++ "!") . arr foo . IOArrow bar . arr id
  result <- runIOArrow f "123"
  putStrLn result

Here I am calling IOArrow and runIOArrow, but if I were passing these arrows around in a library of polymorphic functions, they would only need to accept arguments of type "Arrow a => a b c". None of the library code would need to be made aware that a monad was involved. Only the creator and end user of the arrow needs to know.

Generalizing IOArrow to work for functions in any Monad is called the "Kleisli arrow", and there is already a builtin arrow for doing just that:

main :: IO ()
main = do
  let g = arr (++ "!") . arr foo . Kleisli bar . arr id
  result <- runKleisli g "123"
  putStrLn result

You could of course also use arrow composition operators, and proc syntax, to make it a little clearer that arrows are involved:

arrowUser :: Arrow a => a String String -> a String String
arrowUser f = proc x -> do
  y <- f -< x
  returnA -< y

main :: IO ()
main = do
  let h =     arr (++ "!")
          <<< arr foo
          <<< Kleisli bar
          <<< arr id
  result <- runKleisli (arrowUser h) "123"
  putStrLn result

Here it should be clear that although main knows the IO monad is involved, arrowUser does not. There would be no way of "hiding" IO from arrowUser without arrows -- not without resorting to unsafePerformIO to turn the intermediate monadic value back into a pure one (and thus losing that context forever). For example:

arrowUser' :: (String -> String) -> String -> String
arrowUser' f x = f x

main' :: IO ()
main' = do
  let h      = (++ "!") . foo . unsafePerformIO . bar . id
      result = arrowUser' h "123"
  putStrLn result

Try writing that without unsafePerformIO, and without arrowUser' having to deal with any Monad type arguments.


There are John Hughes's Lecture Notes from an AFP (Advanced Functional Programming) workshop. Note they were written before the Arrow classes were changed in the Base libraries:

http://www.cse.chalmers.se/~rjmh/afp-arrows.pdf


When I began exploring Arrow compositions (essentially Monads), my approach was to break out of the functional syntax and composition it is most commonly associated with and begin by understanding its principles using a more declarative approach. With this in mind, I find the following breakdown more intuitive:

function(x) {
  func1result = func1(x)
  if(func1result == null) {
    return null
  } else {
    func2result = func2(func1result)
    if(func2result == null) {
      return null
    } else {
      func3(func2result)
    } 

So, essentially, for some value x, call one function first that we assume may return null (func1), another that may retun null or be assigned to null interchangably, finally, a third function that may also return null. Now given the value x, pass x to func3, only then, if it does not return null, pass this value into func2, and only if this value is not null, pass this value into func1. It's more deterministic and the control flow allows you to construct more sophisticated exception handling.

Here we can utilise the arrow composition: (func3 <=< func2 <=< func1) x.

참고URL : https://stackoverflow.com/questions/4191424/what-are-arrows-and-how-can-i-use-them

반응형