ติดตั้งคลาส Haskell ด้วยอินเตอร์เฟส C #


13

ฉันพยายามที่จะเปรียบเทียบคลาสประเภทของ Haskell และอินเทอร์เฟซของ C # Functorสมมติว่ามี

Haskell:

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);
}

นี้คือการดำเนินการที่ไม่ถูกต้องและฉัน stucked จริงกับทั่วไปประเภทที่ควรจะส่งกลับโดยF fmapควรกำหนดไว้อย่างไรและที่ไหน?

มันเป็นไปไม่ได้ที่จะใช้Functorใน C # และทำไม? หรืออาจมีแนวทางอื่น


8
Eric Lippert พูดถึงนิดหน่อยว่าระบบของ C # นั้นยังไม่เพียงพอที่จะสนับสนุนธรรมชาติของ Functors ที่สูงขึ้นตามที่ Haskell ได้กำหนดไว้ในคำตอบนี้: stackoverflow.com/a/4412319/303940
KChaloux

1
นี่คือประมาณ 3 ปีที่ผ่านมา มีอะไรเปลี่ยนแปลงบ้าง?
ДМИТРИЙМАЛИКОВ

4
ไม่มีอะไรเปลี่ยนแปลงเพื่อให้เป็นไปได้ใน C # และฉันไม่คิดว่ามันจะเป็นในอนาคต
jk

คำตอบ:


8

ระบบชนิด C # ขาดคุณสมบัติสองอย่างที่จำเป็นในการใช้คลาสประเภทเป็นอินเทอร์เฟซอย่างเหมาะสม

มาเริ่มด้วยตัวอย่างของคุณกัน แต่กุญแจสำคัญคือการแสดงบัญชีที่สมบูรณ์ยิ่งขึ้นว่า typeclass คืออะไรและทำอะไรจากนั้นลองแมปเหล่านั้นกับ C # bits

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 ช่วยให้คุณสามารถทำกับคลาสประเภทที่ไม่แปลโดยเฉพาะอย่างยิ่งเมื่อคุณเริ่มดูจำนวนนิยมในประเภทอัตถิภาวนิยมหรือส่วนขยาย GHC อื่น ๆ เช่น Generic ADTs

คุณเห็นด้วย Haskell คุณสามารถกำหนดฟังก์ชั่น

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 # ขาด polamorphism ที่เหมาะสม (ซึ่งกลายเป็นซุปเปอร์ชัดเจนทันทีที่คุณพยายาม interop F # กับ C #) คุณไม่สามารถแยกแยะได้อย่างชัดเจนหรือชัดเจนว่าทางขวาหรือซ้ายเป็นTerminalของ ดีที่สุดที่คุณสามารถทำได้คือใช้nullแต่จะทำอย่างไรถ้าคุณกำลังพยายามสร้างค่าประเภท a 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);
}

ดังนั้นถ้าเราใช้ Option monad (เพราะ monads ทั้งหมดเป็น functors)

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> เป็น <A> บางส่วนเมื่อคุณต้องการ


ฉันมีปัญหากับpureอินเทอร์เฟซ functor ทั่วไป: คอมไพเลอร์บ่นIF<Functor, A> pure<A>(A a);กับ "ประเภทFunctorไม่สามารถใช้เป็นพารามิเตอร์ประเภทFunctorในวิธีการทั่วไปประเภทIF<Functor, A>ไม่มีการแปลงมวยหรือการแปลงพารามิเตอร์ประเภทจากFunctorเป็นF<Functor>" สิ่งนี้หมายความว่า? แล้วทำไมเราต้องกำหนดpureในสองแห่ง นอกจากนี้ไม่ควรpureจะคงที่?
Niriel

1
สวัสดี ฉันคิดว่าเพราะฉันกำลังพูดถึง monads และ monad transformers เมื่อออกแบบชั้นเรียน หม้อแปลง monad เช่น OptionT monad transformer (อาจเป็น Haskell) ถูกกำหนดใน C # เป็น OptionT <M, A> โดยที่ M เป็นอีกหนึ่ง monad ทั่วไป กล่อง OptionT monad transformer ใน monad เป็นประเภท M <Option <A>> แต่เนื่องจาก C # ไม่มีประเภทที่สูงกว่าคุณจำเป็นต้องมีวิธีในการสร้างอินสแตนซ์ M monad ที่สูงขึ้นเมื่อเรียก OptionT.map และ OptionT.bind วิธีการคงที่ไม่ทำงานเพราะคุณไม่สามารถโทรหา M.pure (A) สำหรับ monad M. ใด ๆ
DetriusXii
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.