development

실용적인 스타일의 실용적인 용도는 무엇입니까?

big-blog 2020. 11. 6. 21:03
반응형

실용적인 스타일의 실용적인 용도는 무엇입니까?


저는 스칼라 프로그래머로 현재 Haskell을 배우고 있습니다. 데코레이터, 전략 패턴 등과 같은 OO 개념에 대한 실제 사용 사례와 실제 사례를 쉽게 찾을 수 있습니다. 책과 인터넷 웹이 가득합니다.

나는 이것이 어떻게 든 기능적 개념의 경우가 아니라는 것을 깨달았습니다. 적절한 사례 : 응용 프로그램 .

응용 프로그램에 대한 실용적인 사용 사례를 찾기 위해 고군분투하고 있습니다. 지금까지 접한 거의 모든 자습서와 책에서 []의 예를 제공합니다 Maybe. 나는 응용 프로그램이 FP 커뮤니티에서 얻는 모든 관심을 보면서 그보다 더 적용 가능할 것이라고 기대했습니다.

나는 응용 프로그램 의 개념적 기초를 이해하고 있다고 생각하며 (내가 틀렸을 수도 있음) 깨달음의 순간을 오래 기다렸습니다. 그러나 그것은 일어나지 않는 것 같습니다. 프로그래밍을하는 동안, "유레카! 여기에 applicative를 사용할 수 있습니다!"라고 기쁨으로 외칠 시간이 없었습니다. (다시 제외하고, []Maybe).

누군가가 일상적인 프로그래밍에서 응용 프로그램을 어떻게 사용할 수 있는지 안내해 주시겠습니까? 패턴을 발견하려면 어떻게해야합니까? 감사!


경고 : 내 대답은 설교 / 사과 적입니다. 그러니 저를 고소하세요.

글쎄요, 일상적인 Haskell 프로그래밍에서 얼마나 자주 새로운 데이터 유형을 생성합니까? 자신의 Applicative 인스턴스를 언제 만들지 알고 싶은 것처럼 들리며, 자신의 파서를 롤링하지 않는 한 솔직히 말해서 그렇게 많이 할 필요가 없을 것입니다. 반면에 적용 가능한 인스턴스를 사용하면 자주 수행하는 방법을 배워야합니다.

Applicative는 데코레이터 나 전략과 같은 "디자인 패턴"이 아닙니다. 그것은 추상화로, 훨씬 더 널리 퍼지고 일반적으로 유용하지만 훨씬 덜 가시적입니다. "실용적인 용도"를 찾는 데 어려움을 겪는 이유는 예제 용도가 너무 간단하기 때문입니다. 데코레이터를 사용하여 창에 스크롤바를 배치합니다. 전략을 사용하여 체스 봇의 공격적 및 수비 적 동작을위한 인터페이스를 통합합니다. 그러나 응용 프로그램은 무엇입니까? 글쎄, 그들은 훨씬 더 일반화되어 있기 때문에 그들이 무엇을위한 것인지 말하기가 어렵습니다. 괜찮습니다. 응용 프로그램은 구문 분석 결합 자로 편리합니다. Yesod 웹 프레임 워크는 Applicative를 사용하여 양식에서 정보를 설정하고 추출하는 데 도움을줍니다. 보시면 Applicative에 대한 백만 가지 용도를 찾을 수 있습니다. 여기 저기 있습니다. 하지만 이후


응용 프로그램은 여러 변수의 평범한 오래된 함수가 있고 인수가 있지만 어떤 종류의 컨텍스트로 묶여있을 때 훌륭합니다. 예를 들어, 평범한 이전 연결 함수가 (++)있지만 I / O를 통해 얻은 2 개의 문자열에 적용하려고합니다. 그런 다음 IO실용적인 펑 터라는 사실이 구출됩니다.

Prelude Control.Applicative> (++) <$> getLine <*> getLine
hi
there
"hithere"

Maybe예제 를 명시 적으로 요청했지만 저에게는 훌륭한 사용 사례처럼 보이므로 예제를 제공하겠습니다. 여러 변수의 정규 함수가 있지만 필요한 모든 값이 있는지 알 수 없습니다 (일부는 계산에 실패하여를 산출 할 수 있음 Nothing). 따라서 본질적으로 "부분 값"이 있기 때문에 함수를 부분 함수로 바꾸고 싶습니다. 이는 입력이 정의되지 않은 경우 정의되지 않습니다. 그때

Prelude Control.Applicative> (+) <$> Just 3 <*> Just 5
Just 8

그러나

Prelude Control.Applicative> (+) <$> Just 3 <*> Nothing
Nothing

