더 높은 종류의 유형은 언제 유용합니까?


88

나는 한동안 F #에서 개발을 해왔고 그것을 좋아합니다. 그러나 내가 아는 한 가지 유행어는 F #에 존재하지 않는 고급 유형입니다. 나는 고급 유형에 대한 자료를 읽었으며 그 정의를 이해한다고 생각합니다. 나는 그들이 왜 유용한 지 잘 모르겠습니다. 누군가 Scala 또는 Haskell에서 F #의 해결 방법이 필요한 고급 유형의 예를 제공 할 수 있습니까? 또한 이러한 예제의 경우 더 높은 종류의 유형이 없으면 해결 방법이 무엇입니까 (또는 F #의 경우 그 반대)? 어쩌면 나는 그 문제를 해결하는 데 너무 익숙해서 그 기능이 없다는 것을 알지 못합니다.

(나는 생각한다) 대신 myList |> List.map f또는 myList |> Seq.map f |> Seq.toList더 높은 종류의 유형을 사용하면 간단하게 작성할 myList |> map f수 있으며 List. 그것은 훌륭하지만 (정확하다고 가정) 약간 사소한 것 같습니까? (그리고 단순히 함수 오버로딩을 허용하는 것만으로 할 수는 없나요?) 저는 보통 Seq어쨌든 로 변환 한 다음 나중에 원하는대로 변환 할 수 있습니다. 다시 말하지만, 나는 그것을 해결하는 데 너무 익숙 할 것입니다. 그러나 더 높은 종류의 유형 이 키 입력이나 유형 안전에서 실제로 당신을 구하는 예가 있습니까?


2
Control.Monad의 많은 함수 는 더 높은 종류를 사용하므로 몇 가지 예를 찾아 볼 수 있습니다. F #에서는 각 구체적인 모나드 유형에 대해 구현을 반복해야합니다.
— Lee

1
@Lee하지만 인터페이스 IMonad<T>를 만든 다음 예를 들어 IEnumerable<int>또는 IObservable<int>완료되면 다시 캐스팅 할 수 없습니까? 이 모든 것이 캐스팅을 피하기위한 것입니까?
— lobsterism

4
Well Casting은 안전하지 않으므로 형식 안전성에 대한 질문에 답합니다. 또 다른 문제는 return그것이 실제로는 특정 인스턴스가 아닌 모나드 유형에 속하므로 IMonad인터페이스에 전혀 넣지 않기 때문에 어떻게 작동 하는지 입니다 .
— Lee

4
@Lee yeah 나는 표현 후에 최종 결과를 캐스팅해야 할 것이라고 생각하고 있었는데, 그냥 표현을해서 타입을 알기 때문에 별거 아니야. 그러나 bind일명 SelectMany등의 각 impl 내부에도 캐스팅해야 할 것 같습니다 . 누군가에게 API를 사용할 수 있습니다 의미합니다 로 하고, 그것을 작동합니다 생각하는 그런 경우와 방법은 그 주위에 존재하지 않는 경우는 우웩 그래. 주변에 방법이 없다고 100 % 확신하지는 않습니다. bindIObservableIEnumerable
— lobsterism

5
좋은 질문입니다. 이 언어 기능이 유용한 IRL이라는 강력한 실용적인 예는 아직 보지 못했습니다.
— JD

답변:


78

