อะไรคือทางเลือกในการตั้งโปรแกรมการทำงานของอินเตอร์เฟส?


15

หากฉันต้องการตั้งโปรแกรมในรูปแบบ "ฟังก์ชั่น" ฉันจะใช้อะไรแทนอินเตอร์เฟสได้บ้าง?

interface IFace
{
   string Name { get; set; }
   int Id { get; }
}
class Foo : IFace { ... }

อาจจะTuple<>?

Tuple<Func<string> /*get_Name*/, Action<String> /*set_Name*/, Func<int> /*get_Id*/> Foo;

เหตุผลเดียวที่ฉันใช้อินเทอร์เฟซตั้งแต่แรกก็เพราะฉันต้องการให้มีคุณสมบัติ / วิธีการบางอย่างอยู่เสมอ


แก้ไข:รายละเอียดเพิ่มเติมบางอย่างเกี่ยวกับสิ่งที่ฉันคิด / พยายาม

บอกว่าฉันมีวิธีที่ใช้สามฟังก์ชั่น:

static class Blarf
{
   public static void DoSomething(Func<string> getName, Action<string> setName, Func<int> getId);
}

ด้วยตัวอย่างของBarฉันสามารถใช้วิธีนี้:

class Bar
{
   public string GetName();
   public void SetName(string value);

   public int GetId();
}
...
var bar = new Bar();
Blarf.DoSomething(bar.GetName, bar.SetName, bar.GetId);

แต่นั่นเป็นความเจ็บปวดเล็กน้อยที่ฉันต้องพูดถึงbarสามครั้งในการโทรครั้งเดียว นอกจากนี้ฉันไม่ได้ตั้งใจให้ผู้โทรจัดหาฟังก์ชั่นจากอินสแตนซ์ที่แตกต่างกัน

Blarf.DoSomething(bar1.GetName, bar2.SetName, bar3.GetId); // NO!

ใน C #, interfaceเป็นวิธีหนึ่งในการจัดการกับสิ่งนี้; แต่ดูเหมือนว่าเป็นวิธีการเชิงวัตถุมาก ฉันสงสัยว่ามีวิธีแก้ปัญหาการทำงานเพิ่มเติม: 1) ผ่านกลุ่มของฟังก์ชั่นร่วมกันและ 2) ให้แน่ใจว่าฟังก์ชั่นที่เกี่ยวข้องกันอย่างถูกต้อง


คุณจะไม่ อินเทอร์เฟซสำหรับประเภทข้อมูลนั้นสมบูรณ์แบบ (แม้ว่าคุณจะชอบวัตถุที่ไม่เปลี่ยนรูป)
Telastyn

1
บทที่ 2 ของ SICP ค่อนข้างเกี่ยวกับเรื่องนี้
user16764

7
หลังจากอ่านคำถามของคุณใหม่แล้วฉันอยากรู้ว่าคุณกำลังพยายามทำสิ่งใดเป็นพิเศษ สิ่งที่คุณดูเหมือนจะขอเป็นวิธีการทำรูปแบบอู programmin ด้าน effectful กับอินสแตนซ์ในรูปแบบการทำงานที่ไม่ได้ให้ความรู้สึก ..
จิมมี่ฮอฟฟา

คำตอบนั้นขึ้นอยู่กับภาษา ใน Clojure คุณสามารถใช้clojure.org/protocolsโดยที่บริเวณที่อ่อนนุ่มเพียงอย่างเดียวคือประเภทของพารามิเตอร์ที่ฟังก์ชั่นต้องทำงาน - เป็นวัตถุ - นั่นคือทั้งหมดที่คุณรู้
งาน

1
ทำให้มันง่ายขึ้น: โครงสร้างที่มีพอยน์เตอร์ของเมธอดเหล่านั้นรวมถึงฟังก์ชั่นเพื่อเริ่มต้นจากอินสแตนซ์ของวัตถุ ทำไมต้องแฮสเคลล์? ;)
mlvljr

คำตอบ:


6

