C # 인터페이스로 Haskell 타입 클래스 구현


13

Haskell의 형식 클래스와 C #의 인터페이스를 비교하려고합니다. 가 있다고 가정합니다 Functor.

하스켈 :

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

이 유형 클래스를 C #의 인터페이스로 구현하는 방법은 무엇입니까?

내가 시도한 것 :

interface Functor<A, B>
{
    F<B> fmap(Func<A, B> f, F<A> x);
}

이것은 유효하지 않은 구현이며 실제로 F반환 해야하는 제네릭 형식으로 붙어 있습니다 fmap. 어떻게 정의하고 어디에?

FunctorC #에서는 구현이 불가능 하며 그 이유는 무엇입니까? 아니면 다른 접근법이 있습니까?


8
: 에릭 Lippert의이 대답에 하스켈에 의해 정의 된 C #의 형식 시스템이 펑터의 높은 kinded 특성을 지원하기 정말 충분하지 않다 방법에 대해 조금 이야기 stackoverflow.com/a/4412319/303940
KChaloux

1
이것은 약 3 년 전이었습니다. 뭔가 바뀌 었나요?
ДМИТРИЙ МАЛИКОВ

4
C #에서 이것을 가능하게하기 위해 아무것도 바뀌지 않았으며, 미래에도 그 가능성을 생각하지 않습니다
jk.

답변:


8

C #의 형식 시스템에는 형식 클래스를 인터페이스로 올바르게 구현하는 데 필요한 몇 가지 기능이 없습니다.

예제부터 시작해 봅시다.하지만 핵심은 타입 클래스가 무엇인지, 무엇을하는지에 대한 더 자세한 설명을 보여준 다음 C # 비트에 매핑하려고합니다.

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

이것은 유형 클래스 정의이거나 인터페이스와 유사합니다. 이제 타입의 정의와 그 타입 클래스의 구현을 보자.

data Awesome a = Awesome a a

instance Functor Awesome where
  fmap f (Awesome a1 a2) = Awesome (f a1) (f a2)

이제 인터페이스로는 가질 수없는 타입 클래스의 한 가지 분명한 사실을 볼 수 있습니다 . 형식 클래스의 구현은 형식 정의의 일부가 아닙니다. C #에서 인터페이스를 구현하려면 인터페이스를 구현하는 유형 정의의 일부로 인터페이스를 구현해야합니다. 즉, 자신이 구현하지 않은 유형에 대한 인터페이스를 구현할 수 없지만 Haskell에서는 액세스 할 수있는 모든 유형에 대한 유형 클래스를 구현할 수 있습니다.

그것은 아마도 가장 큰 것일 수도 있지만 C #과 동등한 것을 실제로 작동시키지 않는 상당히 중요한 또 다른 차이점이 있습니다. 다형성에 관한 것입니다. 또한 Haskell을 사용하면 기존 유형 또는 일반 ADT와 같은 다른 GHC 확장에서 일반화의 양을 볼 때 특히 번역하지 않는 유형 클래스로 할 수있는 비교적 일반적인 일이 있습니다.

하스켈을 사용하면 펑터를 정의 할 수 있습니다.

data List a = List a (List a) | Terminal
data Tree a = Tree val (Tree a) (Tree a) | Terminal

instance Functor List where
  fmap :: (a -> b) -> List a -> List b
  fmap f (List a Terminal) = List (f a) Terminal
  fmap f (List a rest) = List (f a) (fmap f rest)

instance Functor Tree where
  fmap :: (a -> b) -> Tree a -> Tree b
  fmap f (Tree val Terminal Terminal) = Tree (f val) Terminal Terminal
  fmap f (Tree val Terminal right) = Tree (f val) Terminal (fmap f right)
  fmap f (Tree val left Terminal) = Tree (f val) (fmap f left) Terminal
  fmap f (Tree val left right) = Tree (f val) (fmap f left) (fmap f right)

그런 다음 소비에 기능을 가질 수 있습니다.

mapsSomething :: Functor f, Show a => f a -> f String
mapsSomething rar = fmap show rar

여기에 문제가 있습니다. C #에서는이 함수를 어떻게 작성합니까?

public Tree<a> : Functor<a>
{
    public a Val { get; set; }
    public Tree<a> Left { get; set; }
    public Tree<a> Right { get; set; }

    public Functor<b> fmap<b>(Func<a,b> f)
    {
        return new Tree<b>
        {
            Val = f(val),
            Left = Left.fmap(f);
            Right = Right.fmap(f);
        };
    }
}
public string Show<a>(Showwable<a> ror)
{
    return ror.Show();
}