정확히 당신이 원하는 것입니다.

기본 아이디어는 원하는만큼 많은 인수에 적용 할 수있는 컨텍스트로 일반 함수를 "리프팅"하는 것입니다. Applicative기본 이상의 추가 힘은 Functor임의의 함수를 fmap들어 올릴 수있는 반면 단항 함수 만 들어 올릴 수 있다는 것입니다.


많은 응용 프로그램도 모나드이기 때문에이 질문에는 정말 양면이 있다고 생각합니다.

둘 다 사용할 수 있는데 모나 딕 대신 응용 인터페이스를 사용하고 싶은 이유는 무엇입니까?

이것은 대부분 스타일의 문제입니다. 모나드는- do표기법 의 통사론 적 설탕을 가지고 있지만 , 적용 스타일을 사용하면 종종 더 간결한 코드로 이어집니다.

이 예에서는 유형 Foo이 있으며이 유형의 임의 값을 구성하려고합니다. 에 대한 모나드 인스턴스를 사용하여 다음 IO과 같이 작성할 수 있습니다.

data Foo = Foo Int Double

randomFoo = do
    x <- randomIO
    y <- randomIO
    return $ Foo x y

적용 가능한 변형은 상당히 짧습니다.

randomFoo = Foo <$> randomIO <*> randomIO

물론 liftM2비슷한 간결함을 얻기 위해 사용할 수 있지만 적용 스타일은 arity 특정 리프팅 기능에 의존하는 것보다 깔끔합니다.

실제로 저는 점없는 스타일을 사용하는 것과 같은 방식으로 응용 프로그램을 사용하는 경우가 많습니다. 작업이 다른 작업의 구성으로 더 명확하게 표현 될 때 중간 값의 이름을 지정하지 않으려면.

모나드가 아닌 응용 프로그램을 사용하려는 이유는 무엇입니까?

애플리케이션은 모나드보다 더 제한적이므로 더 유용한 정적 정보를 추출 할 수 있습니다.

이에 대한 예는 응용 파서입니다. 모나 딕 파서는를 사용하는 순차 구성을 지원하는 반면 (>>=) :: Monad m => m a -> (a -> m b) -> m b, 응용 파서는 (<*>) :: Applicative f => f (a -> b) -> f a -> f b. 유형은 차이를 분명하게 만듭니다. 모나 딕 파서에서는 입력에 따라 문법이 변경 될 수 있지만 응용 파서에서는 문법이 고정됩니다.

이러한 방식으로 인터페이스를 제한함으로써 예를 들어 파서가 빈 문자열 을 실행하지 않고 허용할지 여부를 결정할 수 있습니다 . 또한 최적화에 사용할 수있는 첫 번째 및 후속 집합을 결정할 수도 있고, 최근에 작업 한 것처럼 더 나은 오류 복구를 지원하는 파서를 구성 할 수도 있습니다.


나는 Functor, Applicative, Monad를 디자인 패턴으로 생각합니다.

Future [T] 클래스를 작성한다고 상상해보십시오. 즉, 계산할 값을 보유하는 클래스입니다.

Java 사고 방식에서 다음과 같이 만들 수 있습니다.

trait Future[T] {
  def get: T
}

'get'은 값을 사용할 수있을 때까지 차단됩니다.

이것을 깨닫고 콜백을 받도록 다시 작성할 수 있습니다.

trait Future[T] {
  def foreach(f: T => Unit): Unit
}

그러나 미래에 두 가지 용도가 있다면 어떻게 될까요? 이는 콜백 목록을 유지해야 함을 의미합니다. 또한 메서드가 Future [Int]를 수신하고 내부 Int를 기반으로 계산을 반환해야하는 경우 어떻게됩니까? 또는 두 개의 미래가 있고 그들이 제공 할 가치를 기반으로 무언가를 계산해야하는 경우 어떻게합니까?

그러나 FP 개념을 알고 있다면 T에서 직접 작업하는 대신 Future 인스턴스를 조작 할 수 있다는 것을 알고 있습니다.

trait Future[T] {
  def map[U](f: T => U): Future[U]
}

이제 응용 프로그램이 변경되어 포함 된 값에 대해 작업해야 할 때마다 새 Future를 반환합니다.

이 길에서 시작하면 거기서 멈출 수 없습니다. 두 개의 퓨처를 조작하려면 응용 프로그램으로 모델링하고 퓨처를 생성하려면 미래에 대한 모나드 정의가 필요하다는 것을 알고 있습니다.

UPDATE: As suggested by @Eric, I've written a blog post: http://www.tikalk.com/incubator/blog/functional-programming-scala-rest-us