อย่าใช้การเขียนโปรแกรมที่ใช้งานเป็นแผ่นไม้อัดบาง ๆ มีมากกว่าความแตกต่างของวากยสัมพันธ์

ในกรณีนี้คุณมีGetIDวิธีการซึ่งแสดงถึงเอกลักษณ์ของวัตถุ นี่ไม่ใช่วิธีการที่ดีในการเขียนโปรแกรมที่ใช้งานได้ บางทีคุณอาจบอกเราถึงปัญหาที่คุณพยายามแก้ไขและเราสามารถให้คำแนะนำที่มีความหมายมากขึ้น


3
"คุณต้องคิดเป็นภาษารัสเซีย"
Marаn

ยุติธรรมเพียงพอ ปัญหาที่แท้จริงของฉันไม่น่าสนใจอย่างยิ่ง ฉันได้รับสิ่งที่ทำงานได้ดีใน C # ( github.com/JDanielSmith/Projects/tree/master/PictureOfTheDayหากคุณต้องการดูรหัส) แต่มันสนุกที่จะทำในรูปแบบที่ใช้งานได้มากกว่าในขณะที่ ยังคงใช้ C #
Marаn

1
@ Ðаnคือว่าจริงๆFirefox (ภาพยนตร์)อ้าง (เพราะที่น่ากลัวถ้ามันเป็น)? หรือจะใช้ที่อื่นหรือไม่
icc97

2
เมื่อฉันเห็นด้วยว่ามันเป็นการปรับเปลี่ยนกระบวนทัศน์อย่างสมบูรณ์จากการเขียนโปรแกรมเชิงซ้อนไปเป็นฟังก์ชั่นมีคำสั่งจำนวนมากที่มีโค้ดมากกว่าหนึ่งบรรทัดที่เขียนในลักษณะที่จำเป็น ดังนั้นกรณีมุมที่เขียนขึ้นสำหรับระบบขนาดใหญ่จำนวนมากจะถูกค้นพบด้วยการเขียนโปรแกรมที่จำเป็น มีวิธีปฏิบัติที่ดีมากมายในการเขียนโปรแกรมที่จำเป็นและรู้ว่าทักษะเหล่านั้นสามารถแปลได้หรือหากพวกเขาไม่ใช่ประเด็นใน FP เป็นคำถามที่มีค่า เป็นไปได้อย่างสมบูรณ์ที่จะเขียนโค้ดที่น่ากลัวใน FP ดังนั้นคำถามประเภทนี้ควรเน้นส่วนที่ดีของ FP ด้วย
icc97

11

Haskell และอนุพันธ์นั้นมี typeclasses ซึ่งคล้ายกับอินเตอร์เฟส แม้ว่าดูเหมือนว่าคุณกำลังถามเกี่ยวกับวิธีการห่อหุ้มซึ่งเป็นคำถามเกี่ยวกับระบบการพิมพ์ ระบบประเภท hindley Milner นั้นเป็นเรื่องปกติในภาษาที่ใช้งานได้และมีประเภทข้อมูลที่ทำเพื่อคุณในรูปแบบที่แตกต่างกันในภาษาต่างๆ


5
+1 สำหรับประเภทย่อย - ความแตกต่างหลักระหว่างประเภทย่อยของ Haskell และส่วนต่อประสาน Java คือส่วนแบ่งประเภทเกี่ยวข้องกับชนิดหลังจากที่ทั้งคู่ถูกประกาศแยกกัน คุณสามารถใช้ประเภทเก่าผ่าน "อินเทอร์เฟซ" ใหม่ได้อย่างง่ายดายเหมือนกับที่คุณสามารถใช้ "อินเทอร์เฟซ" แบบเก่าเพื่อเข้าถึงประเภทใหม่ สำหรับการซ่อนข้อมูลคุณซ่อนการใช้งานของประเภทในโมดูล อย่างน้อยตามเบอร์ทรานด์เมเยอร์แห่งความมีชื่อเสียงของไอเฟลชั้น OOP เป็นวิชาเรียน
Steve314