따라서 유형의 종류는 단순한 유형입니다. 예를 들어 기본 유형이며 값으로 인스턴스화 할 수 있음을 의미하는 Int종류 *가 있습니다. 더 높은 종류의 유형에 대한 느슨한 정의 (그리고 F #이 선을 그리는 위치가 확실하지 않으므로 포함하겠습니다) 다형성 컨테이너 는 더 높은 종류의 유형의 좋은 예입니다.

data List a = Cons a (List a) | Nil

타입 생성자는 List종류가 * -> *: 그것은 구체적인 유형을 초래할하기 위해 구체적인 유형을 전달해야하는 수단 List Int등의 주민을 가질 수 [1,2,3]있지만 List그 자체가 없습니다.

다형성 컨테이너의 이점은 분명하지만 컨테이너보다 더 유용한 종류의 * -> *유형이 존재 한다고 가정하겠습니다 . 예를 들어, 관계

data Rel a = Rel (a -> a -> Bool)

또는 파서

data Parser a = Parser (String -> [(a, String)])

둘 다 종류도 있습니다 * -> *.


그러나 우리는 더 높은 순서의 종류를 가진 타입을 가짐으로써 Haskell에서 이것을 더 나아갈 수 있습니다. 예를 들어 우리는 kind를 가진 유형을 찾을 수 (* -> *) -> *있습니다. 이것의 간단한 예 Shape는 종류의 컨테이너를 채우려 고 시도하는 것일 수 있습니다 * -> *.

data Shape f = Shape (f ())

[(), (), ()] :: Shape List

Traversable예를 들어 Haskell에서 s 를 특성화하는 데 유용합니다. 예를 들어 항상 모양과 내용으로 나눌 수 있기 때문입니다.

split :: Traversable t => t a -> (Shape t, [a])

또 다른 예로, 가지고있는 가지의 종류에 따라 매개 변수화 된 트리를 생각해 봅시다. 예를 들어, 일반 나무는

data Tree a = Branch (Tree a) a (Tree a) | Leaf

하지만 분기 유형에 a Pairof Tree as 가 포함되어 있으므로 해당 유형에서 매개 변수로 해당 부분을 추출 할 수 있습니다.

data TreeG f a = Branch a (f (TreeG f a)) | Leaf

data Pair a = Pair a a
type Tree a = TreeG Pair a

이 TreeG유형 생성자는 kind가 (* -> *) -> * -> *있습니다. 우리는 그것을 사용하여 다음과 같은 흥미로운 다른 변형을 만들 수 있습니다.RoseTree

type RoseTree a = TreeG [] a

rose :: RoseTree Int
rose = Branch 3 [Branch 2 [Leaf, Leaf], Leaf, Branch 4 [Branch 4 []]]

또는 병리학적인 것 MaybeTree

data Empty a = Empty
type MaybeTree a = TreeG Empty a

nothing :: MaybeTree a
nothing = Leaf

just :: a -> MaybeTree a
just a = Branch a Empty

또는 TreeTree

type TreeTree a = TreeG Tree a

treetree :: TreeTree Int
treetree = Branch 3 (Branch Leaf (Pair Leaf Leaf))

이것이 나타나는 또 다른 장소는 "펑터의 대수"입니다. 추상화 레이어를 몇 개 삭제하면 sum :: [Int] -> Int. 대수는 펑터 와 캐리어에 대해 매개 변수화 됩니다. 펑은 종류가 * -> *캐리어 종류 *그래서 모두

data Alg f a = Alg (f a -> a)

종류가 (* -> *) -> * -> *있습니다. Alg데이터 유형과 그 위에 구축 된 재귀 체계와의 관계 때문에 유용합니다.

-- | The "single-layer of an expression" functor has kind `(* -> *)`
data ExpF x = Lit Int
            | Add x x
            | Sub x x
            | Mult x x

-- | The fixed point of a functor has kind `(* -> *) -> *`
data Fix f = Fix (f (Fix f))

type Exp = Fix ExpF

exp :: Exp
exp = Fix (Add (Fix (Lit 3)) (Fix (Lit 4))) -- 3 + 4

fold :: Functor f => Alg f a -> Fix f -> a
fold (Alg phi) (Fix f) = phi (fmap (fold (Alg phi)) f)

그들은 이론적으로는 가능이야하지만 마지막으로, 나는 못 볼 것 도 높은 kinded 타입의 생성자를. 우리는 때때로와 같은 유형의 함수를 볼 수 mask :: ((forall a. IO a -> IO a) -> IO b) -> IO b있지만 유형의 복잡성 수준을 보려면 유형 프롤로그 또는 종속 유형의 문헌을 파헤쳐 야한다고 생각합니다.


3
몇 분 안에 코드를 입력 확인하고 수정하겠습니다. 지금 바로 전화를 사용하고 있습니다.
— J. Abrahamson 2014 년

12
@ J.Abrahamson +1 좋은 답변과 인내심을 가지고 휴대 전화에 입력 O_o
— Daniel Gratzer

3
@lobsterism A TreeTree는 단지 병리 적이지만 실제로는 두 가지 유형의 나무가 서로 얽혀 있음을 의미합니다.이 아이디어를 조금 더 밀면 정적으로 안전한 빨간색과 같은 매우 강력한 유형 안전 개념을 얻을 수 있습니다. 검은 나무와 깔끔한 정적으로 균형 잡힌 FingerTree 유형.
— J. Abrahamson 2014 년

3
@JonHarrop 표준 실제 예제는 mtl 스타일 효과 스택과 같은 모나드를 추상화하는 것입니다. 그러나 이것이 실제 세계에서 가치가 있다는 데 동의하지 않을 수 있습니다. HKT 없이도 언어가 성공적으로 존재할 수 있다는 것이 일반적으로 분명하다고 생각합니다. 따라서 어떤 예도 다른 언어보다 더 정교한 추상화를 제공 할 것입니다.
— J. Abrahamson

2
예를 들어 다양한 모나드에서 인증 된 효과의 하위 집합을 가질 수 있으며 해당 사양을 충족하는 모나드에 대해 추상화 할 수 있습니다. 예를 들어, 문자 수준 읽기 및 쓰기를 가능하게하는 "teletype"을 인스턴스화하는 모나드는 IO 및 파이프 추상화를 모두 포함 할 수 있습니다. 다른 예로 다양한 비동기 구현을 추상화 할 수 있습니다. HKT가 없으면 해당 일반 조각으로 구성된 모든 유형을 제한합니다.
— J. Abrahamson 2015 년

64

더 높은 종류의 유형 변수가있는 FunctorHaskell 의 유형 클래스를 고려하십시오 f.

class Functor f where
    fmap :: (a -> b) -> f a -> f b

이 유형 서명이 말하는 것은 fmap이의 유형 매개 변수를 ffrom a으로 변경 b하지만 f그대로 유지한다는 것입니다. 따라서 fmap목록을 통해 사용 하면 목록을 얻을 수 있고 파서를 통해 사용하면 구문 분석기를 얻게되는 식입니다. 그리고 이것들은 정적입니다 , 컴파일 타임 보장입니다.

저는 F #을 모릅니다.하지만 Functor상속과 제네릭을 사용하여 더 높은 종류의 제네릭이없는 Java 또는 C #과 같은 언어로 추상화 를 표현하려고하면 어떤 일이 발생하는지 살펴 보겠습니다 . 첫 시도:

interface Functor<A> {
    Functor<B> map(Function<A, B> f);
}

이 첫 번째 시도의 문제점은 인터페이스 구현이 를 구현하는 모든 클래스 를 반환 할 수 있다는 것 Functor입니다. 누군가는 FunnyList<A> implements Functor<A>그 map메서드가 다른 종류의 컬렉션을 반환하거나 컬렉션이 아니지만 여전히 Functor. 또한 map메서드 를 사용할 때 실제로 예상하는 유형으로 다운 캐스트하지 않는 한 결과에 대해 하위 유형별 메서드를 호출 할 수 없습니다. 따라서 두 가지 문제가 있습니다.

  1. 유형 시스템은 map메서드가 항상 Functor수신자 와 동일한 하위 클래스를 반환 한다는 불변성을 표현할 수 없습니다 .
  2. 따라서 Functor의 결과에 대해 비 메서드 를 호출하는 정적으로 형식이 안전한 방식 은 없습니다 map.

시도 할 수있는 다른 더 복잡한 방법이 있지만 실제로 작동하는 방법은 없습니다. 예를 들어, Functor결과 유형을 제한 하는 하위 유형을 정의하여 첫 번째 시도를 보강 할 수 있습니다.

interface Collection<A> extends Functor<A> {
    Collection<B> map(Function<A, B> f);
}

interface List<A> extends Collection<A> {
    List<B> map(Function<A, B> f);
}

interface Set<A> extends Collection<A> {
    Set<B> map(Function<A, B> f);
}

interface Parser<A> extends Functor<A> {
    Parser<B> map(Function<A, B> f);
}

// …

이는 좁은 인터페이스의 구현자가 메서드 Functor에서 잘못된 유형을 반환하는 것을 방지하는 데 도움이 map되지만, Functor구현할 수있는 구현 수에 제한이 없기 때문에 필요한 더 좁은 인터페이스 수에는 제한이 없습니다.

( 편집 : 그리고 이것은 Functor<B>결과 유형으로 나타나기 때문에 작동하므로 자식 인터페이스가 범위를 좁힐 수 있습니다. 따라서 AFAIK는 Monad<B>다음 인터페이스에서 두 가지 용도를 모두 좁힐 수 없습니다 .

interface Monad<A> {
    <B> Monad<B> flatMap(Function<? super A, ? extends Monad<? extends B>> f);
}

Haskell에서 상위 유형 변수를 사용하는 경우 이것은입니다 (>>=) :: Monad m => m a -> (a -> m b) -> m b.)

또 다른 시도는 재귀 제네릭을 사용하여 인터페이스가 하위 유형의 결과 유형을 하위 유형 자체로 제한하도록하는 것입니다. 장난감 예 :

/**
 * A semigroup is a type with a binary associative operation.  Law:
 *
 * > x.append(y).append(z) = x.append(y.append(z))
 */
interface Semigroup<T extends Semigroup<T>> {
    T append(T arg);
}

class Foo implements Semigroup<Foo> {
    // Since this implements Semigroup<Foo>, now this method must accept 
    // a Foo argument and return a Foo result. 
    Foo append(Foo arg);
}

class Bar implements Semigroup<Bar> {
    // Any of these is a compilation error:

    Semigroup<Bar> append(Semigroup<Bar> arg);

    Semigroup<Foo> append(Bar arg);

    Semigroup append(Bar arg);

    Foo append(Bar arg);

}

그러나 이런 종류의 기술 (당신의 평범한 OOP 개발자에게는 다소 비현실적이며 평범한 기능 개발자에게는 도대체)은 여전히 ​​원하는 Functor제약을 표현할 수 없습니다 .

interface Functor<FA extends Functor<FA, A>, A> {
    <FB extends Functor<FB, B>, B> FB map(Function<A, B> f);
}

여기서 문제는이 제한하지 않는 것입니다 FB같은 가지고 F같은 FA당신이 유형을 선언 할 때 - 그럼 List<A> implements Functor<List<A>, A>의 map방법은 수 여전히 을 반환을 NotAList<B> implements Functor<NotAList<B>, B>.

원시 유형 (매개 변수화되지 않은 컨테이너)을 사용하여 Java에서 마지막 시도 :

interface FunctorStrategy<F> {
    F map(Function f, F arg);
} 

여기에서 Fjust List또는 같은 매개 변수화되지 않은 유형으로 인스턴스화됩니다 Map. 이것은 FunctorStrategy<List>a가List 있지만 목록의 요소 유형을 추적하는 데 유형 변수 사용을 포기했습니다.

여기서 문제의 핵심은 Java 및 C #과 같은 언어가 유형 매개 변수가 매개 변수를 갖는 것을 허용하지 않는다는 것입니다. 경우 자바에서 T형태 변수는, 당신은 쓸 수 T및 List<T>하지만, T<String>. 더 높은 종류의 유형은이 제한을 제거하므로 다음과 같은 것을 가질 수 있습니다 (완전히 고려되지 않음).

interface Functor<F, A> {
    <B> F<B> map(Function<A, B> f);
}

class List<A> implements Functor<List, A> {

    // Since F := List, F<B> := List<B>
    <B> List<B> map(Function<A, B> f) {
        // ...
    }

}

특히이 비트를 처리합니다.

(나는 생각한다) 대신 myList |> List.map f또는 myList |> Seq.map f |> Seq.toList더 높은 종류의 유형을 사용하면 간단하게 작성할 myList |> map f수 있으며 List. 훌륭하지만 (정확하다고 가정 할 때) 약간 사소한 것 같나요? (그리고 단순히 함수 오버로딩을 허용하는 것만으로 할 수는 없나요?) 저는 보통로 변환 Seq한 다음 나중에 원하는대로 변환 할 수 있습니다.

map이러한 방식으로 함수 의 개념을 일반화하는 많은 언어가 있습니다. 마치 매핑이 시퀀스에 관한 것처럼 모델링함으로써. 이 발언은 그 정신입니다.에서 변환을 지원하는 유형이있는 경우를 Seq재사용하여 "무료"로지도 작업을 수행 할 수 Seq.map있습니다.

그러나 Haskell에서는 Functor클래스가 그것보다 더 일반적입니다. 그것은 시퀀스의 개념에 묶여 있지 않습니다. 액션, 파서 결합 자, 함수 fmap등과 같이 시퀀스에 대한 좋은 매핑이없는 유형에 대해 구현할 수 있습니다 IO.

instance Functor IO where
    fmap f action =
        do x <- action
           return (f x)

 -- This declaration is just to make things easier to read for non-Haskellers 
newtype Function a b = Function (a -> b)

instance Functor (Function a) where
    fmap f (Function g) = Function (f . g)  -- `.` is function composition

"매핑"의 개념은 실제로 시퀀스와 관련이 없습니다. 펑터 법칙을 이해하는 것이 가장 좋습니다.

(1) fmap id xs == xs
(2) fmap f (fmap g xs) = fmap (f . g) xs

매우 비공식적으로 :

  1. 첫 번째 법칙은 identity / noop 함수를 사용한 매핑이 아무것도하지 않는 것과 같다고 말합니다.
  2. 두 번째 법칙은 두 번 매핑하여 생성 할 수있는 결과를 한 번 매핑하여 생성 할 수도 있다는 것입니다.

이것이 fmap유형을 보존 하려는 이유 map입니다. 다른 결과 유형을 생성하는 연산을 받는 즉시 이와 같은 보장을하기가 훨씬 더 어려워지기 때문입니다.


나는 당신의 마지막 비트에 관심이 있어요 그래서, 왜 그것은이 가지고 유용 fmap에 Function a이미 그것을 가질 때 .작업을? 나는 왜 op .의 정의가 타당하다는 것을 이해 fmap하지만 . fmap대신 사용할 필요가있는 곳을 얻지 못했습니다 .. 유용 할만한 예를 들어 보면 이해하는 데 도움이 될 것입니다.
— lobsterism

1
아, 그것을 가지고 : 당신이 FN 할 수 double펑터의 double [1, 2, 3]제공 [2, 4, 6]및 double sin이중 죄 인 FN을 제공합니다. 그 사고 방식에서 생각하기 시작하면 배열에서 맵을 실행할 때 시퀀스가 ​​아닌 배열을 기대할 수 있습니다. 왜냐하면 여기서 배열 작업을하고 있기 때문입니다.
— lobsterism

@lobsterism : a를 추상화 Functor하고 라이브러리의 클라이언트가 선택하도록하는 알고리즘 / 기술이 있습니다. J. Abrahamson의 답변은 한 가지 예를 제공합니다. 재귀 폴드는 펑터를 사용하여 일반화 할 수 있습니다. 또 다른 예는 무료 모나드입니다. 이것들은 일종의 일반 인터프리터 구현 라이브러리로 생각할 수 있습니다. 여기서 클라이언트는 "명령어 세트"를 임의의 Functor.
— Luis Casillas 2014 년

3
기술적으로 건전한 대답이지만 왜 누군가가 이것을 실제로 원하는지 궁금해합니다. 나는 Haskell Functor이나 SemiGroup. 실제 프로그램이이 언어 기능을 가장 많이 사용하는 곳은 어디입니까?
— JD

28

이미 여기에있는 훌륭한 답변에 대한 정보를 반복하고 싶지는 않지만 추가하고 싶은 핵심 사항이 있습니다.

일반적으로 특정 모나드 또는 펑터 (또는 응용 펑터, 화살표 또는 ...)를 구현하기 위해 더 높은 종류의 유형이 필요하지 않습니다. 그러나 그렇게하는 것은 대부분 요점을 놓치고 있습니다.

일반적으로 나는 사람들이 functors / monads / whatevers의 유용성을 보지 못할 때 종종 이러한 것들을 한 번에 하나씩 생각하기 때문이라는 것을 발견 했습니다 . Functor / monad / etc 작업은 실제로 하나의 인스턴스에 아무것도 추가하지 않습니다 (bind, fmap 등을 호출하는 대신 bind, fmap 등을 구현 하는 데 사용한 모든 작업을 호출 할 수 있음 ). 이러한 추상화를 정말로 원하는 것은 모든 functor / monad / etc에서 일반적으로 작동하는 코드를 가질 수 있도록하는 것 입니다.

이러한 일반 코드가 널리 사용되는 상황에서 이는 새 모나드 인스턴스를 작성할 때마다 유형 이 이미 작성된 많은 유용한 작업에 즉시 액세스 할 수 있음을 의미합니다 . 그것이 어디에서나 모나드 (그리고 펑터, ...)를 보는 요점입니다. 아니 내가 사용할 수있는 bind것보다 concat및 map구현 myFunkyListOperation(나에게 그 자체로 아무것도 얻는없는), 오히려 내가 필요에 올 때 myFunkyParserOperation와 myFunkyIOOperation는 제네릭 모나드 실제로 있기 때문에 내가 할 수있는 목록의 관점에서 코드 나는 원래 톱을 다시 사용 .

그러나 type safety가 있는 모나드 와 같은 매개 변수화 된 유형을 추상화 하려면 더 높은 종류의 유형이 필요합니다 (여기에 다른 답변에서 잘 설명되어 있습니다).


9
이것은 지금까지 읽은 다른 답변보다 유용한 답변에 가깝지만 더 높은 종류가 유용한 단일 실용적인 응용 프로그램을 계속보고 싶습니다.
— JD

"당신이 정말로 원하는 것은 모든 펑터 / 모나드에서 일반적으로 작동하는 코드를 가질 수 있도록하기 위해서입니다." F #은 13 년 전에 계산 표현식의 형태로 모나드를 얻었으며 원래는 seq 및 비동기 모나드를 사용했습니다. 오늘날 F #은 세 번째 모나드 인 쿼리를 사용합니다. 공통점이 거의없는 모나드가 거의 없는데 왜 그것들을 추상화하고 싶습니까?
— JD

@JonHarrop 다른 사람들이 HKT를 지원하는 언어로 엄청난 수의 모나드 (및 펑터, 화살표 등; HKT는 모나드에 관한 것이 아님)를 사용하여 코드를 작성하고이를 추상화하는 용도를 찾았다는 것을 분명히 알고 있습니다. 그리고 분명히 당신은 그 코드 중 어느 것도 실용적인 용도가 없다고 생각하고 다른 사람들이 왜 그것을 작성하려고하는지 궁금합니다. 5 년 전에 이미 댓글을 단 6 년 된 게시물에 대한 토론을 시작하기 위해 돌아와서 어떤 종류의 통찰력을 얻고 싶습니까?
— Ben

"6 년 된 게시물에 대한 토론을 시작하기 위해 돌아와서 이득을 얻고 싶다". 회고전. 돌이켜 보면 이제 모나드에 대한 F #의 추상화가 거의 사용되지 않는다는 것을 알 수 있습니다. 따라서 크게 다른 3 가지를 추상화하는 능력은 설득력이 없습니다.
— JD

@JonHarrop 내 대답의 요점은 개별 모나드 (또는 펑터 등)가 유목 인터페이스없이 표현 된 유사한 기능보다 실제로 더 유용하지는 않지만 많은 이질적인 것들을 통합한다는 것입니다. F #에 대한 귀하의 전문 지식을 따르겠습니다. 그러나 실패, 상태 저장, 구문 분석 등과 같은 하나를 가질 수있는 모든 개념에 대해 모나드 인터페이스를 구현하는 대신 3 개의 개별 모나드 만 있다고 말하는 경우 예,이 세 가지를 통합하여 많은 이점을 얻지 못한다는 것은 놀라운 일이 아닙니다.
— Ben

15

좀 더 .NET 관련 관점에서 나는 이것에 대한 블로그 게시물을 얼마 전에 썼습니다 . 핵심은 더 높은 종류의 유형을 사용하면 잠재적으로 IEnumerables와 사이에 동일한 LINQ 블록을 재사용 할 수 있다는 것입니다.IObservables 있지만 더 높은 종류의 유형이 없으면 불가능합니다.

당신이 (필자는 블로그를 게시 한 후 알아 낸) 얻을 수있는 가장 가까운 당신의 자신을 만드는 것입니다 IEnumerable<T>및 IObservable<T>과에서 둘 다 확장 IMonad<T>. 이것은 그들이 표시하는 경우 당신은 당신의 LINQ 블록을 재사용 할 수 있도록 것입니다 IMonad<T>하지만 수 있기 때문에 다음 더 이상 형태 보증의 당신은 믹스 앤 매치를하는 방법 IObservables과IEnumerables 그것을, 당신은 좋겠이 가능하도록주고 싶었어요 사운드 수 있지만 같은 블록 내에서 기본적으로 정의되지 않은 동작이 발생합니다.

나는 Haskell이 이것을 어떻게 쉽게 만드는지에 대한 후기 글 을 썼습니다 . (실제로, 블록을 특정 종류의 모나드로 제한하려면 코드가 필요합니다. 재사용을 활성화하는 것이 기본값입니다.)


2
실용적인 것을 언급하는 유일한 대답 인 것에 대해 +1을 드릴 것입니다. 그러나 저는 IObservables프로덕션 코드에서 사용한 적이 없다고 생각 합니다.
— JD

5
@JonHarrop 이것은 사실이 아닌 것 같습니다. F #에서 모든 이벤트는 IObservable이며, 책의 WinForms 장에있는 이벤트를 사용합니다.
— Dax Fohl

1
마이크로 소프트는 저에게 그 책을 쓰기 위해 돈을 지불했고 그 기능을 다루도록 요구했습니다. 프로덕션 코드에서 이벤트를 사용한 것을 기억할 수 없지만 살펴 보겠습니다.
— JD

너무 나는 가정 가능할 것이다 된 IQueryable 및 IEnumerable을 사이에 재사용
— 콜라

4 년 후 저는 조사를 마쳤습니다. 우리는 Rx를 생산에서 제거했습니다.
— JD

13

Haskell에서 가장 많이 사용되는 유형 다형성의 예는 Monad인터페이스입니다. Functor그리고 Applicative내가 보여 줄게, 그래서 같은 방식으로 높은 kinded하는 Functor뭔가 간결을 보여주기 위해.

class Functor f where
    fmap :: (a -> b) -> f a -> f b

이제 유형 변수 f가 사용되는 방식을 살펴보면서 해당 정의를 검토합니다 . f가치가있는 유형을 의미 할 수 없음을 알 수 있습니다. 해당 유형 서명의 값은 함수에 대한 인수 및 결과이기 때문에 식별 할 수 있습니다. 따라서 유형 변수 a및 b값을 가질 수있는 유형입니다. 유형 표현식 f a및 f b. 하지만 f그 자체는 아닙니다 . f더 높은 종류의 유형 변수의 예입니다. 주어진 *값을 가질 수 있습니다 유형의 종류, f종류가 있어야합니다 * -> *. 즉, 우리가 이전 조사에서 알 수 있기 때문에, 값을 가질 수있는 유형 소요입니다 a및 b값을해야합니다. 그리고 우리는 또한 알고 f a및f b 값이 있어야하므로 값이 있어야하는 유형을 반환합니다.

이것은 더 높은 종류의 유형 변수 f의 정의에 사용됩니다 Functor.

Applicative및 Monad인터페이스는 더 추가 할 수 있지만 그들은 호환입니다. 이것은 그들이 종류와 함께 유형 변수에서도 작동한다는 것을 의미합니다 * -> *.

더 높은 종류의 유형에 대해 작업하면 추가 수준의 추상화가 도입됩니다. 기본 유형에 대한 추상화를 만드는 데만 제한되지 않습니다. 다른 유형을 수정하는 유형에 대한 추상화를 만들 수도 있습니다.


4
더 높은 종류가 무엇인지에 대한 또 다른 훌륭한 기술적 설명은 그것이 무엇에 유용한 지 궁금하게 만듭니다. 실제 코드에서 이것을 어디에서 활용 했습니까?
— JD
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.