ทำไมไม่มียาชื่อสามัญใน Go?


126

ข้อจำกัดความรับผิดชอบ: ตอนนี้ฉันเล่นกับ Go เพียงวันเดียวดังนั้นมีโอกาสที่ดีที่ฉันพลาดไปมาก

มีใครรู้บ้างไหมว่าทำไมไม่มีการรองรับ generics / template / whatsInAName ใน Go? ดังนั้นจึงมีแบบทั่วไปmapแต่จัดทำโดยคอมไพเลอร์ในขณะที่โปรแกรมเมอร์ Go ไม่สามารถเขียนการใช้งานของตัวเองได้ จากการพูดคุยเกี่ยวกับการทำให้ Go เป็นแบบตั้งฉากกันมากที่สุดทำไมฉันถึงใช้ประเภททั่วไป แต่ไม่สามารถสร้างใหม่ได้

โดยเฉพาะอย่างยิ่งเมื่อพูดถึงการเขียนโปรแกรมเชิงฟังก์ชั่นมี lambdas แม้กระทั่งการปิด แต่ด้วยระบบประเภทคงที่ซึ่งขาด generics ฉันจะเขียนฟังก์ชันลำดับที่สูงกว่าทั่วไปได้filter(predicate, list)อย่างไร? ตกลงรายการที่เชื่อมโยงและสิ่งที่คล้ายกันสามารถทำได้ด้วยการinterface{}เสียสละความปลอดภัยประเภท

เนื่องจากการค้นหาอย่างรวดเร็วบน SO / Google ไม่ได้เปิดเผยข้อมูลเชิงลึกใด ๆ ดูเหมือนว่าข้อมูลทั่วไปจะถูกเพิ่มลงใน Go ในภายหลัง ฉันเชื่อว่า ธ อมป์สันจะทำได้ดีกว่าพวก Java แต่ทำไมต้องใช้ชื่อสามัญ? หรือมีการวางแผนและยังไม่ได้ดำเนินการ?


ฉันคิดว่ามันคุ้มค่าที่จะชี้ให้เห็น: การใช้อินเทอร์เฟซ {} ไม่ลดทอนความปลอดภัย เป็นประเภทและสามารถยืนยัน (ไม่ส่ง) เป็นประเภทอื่นได้ แต่การยืนยันเหล่านี้ยังคงเรียกใช้การตรวจสอบรันไทม์เพื่อรักษาความปลอดภัยของประเภท
cthom06

12
interface{}เสียสละความปลอดภัยประเภทคงที่ อย่างไรก็ตามนี่เป็นข้อร้องเรียนที่ค่อนข้างแปลกเมื่อกล่าวถึง Scheme เป็นย่อหน้าถัดไปเนื่องจากปกติ Scheme ไม่มีการตรวจสอบประเภทคงที่
poolie

@poolie: สิ่งที่ฉันกังวลคือการยึดติดกับกระบวนทัศน์เดียวภายในภาษา ฉันใช้ XOR ความปลอดภัยชนิดคงที่ไม่ได้

2
btw มันสะกดว่า 'Go' ไม่ใช่ 'GO' อย่างที่คุณเห็นใน golang.org และเป็นกรณี ๆ ไป :-)
poolie

1

คำตอบ:


79

คำตอบนี้คุณจะพบได้ที่นี่: http://golang.org/doc/faq#generics

ทำไม Go ถึงไม่มีประเภททั่วไป?

อาจมีการเพิ่ม Generics ในบางจุด เราไม่รู้สึกเร่งด่วนสำหรับพวกเขาแม้ว่าเราจะเข้าใจโปรแกรมเมอร์บางคน

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

นี่ยังคงเป็นปัญหาที่เปิดอยู่


14
@amoebe "อินเทอร์เฟซว่าง" สะกดinterface{}เป็นประเภทอินเทอร์เฟซพื้นฐานที่สุดและทุกวัตถุมีให้ หากคุณสร้างตู้คอนเทนเนอร์ไว้มันสามารถรับวัตถุใด ๆ (ที่ไม่ใช่ดั้งเดิม) ได้ ดังนั้นจึงคล้ายกับคอนเทนเนอร์ที่ถือObjectsใน Java
poolie

4
@YinWang Generics ไม่ใช่เรื่องง่ายในสภาพแวดล้อมที่อนุมานประเภท ที่สำคัญกว่า; อินเทอร์เฟซ {} ไม่เทียบเท่ากับ void * พอยน์เตอร์ใน C. การเปรียบเทียบที่ดีกว่าจะเป็นประเภท ID ของ System.Object หรือ Objective-C ของ C # ข้อมูลประเภทจะถูกเก็บรักษาไว้และสามารถ "หล่อ" (ยืนยันตามความเป็นจริง) กลับไปเป็นชนิดคอนกรีตได้ รับรายละเอียดที่ชัดเจนที่นี่: golang.org/ref/spec#Type_assertions
tbone