public Functor<String> mapsSomething<a,b>(Functor<a> rar) where a : Showwable<b>
{
    return rar.fmap(Show<b>);
}

그래서 C # 버전과 함께 몇 가지의 잘못은 내가 당신이 사용 할 수도 확실하지 않다 한 가지, 거기에 <b>내가 거기처럼 규정 만하지 않고 내가 생각 이 파견하지 않을 특정 Show<>(시도 주시기 적절하게 알아 내기 위해 컴파일하십시오.

그러나 여기서 더 큰 문제는 위의 Haskell과 달리 Terminal유형의 일부로 정의 된 다음 유형 대신 사용할 수 있다는 것입니다 .C #에 적절한 매개 변수 다형성이 부족하기 때문에 (상호 작용하려고하자마자 명백해집니다) F # with C #)를 사용하면 Right 또는 Left가 Terminals 인지 명확하거나 명확하게 구분할 수 없습니다 . 최선의 방법은을 사용하는 것입니다 null. 그러나 값 유형을 만들려고 Functor하거나 Either값을 전달하는 두 가지 유형을 구별하려는 경우 어떻게해야합니까? 이제 하나의 유형을 사용하고 두 가지 다른 값을 사용하여 차별을 모델링하기 위해 확인하고 전환해야합니까?

적절한 합계 유형, 공용체 유형, ADT가 없으면 호출하려는 유형에 관계없이 하루 종일 여러 유형 (생성자)을 단일 유형으로 처리 할 수 ​​있기 때문에 유형 클래스에서 많은 것을 포기하게됩니다. .NET의 기본 유형 시스템에는 그러한 개념이 없습니다.


2
나는 Haskell (표준 ML 만)에 정통하지 않으므로 이것이 얼마나 큰 차이인지는 모르지만 C #으로 합계 유형을 인코딩 할 수 있습니다 .
Doval

5

두 가지 클래스가 필요합니다. 하나는 고차 제네릭 (functor)을 모델링하고 다른 하나는 결합 된 functor를 자유 값 A로 모델링합니다.

interface F<Functor> {
   IF<Functor, A> pure<A>(A a);
}

interface IF<Functor, A> where Functor : F<Functor> {
   IF<Functor, B> pure<B>(B b);
   IF<Functor, B> map<B>(Func<A, B> f);
}

옵션 모나드를 사용하면 모든 모나드가 펑터이기 때문에

class Option : F<Option> {
   IF<Option, A> pure<A>(A a) { return new Some<A>(a) };
}

class OptionF<A> : IF<Option, A> {
   IF<Option, B> pure<B>(B b) {
      return new Some<B>(b);
   }

   IF<Option, B> map<B>(Func<A, B> f) {
       var some = this as Some<A>;
       if (some != null) {
          return new Some<B>(f(some.value));
       } else {
          return new None<B>();
       }
   } 
}

그런 다음 정적 확장 메서드를 사용하여 필요할 때 IF <Option, B>에서 Some <A>로 변환 할 수 있습니다.


나는에 어려움이있는 pure일반 펑 인터페이스 : 컴파일러에 불만 IF<Functor, A> pure<A>(A a);을 가진 "유형이 Functor유형의 매개 변수로 사용할 수 없습니다 Functor방법의 일반적인 유형 IF<Functor, A>에는 권투 변환 또는 유형 매개 변수 변환에서 없다. FunctorF<Functor>." 이것은 무엇을 의미 하는가? 그리고 왜 우리 pure는 두 곳에서 정의해야 합니까? 또한 pure정적 이 아니어야 합니까?
Niriel

1
안녕하세요. 클래스를 디자인 할 때 모나드와 모나드 트랜스포머를 암시하고 있었기 때문에 생각합니다. OptionT 모나드 변환기 (Haskell의 MayTT)와 같은 모나드 변환기는 C #에서 OptionT <M, A>로 정의됩니다. 여기서 M은 다른 일반 모나드입니다. M <Option <A >> 유형의 모나드에있는 OptionT 모나드 변환기 상자는 C #에 더 높은 종류의 유형이 없으므로 OptionT.map 및 OptionT.bind를 호출 할 때 더 높은 종류의 M 모나드를 인스턴스화하는 방법이 필요합니다. 정적 메소드는 모나드 M에 대해 M.pure (A a)를 호출 할 수 없으므로 작동하지 않습니다.
DetriusXii
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.