5

มีสองสามวิธีในการอนุญาตให้ฟังก์ชันจัดการกับอินพุตหลายอินพุต

ครั้งแรกและที่พบบ่อยที่สุด: Parametric Polymorphism

สิ่งนี้ทำให้ฟังก์ชั่นกระทำกับชนิดใด ๆ :

--Haskell Example
id :: a -> a --Here 'a' is just some arbitrary type
id myRandomThing = myRandomThing

head :: [a] -> a
head (listItem:list) = listItem

เป็นสิ่งที่ดี แต่ไม่ได้ให้การกระจายแบบไดนามิกที่ส่วนต่อประสาน OO มีให้คุณ สำหรับ Haskell นี้มีประเภทของงานพิมพ์, Scala มีส่วนเกี่ยวข้อง ฯลฯ

class Addable a where
   (<+>) :: a -> a -> a
instance Addable Int where
   a <+> b = a + b
instance Addable [a] where
   a <+> b = a ++ b

--Now we can get that do something similar to OO (kinda...)
addStuff :: (Addable a) => [a] -> a
-- Notice how we limit 'a' here to be something Addable
addStuff (x:[]) = x
addStuff (x:xs) = x <+> addStuff xs
-- In better Haskell form
addStuff' = foldl1 <+>

ระหว่างกลไกทั้งสองนี้คุณสามารถแสดงพฤติกรรมที่ซับซ้อนและน่าสนใจทุกประเภทในประเภทของคุณ


1
คุณสามารถเพิ่มคำแนะนำการเน้น sintaxเมื่อภาษาในคำตอบไม่ตรงกับภาษาในคำถาม ดูตัวอย่างการแก้ไขที่แนะนำของฉัน
hugomg

1

กฎพื้นฐานของหัวแม่มือคือในฟังก์ชั่นการเขียนโปรแกรม FP ทำงานเช่นเดียวกับวัตถุที่ทำในการเขียนโปรแกรม OO คุณสามารถเรียกวิธีการของพวกเขา (เช่นกันวิธีการ "เรียก") และพวกเขาตอบสนองตามกฎภายในบางอย่างที่ห่อหุ้มแล้ว โดยเฉพาะอย่างยิ่งภาษา FP ที่เหมาะสมทุกประเภทช่วยให้คุณมี "ตัวแปรอินสแตนซ์" ในการทำงานของคุณด้วยการปิด / กำหนดขอบเขตศัพท์

var make_OO_style_counter = function(){
   return {
      counter: 0
      increment: function(){
          this.counter += 1
          return this.counter;
      }
   }
};

var make_FP_style_counter = function(){
    var counter = 0;
    return fucntion(){
        counter += 1
        return counter;
    }
};

ตอนนี้คำถามต่อไปคือสิ่งที่คุณหมายถึงโดยอินเตอร์เฟซ วิธีการหนึ่งคือการใช้อินเตอร์เฟสเล็กน้อย (ซึ่งเป็นไปตามอินเตอร์เฟสถ้ามันบอกว่าทำเช่นนั้น) - วิธีนี้มักจะขึ้นอยู่กับภาษาที่คุณใช้เป็นจำนวนมาก อีกวิธีหนึ่งในการกำหนดอินเทอร์เฟซเป็นวิธีโครงสร้างดูสิ่งที่พารามิเตอร์รับและส่งคืน นี่คือการเรียงลำดับของอินเทอร์เฟซที่คุณมักจะเห็นในภาษาไดนามิกเป็ดพิมพ์และมันเหมาะมากกับ FP ทั้งหมด: อินเทอร์เฟซเป็นเพียงประเภทของพารามิเตอร์อินพุตกับฟังก์ชั่นของเราและประเภทที่พวกเขากลับมา ประเภทที่ถูกต้องเหมาะสมกับอินเตอร์เฟส!