2
System.Object ของ @tbone C # (หรือ Java's Object per se) คือสิ่งที่ฉันหมายถึงโดย "ตัวชี้โมฆะของ C" (โดยไม่สนใจส่วนที่คุณไม่สามารถทำเลขคณิตตัวชี้ในภาษาเหล่านั้นได้) นี่คือจุดที่ข้อมูลประเภทคงที่สูญหายไป การแคสต์จะช่วยได้ไม่มากเพราะคุณจะได้รับข้อผิดพลาดรันไทม์
เอียน

1
เทมเพลตของ @ChristopherPfohl D ดูเหมือนจะมีค่าใช้จ่ายในการคอมไพล์น้อยกว่าเล็กน้อยและโดยปกติคุณจะไม่สร้างโค้ดด้วยเทมเพลตมากกว่าที่คุณทำตามปกติ (ในความเป็นจริงคุณสามารถทำได้ด้วยโค้ดน้อยกว่าขึ้นอยู่กับสถานการณ์)
คิวบิก

3
@ChristopherPfohl ฉันคิดว่าเฉพาะ Java generics เท่านั้นที่มีปัญหาการชกมวย / unboxing สำหรับประเภทดั้งเดิม? reified ทั่วไปของ C # ไม่มีปัญหา
ca9163d9

33

ไป 2

มีการออกแบบร่างยาชื่อสามัญที่https://blog.golang.org/go2draft

ไปที่ 1

Russ Cox หนึ่งในทหารผ่านศึก Go เขียนบล็อกโพสต์ชื่อ The Generic Dilemmaซึ่งเขาถาม

…คุณต้องการโปรแกรมเมอร์ที่ช้าคอมไพเลอร์ช้าและไบนารีที่ป่องหรือเวลาดำเนินการช้าหรือไม่?

โปรแกรมเมอร์ที่ทำงานช้าซึ่งเป็นผลมาจากการไม่มี generics คอมไพเลอร์ที่ช้าเกิดจาก C ++ เหมือนกับ generics และเวลาในการดำเนินการที่ช้าเกิดจากวิธี Boxing-unboxing ที่ Java ใช้

ความเป็นไปได้ที่สี่ที่ไม่ได้กล่าวถึงในบล็อกคือเส้นทาง C # สร้างรหัสพิเศษเช่นใน C ++ แต่ในขณะรันไทม์เมื่อจำเป็น ฉันชอบมันมาก แต่ Go แตกต่างจาก C # มากดังนั้นสิ่งนี้อาจใช้ไม่ได้เลย ...

ฉันควรพูดถึงว่าการใช้ Java 1.4 ที่เป็นที่นิยมเช่นเทคนิคการเขียนโปรแกรมทั่วไปในการเดินทาง ซึ่งจะต้องinterface{}ทนทุกข์ทรมานจากปัญหาเดียวกันกับการแกะกล่องมวย (เพราะนั่นคือสิ่งที่เรากำลังทำอยู่) นอกเหนือจากการสูญเสียความปลอดภัยของประเภทเวลาคอมไพล์ สำหรับประเภทขนาดเล็ก (เช่น ints) Go จะปรับinterface{}ประเภทให้เหมาะสมเพื่อให้รายการของ int ที่ส่งไปยังอินเทอร์เฟซ {} ใช้พื้นที่หน่วยความจำที่ต่อเนื่องกันและใช้พื้นที่เพียงสองเท่าของ ints ปกติ ยังคงมีค่าใช้จ่ายในการตรวจสอบรันไทม์ในขณะที่แคสต์จากinterface{}แม้ว่า ข้อมูลอ้างอิง .

โปรเจ็กต์ทั้งหมดที่เพิ่มการรองรับทั่วไป (มีหลายโปรเจ็กต์และทั้งหมดนั้นน่าสนใจ) ไปที่เส้นทาง C ++ ของการสร้างรหัสเวลาคอมไพล์


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

1
มันถูกกล่าวถึงว่าประเภทขนาดเล็ก (เช่น int) ที่เก็บอยู่ใน[]interface{}การใช้งาน 2 []intเท่าแรมเป็น ในขณะที่เป็นจริงแม้แต่ประเภทที่เล็กกว่า (เช่นไบต์) ก็ใช้ RAM ได้ถึง 16 เท่าเช่น[]byteกัน
BMiner

ไม่มีปัญหากับวิธี C ++ หากโปรแกรมเมอร์เลือกที่จะเขียนโค้ดเทมเพลตประโยชน์ของการทำเช่นนั้นจะต้องท่วมท้นค่าใช้จ่ายในการคอมไพล์ช้า มิฉะนั้นเขาก็สามารถทำแบบเก่าได้
John Z. Li

ภาวะที่กลืนไม่เข้าคายไม่ออกคือการเลือกแนวทางใด หากคุณแก้ไขภาวะที่กลืนไม่เข้าคายไม่ออกโดยใช้วิธี C ++ ปัญหาจะได้รับการแก้ไข
user7610

9

แม้ว่า generics จะไม่ได้อยู่ในตัวในขณะนี้ แต่ก็มีการนำ generics for go ไปใช้ภายนอกหลายอย่างซึ่งใช้ความคิดเห็นร่วมกับยูทิลิตี้ขนาดเล็กที่สร้างโค้ด

นี่คือหนึ่งในการใช้งานดังกล่าว: http://clipperhouse.github.io/gen/