I finally understood how applicatives can help in day-to-day programming with that presentation:

https://web.archive.org/web/20100818221025/http://applicative-errors-scala.googlecode.com/svn/artifacts/0.6/chunk-html/index.html

The autor shows how applicatives can help for combining validations and handling failures.

The presentation is in Scala, but the author also provides the full code example for Haskell, Java and C#.


I think Applicatives ease the general usage of monadic code. How many times have you had the situation that you wanted to apply a function but the function was not monadic and the value you want to apply it to is monadic? For me: quite a lot of times!
Here is an example that I just wrote yesterday:

ghci> import Data.Time.Clock
ghci> import Data.Time.Calendar
ghci> getCurrentTime >>= return . toGregorian . utctDay

in comparison to this using Applicative:

ghci> import Control.Applicative
ghci> toGregorian . utctDay <$> getCurrentTime

This form looks "more natural" (at least to my eyes :)


Coming at Applicative from "Functor" it generalizes "fmap" to easily express acting on several arguments (liftA2) or a sequence of arguments (using <*>).

Coming at Applicative from "Monad" it does not let the computation depend on the value that is computed. Specifically you cannot pattern match and branch on a returned value, typically all you can do is pass it to another constructor or function.

Thus I see Applicative as sandwiched in between Functor and Monad. Recognizing when you are not branching on the values from a monadic computation is one way to see when to switch to Applicative.


There are some ADTs like ZipList that can have applicative instances, but not monadic instances. This was a very helpful example for me when understanding the difference between applicatives and monads. Since so many applicatives are also monads, it's easy to not see the difference between the two without a concrete example like ZipList.


Here is an example taken from the aeson package:

data Coord = Coord { x :: Double, y :: Double }

instance FromJSON Coord where
   parseJSON (Object v) = 
      Coord <$>
        v .: "x" <*>
        v .: "y"

I think it might be worthwhile to browse the sources of packages on Hackage, and see first-handedly how applicative functors and the like are used in existing Haskell code.


I described an example of practical use of the applicative functor in a discussion, which I quote below.

Note the code examples are pseudo-code for my hypothetical language which would hide the type classes in a conceptual form of subtyping, so if you see a method call for apply just translate into your type class model, e.g. <*> in Scalaz or Haskell.

If we mark elements of an array or hashmap with null or none to indicate their index or key is valid yet valueless, the Applicative enables without any boilerplate skipping the valueless elements while applying operations to the elements that have a value. And more importantly it can automatically handle any Wrapped semantics that are unknown a priori, i.e. operations on T over Hashmap[Wrapped[T]] (any over any level of composition, e.g. Hashmap[Wrapped[Wrapped2[T]]] because applicative is composable but monad is not).

I can already picture how it will make my code easier to understand. I can focus on the semantics, not on all the cruft to get me there and my semantics will be open under extension of Wrapped whereas all your example code isn’t.

Significantly, I forgot to point out before that your prior examples do not emulate the return value of the Applicative, which will be a List, not a Nullable, Option, or Maybe. So even my attempts to repair your examples were not emulating Applicative.apply.

Remember the functionToApply is the input to the Applicative.apply, so the container maintains control.

list1.apply( list2.apply( ... listN.apply( List.lift(functionToApply) ) ... ) )

Equivalently.

list1.apply( list2.apply( ... listN.map(functionToApply) ... ) )

And my proposed syntactical sugar which the compiler would translate to the above.

funcToApply(list1, list2, ... list N)

It is useful to read that interactive discussion, because I can't copy it all here. I expect that url to not break, given who the owner of that blog is. For example, I quote from further down the discussion.

the conflation of out-of-statement control flow with assignment is probably not desired by most programmers

Applicative.apply is for generalizing the partial application of functions to parameterized types (a.k.a. generics) at any level of nesting (composition) of the type parameter. This is all about making more generalized composition possible. The generality can’t be accomplished by pulling it outside the completed evaluation (i.e. return value) of the function, analogous to the onion can’t be peeled from the inside-out.

Thus it isn’t conflation, it is a new degree-of-freedom that is not currently available to you. Per our discussion up thread, this is why you must throw exceptions or stored them in a global variable, because your language doesn’t have this degree-of-freedom. And that is not the only application of these category theory functors (expounded in my comment in moderator queue).

I provided a link to an example abstracting validation in Scala, F#, and C#, which is currently stuck in moderator queue. Compare the obnoxious C# version of the code. And the reason is because the C# is not generalized. I intuitively expect that C# case-specific boilerplate will explode geometrically as the program grows.

참고URL : https://stackoverflow.com/questions/7103864/what-are-practical-uses-of-applicative-style

반응형