ดังนั้นวิธีที่ตรงไปตรงมาที่สุดในการแสดงวัตถุที่ตรงกับอินเตอร์เฟซคือการมีกลุ่มของฟังก์ชั่น คุณมักจะหลีกเลี่ยงความน่าเกลียดของการส่งฟังก์ชั่นแยกจากกันโดยจัดเก็บไว้ในบันทึกบางประเภท:

var my_blarfable = {
 get_name: function(){ ... },
 set_name: function(){ ... },
 get_id:   function(){ ... }
}

do_something(my_blarfable)

การใช้ฟังก์ชั่นเปล่าหรือบันทึกฟังก์ชั่นเปล่าจะช่วยแก้ปัญหาส่วนใหญ่ของคุณในลักษณะ "ไร้ไขมัน" ได้โดยไม่ต้องใช้หม้อไอน้ำจำนวนมาก หากคุณต้องการบางสิ่งที่สูงกว่านั้นบางครั้งภาษาก็ให้คุณสมบัติพิเศษแก่คุณ ตัวอย่างหนึ่งที่ผู้คนกล่าวถึงคือคลาสประเภท Haskell คลาสประเภทหลักเชื่อมโยงประเภทกับหนึ่งในบันทึกของฟังก์ชั่นเหล่านั้นและช่วยให้คุณสามารถเขียนสิ่งต่าง ๆ เพื่อให้พจนานุกรมมีความหมายและได้รับการส่งผ่านไปยังฟังก์ชั่นภายในโดยอัตโนมัติตามความเหมาะสม

-- Explicit dictionary version
-- no setters because haskell doesn't like mutable state.
data BlargDict = BlargDict {
    blarg_name :: String,
    blarg_id   :: Integer
}

do_something :: BlargDict -> IO()
do_something blarg_dict = do
   print (blarg_name blarg_dict)
   print (blarg_id   blarg_dict)

-- Typeclass version   
class Blargable a where
   blag_name :: a -> String
   blag_id   :: a -> String

do_something :: Blargable a => a -> IO
do_something blarg = do
   print (blarg_name blarg)
   print (blarg_id   blarg)

สิ่งสำคัญสิ่งหนึ่งที่ควรทราบเกี่ยวกับ typeclasses อย่างไรก็ตามคือพจนานุกรมที่เกี่ยวข้องกับชนิดและไม่ใช่ค่า (เช่นสิ่งที่เกิดขึ้นในพจนานุกรมและรุ่น OO) ซึ่งหมายความว่าระบบประเภทไม่อนุญาตให้คุณผสม "ประเภท" [1] หากคุณต้องการรายการ "blargables" หรือฟังก์ชั่นไบนารีที่ใช้ในการ blargables แล้ว typeclasses จะ จำกัด ทุกอย่างให้เป็นประเภทเดียวกันในขณะที่วิธีการพจนานุกรมจะช่วยให้คุณมีต้นกำเนิดที่แตกต่างกัน (รุ่นที่ดีขึ้นมากในสิ่งที่คุณเป็น ทำ)

[1] มีวิธีการขั้นสูงในการทำ "ประเภทอัตถิภาวนิยม" แต่โดยทั่วไปแล้วจะไม่คุ้มกับปัญหา


0

ฉันคิดว่ามันจะเป็นภาษาที่เฉพาะเจาะจง ฉันมาจากภูมิหลังที่เงียบ ในหลายกรณีการเชื่อมต่อกับสถานะแบ่งรูปแบบการทำงานเป็นองศา ตัวอย่างเช่น CLOS คือที่ LISP ทำงานได้น้อยลงและใกล้เคียงกับภาษาที่จำเป็น โดยทั่วไปแล้วพารามิเตอร์ฟังก์ชันที่จำเป็นรวมกับวิธีการระดับที่สูงกว่าอาจเป็นสิ่งที่คุณกำลังมองหา

;; returns a function of the type #'(lambda (x y z &optional a b c)

(defun get-higher-level-method-impl (some-type-of-qualifier) 
    (cond ((eq 'foo) #'the-foo-version)
          ((eq 'bar) #'the-bar-version)))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.