1

จริงตามโพสต์นี้ :

หลายคนสรุป (ไม่ถูกต้อง) ว่าตำแหน่งของทีม Go คือ“ Go will never have generics” ในทางตรงกันข้ามเราเข้าใจว่ายาชื่อสามัญที่มีศักยภาพมีทั้งเพื่อให้ Go มีความยืดหยุ่นและมีประสิทธิภาพมากขึ้นและทำให้ Go มีความซับซ้อนมากขึ้น หากเราจะเพิ่มชื่อสามัญเราต้องการทำในแบบที่ได้รับความยืดหยุ่นและพลังมากที่สุดโดยมีความซับซ้อนที่เพิ่มเข้ามาให้น้อยที่สุด


0

polymorphism Parametric (generics)คือภายใต้การพิจารณาสำหรับการไป 2

แนวทางนี้จะแนะนำแนวคิดของสัญญาที่สามารถใช้เพื่อแสดงข้อ จำกัด เกี่ยวกับพารามิเตอร์ประเภท:

contract Addable(a T) {
  a + a // Could be += also
}

จากนั้นสามารถใช้สัญญาดังกล่าวได้:

func Sum(type T Addable)(l []T) (total T) {
  for _, e := range l {
    total += e
  }
  return total
}

นี่คือข้อเสนอในขั้นตอนนี้


filter(predicate, list)ฟังก์ชันของคุณสามารถใช้งานได้ด้วยพารามิเตอร์ type ดังนี้:

func Filter(type T)(f func(T) bool, l []T) (result []T) {
  for _, e := range l {
    if f(e) {
      result = append(result, e)
    }
  }
  return result
}

ในกรณีนี้ไม่มีความจำเป็นที่จะต้อง Tจำกัด


1
หากคุณกำลังอ่านคำตอบในวันนี้โปรดทราบว่ามีการยกเลิกสัญญาจากร่างข้อเสนอgo.googlesource.com/proposal/+/refs/heads/master/design/…
jub0bs

การวิจัยที่ดีและตัวอย่างรหัส +1
Tom J

0

หากต้องการเพิ่มและอัปเดตคำตอบที่ยอดเยี่ยมโดย @Vinzenz และ @ user7610

แม้ว่าจะยังห่างไกลจากสิ่งที่แน่นอน แต่หลังจากผ่านไปกว่าทศวรรษของการทำงานดูเหมือนว่าการออกแบบสำหรับพหุนามพาราเมตริกสิ่งที่เรียกขาน แต่เรียกว่ายาชื่อสามัญที่ทำให้เข้าใจผิดกำลังจะมาในปีหน้าหรือสองปี มันเป็นปัญหาที่ยากมากในการค้นหาการออกแบบที่ใช้งานได้กับภาษาที่มีอยู่และให้ความรู้สึกราวกับว่ามันเป็นของ แต่เอียนเทย์เลอร์ได้ลงทุนพลังงานจำนวนมหาศาลเข้าไปในปัญหาและดูเหมือนว่าตอนนี้คำตอบจะมาถึงแล้ว ดูhttps://evrone.com/rob-pike-interview

"พารามิเตอร์ประเภท - การออกแบบแบบร่าง" รองรับการใช้พารามิเตอร์ประเภทที่คุณสามารถอ่านฟังก์ชันที่จัดการพารามิเตอร์ขาเข้าโดยไม่ขึ้นอยู่กับประเภทที่ระบุในการประกาศฟังก์ชัน ดูhttps://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md

ตัวอย่างเช่นPrintSliceฟังก์ชันรับส่วนของจำนวนเต็มหรือสตริงและพิมพ์ออกมา ดูhttps://www.jetbrains.com/help/go/how-to-use-type-parameters-for-generic-programming.html

package main

import "fmt"

func PrintSlice(type T)(s []T) {
    for _, v := range s {

        fmt.Print(v)
    }
}

func main() {
    PrintSlice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9})
    PrintSlice([]string{"a", "b", "c", "d"})
}

คุณสามารถทดสอบตัวอย่างนี้ที่นี่https://go2goplay.golang.org/p/I09qwKNjxoq สนามเด็กเล่นนี้ใช้งานได้เหมือนกับ Go playground ทั่วไป แต่รองรับรหัสทั่วไป ดูhttps://blog.golang.org/generics-next-step

โดยทั่วไปแล้วพหุนามแบบพาราเมตริกหมายถึง 'ฟังก์ชันหรือโครงสร้างข้อมูลนี้ทำงานได้เหมือนกันกับทุกประเภท "นั่นคือสิ่งที่เราเรียกว่า generics เช่นความยาวของอาร์เรย์ไม่ได้ขึ้นอยู่กับสิ่งที่อยู่ในอาร์เรย์ดูhttps://news.ycombinator.com / item? id = 23560798 .

ที่เก่าแก่ที่สุดยาชื่อสามัญที่อาจจะเพิ่มไปจะเป็นไป 1.17 การเปิดตัวที่กำหนดไว้ในเดือนสิงหาคม 2021 ดูhttps://blog.golang.org/generics-next-step

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.