เป็นความคิดที่ดีหรือไม่ที่จะส่งคืนชนิดข้อมูลที่แตกต่างจากฟังก์ชันเดียวในภาษาที่พิมพ์แบบไดนามิกหรือไม่?


65

ภาษาหลักของฉันถูกพิมพ์แบบคงที่ (Java) ใน Java คุณต้องคืนค่าประเภทเดียวจากทุกวิธี ตัวอย่างเช่นคุณไม่สามารถมีวิธีการที่มีเงื่อนไขส่งกลับหรือเงื่อนไขส่งกลับString Integerตัวอย่างเช่นใน JavaScript สิ่งนี้เป็นไปได้มาก

ในภาษาที่พิมพ์แบบสแตติกฉันเข้าใจว่าทำไมนี่เป็นความคิดที่ไม่ดี ถ้าทุกวิธีส่งกลับObject(ผู้ปกครองทั่วไปทุกคลาสสืบทอดจาก) ดังนั้นคุณและคอมไพเลอร์ก็ไม่รู้ว่าคุณกำลังทำอะไรอยู่ คุณจะต้องค้นพบข้อผิดพลาดทั้งหมดของคุณในเวลาทำงาน

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


แก้ไข : ฉันจะลบตัวอย่างของฉัน (จนกว่าฉันจะนึกได้ดีกว่า) ฉันคิดว่ามันเป็นแรงผลักดันให้ผู้คนตอบกลับไปยังจุดที่ฉันไม่พยายามทำ


ทำไมไม่โยนข้อยกเว้นในกรณีข้อผิดพลาด?
TrueWill

1
@ TrueWill ฉันจะพูดเรื่องนั้นในประโยคถัดไป
Daniel Kaplan

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

3
ไม่ได้กล่าวถึง แต่มีหลายตัวอย่าง (แม้ใน Java Generics) ของฟังก์ชั่นที่รับคืนประเภทเป็นพารามิเตอร์ เช่นใน Common LISP เรามี(coerce var 'string)อัตราผลตอบแทนstringหรือ(concatenate 'string this that the-other-thing)เช่นเดียวกัน ฉันเขียนสิ่งต่าง ๆ เช่นThingLoader.getThingById (Class<extends FindableThing> klass, long id)กัน และที่นั่นฉันอาจส่งคืนบางสิ่งบางอย่างที่ subclasses ที่คุณขอ: loader.getThingById (SubclassA.class, 14)อาจส่งคืนสิ่งSubclassBที่ขยายออกไปSubclassA...
BRPocock

1
ภาษาพิมพ์แบบไดนามิกเป็นเช่นช้อนในภาพยนตร์เรื่องเดอะเมทริกซ์ อย่าพยายามที่จะกำหนดช้อนเป็นสตริงหรือจำนวน นั่นคงเป็นไปไม่ได้ แต่ ... พยายามเข้าใจความจริงแทน ว่าไม่มีช้อน
Reactgular

คำตอบ:


42

ตรงกันข้ามกับคำตอบอื่น ๆ มีหลายกรณีที่ยอมรับประเภทที่แตกต่างกันกลับได้

ตัวอย่างที่ 1

sum(2, 3)  int
sum(2.1, 3.7)  float

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

var sum = function (a, b) {
    return a + b;
};

ฟังก์ชั่นเดียวกันชนิดของค่าตอบแทน

ตัวอย่างที่ 2

ลองนึกภาพคุณจะได้รับการตอบสนองจากองค์ประกอบ OpenID / OAuth ผู้ให้บริการ OpenID / OAuth บางรายอาจมีข้อมูลเพิ่มเติมเช่นอายุของบุคคล

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Facebook',
//     name: {
//         firstName: 'Hello',
//         secondName: 'World',
//     },
//     email: 'hello.world@example.com',
//     age: 27
// }

คนอื่นจะมีขั้นต่ำมันจะเป็นที่อยู่อีเมลหรือนามแฝง

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Google',
//     email: 'hello.world@example.com'
// }

อีกครั้งฟังก์ชั่นเดียวกันผลลัพธ์ที่แตกต่าง

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

var user = authProvider.findCurrent();
if (user.age || 0 >= 16) {
    // The person can stand mature language.
    allowShowingContent();
} else if (user.age) {
    // OpenID/OAuth gave the age, but the person appears too young to see the content.
    showParentalAdvisoryRequestedMessage();
} else {
    // OpenID/OAuth won't tell the age of the person. Ask the user himself.
    askForAge();
}

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

var age;
if (['Facebook', 'Yahoo', 'Blogger', 'LiveJournal'].contains(user.provider)) {
    age = user.age;
}

5
"ในภาษาที่พิมพ์แบบคงที่สิ่งนี้เกี่ยวข้องกับการโอเวอร์โหลด" ฉันคิดว่าคุณหมายถึง "ในบางภาษาที่พิมพ์แบบคงที่" :) ภาษาที่พิมพ์แบบสแตติกดีไม่จำเป็นต้องมีการโหลดมากเกินไปสำหรับบางอย่างเช่นsumตัวอย่างของคุณ
Andres F.

17
สำหรับตัวอย่างที่เฉพาะเจาะจงของคุณให้พิจารณา sum :: Num a => [a] -> aHaskell: คุณสามารถรวมรายการสิ่งใดก็ได้ที่เป็นตัวเลข ซึ่งแตกต่างจากจาวาสคริปต์หากคุณพยายามที่จะรวมสิ่งที่ไม่ใช่ตัวเลขข้อผิดพลาดจะถูกรวบรวมในเวลารวบรวม
Andres F.

3
@MainMa ใน Scala Iterator[A]มีเมธอดdef sum[B >: A](implicit num: Numeric[B]): Bซึ่งอนุญาตให้รวมหมายเลขชนิดใด ๆ อีกครั้งและตรวจสอบ ณ เวลารวบรวม
Petr Pudlák

3
@Bakuriu แน่นอนว่าคุณสามารถเขียนฟังก์ชั่นดังกล่าวได้แม้ว่าคุณจะต้องโอเวอร์โหลด+สตริงมาก (โดยการนำไปใช้Numซึ่งเป็นความคิดที่น่ากลัว แต่ฉลาดนักออกแบบ แต่ถูกกฎหมาย) หรือคิดค้นโอเปอเรเตอร์ / ฟังก์ชั่นอื่น ๆ ตามลำดับ Haskell มี ad-hoc polymorphism ผ่าน class classes รายการที่มีทั้งจำนวนเต็มและสตริงนั้นยากกว่ามาก (อาจเป็นไปไม่ได้หากไม่มีภาษา) แต่เป็นเรื่องที่แตกต่าง

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

31

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

อย่างไรก็ตามในหลายกรณีมีสัมผัสและเหตุผลที่ชนิดถูกส่งคืนและผลคล้ายกับ overloading หรือ parametric polymorphism ในระบบชนิดสแตติก ในคำอื่น ๆ ประเภทผลที่ได้เป็นที่คาดการณ์มันเป็นเพียงแค่ไม่ได้ค่อนข้างง่ายที่จะแสดง

แต่โปรดทราบว่าอาจมีเหตุผลอื่น ๆ ที่ฟังก์ชั่นที่เฉพาะเจาะจงได้รับการออกแบบไม่ดี: ตัวอย่างเช่นsumฟังก์ชั่นกลับเท็จในการป้อนข้อมูลที่ไม่ถูกต้องเป็นความคิดที่ไม่ดีเป็นหลักเพราะค่าตอบแทนนั้นไม่มีประโยชน์และผิดพลาด


40
สองเซ็นต์ของฉัน: ฉันเกลียดเมื่อมันเกิดขึ้น ฉันเคยเห็นไลบรารี JS ที่คืนค่าว่างเมื่อไม่มีผลลัพธ์วัตถุในผลลัพธ์เดียวและอาร์เรย์ในผลลัพธ์สองรายการขึ้นไป ดังนั้นแทนที่จะแค่วนลูปผ่านอาร์เรย์คุณจะถูกบังคับให้ตรวจสอบว่ามันว่างเปล่าหรือไม่และถ้าไม่ใช่ก็คือถ้าเป็นอาร์เรย์และทำเช่นนั้นทำอย่างอื่นหรือทำอย่างอื่นแทน โดยเฉพาะอย่างยิ่งในความหมายปกตินักพัฒนาสติใด ๆ ที่เพิ่งเพิ่ม Object ไปยัง Array และรีไซเคิลตรรกะโปรแกรมจากการจัดการอาร์เรย์
phyrfox

10
@ อิซกะตะ: ฉันไม่คิดว่าNaNเป็น "ความแตกต่าง" เลย NaN เป็นอินพุตที่ถูกต้องสำหรับการคำนวณจุดลอยตัวใด ๆ คุณสามารถทำอะไรก็ได้NaNที่คุณสามารถทำได้ด้วยหมายเลขจริง จริงผลลัพธ์สุดท้ายของการคำนวณดังกล่าวอาจไม่เป็นประโยชน์กับคุณมากนัก แต่จะไม่นำไปสู่ข้อผิดพลาด runtime แปลก ๆ และคุณไม่จำเป็นต้องตรวจสอบมันตลอดเวลา - คุณสามารถตรวจสอบได้ครั้งเดียวเมื่อสิ้นสุด ชุดการคำนวณ น่านไม่ได้เป็นที่แตกต่างกันชนิดก็แค่พิเศษคุ้มค่า , ประเภทเช่นวัตถุ Null
Aaronaught

5
@Anaught ในทางกลับกันNaNเช่นวัตถุ null มีแนวโน้มที่จะซ่อนเมื่อเกิดข้อผิดพลาดเฉพาะสำหรับพวกเขาที่จะปรากฏขึ้นในภายหลัง ตัวอย่างเช่นโปรแกรมของคุณมีแนวโน้มที่จะระเบิดขึ้นถ้าNaNหลุดเข้าสู่วงแบบมีเงื่อนไข
Izkata

2
@Izkata: NaN และ null มีพฤติกรรมที่แตกต่างกันโดยสิ้นเชิง การอ้างอิงแบบ null จะระเบิดทันทีที่เข้าถึง NaN จะแพร่กระจาย สาเหตุหลังเกิดขึ้นชัดเจนจึงช่วยให้คุณสามารถเขียนนิพจน์ทางคณิตศาสตร์โดยไม่ต้องตรวจสอบผลลัพธ์ของนิพจน์ย่อยทั้งหมด โดยส่วนตัวแล้วฉันเห็นว่าโมฆะเป็นอันตรายมากกว่าเพราะ NaN แสดงแนวคิดเชิงตัวเลขที่เหมาะสมซึ่งคุณไม่สามารถหลีกหนีได้หากปราศจากและในทางคณิตศาสตร์การเผยแพร่เป็นพฤติกรรมที่ถูกต้อง
Phoshi

4
ฉันไม่รู้ว่าทำไมสิ่งนี้จึงกลายเป็นอาร์กิวเมนต์ "เป็นโมฆะกับทุกอย่าง" ฉันแค่ชี้ให้เห็นว่าNaNค่าคือ (a) ไม่ใช่ประเภทผลตอบแทนที่แตกต่างกันจริง ๆ และ (b) มีความหมายลอยตัวที่กำหนดไว้อย่างดีและดังนั้นจึงไม่ได้มีคุณสมบัติเป็นอะนาล็อกกับค่าตอบแทนที่พิมพ์แปรผัน มันไม่ได้ทั้งหมดที่แตกต่างจากศูนย์ในเลขคณิตเลขจำนวนเต็มจริง ๆ ; หากศูนย์ "บังเอิญ" ลดลงในการคำนวณของคุณแล้วคุณมักจะจบลงด้วยศูนย์ทั้งผลหรือข้อผิดพลาดหารด้วยศูนย์ ค่า "อนันต์" กำหนดโดยจุดลอยตัวของ IEEE เช่นกันหรือไม่?
Aaronaught

26

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

เมื่อกลับมาประเภทที่แตกต่างกันทำให้รู้สึก

ตัวอย่างเช่นวิธีนี้เหมาะสม:

def load_file(file): 
    if something: 
       return ['a ', 'list', 'of', 'strings'] 
    return open(file, 'r')

เพราะทั้งไฟล์และรายการของสตริงคือ (ในไพ ธ อน) iterables ที่ส่งคืนสตริง ประเภทที่แตกต่างกันมาก API เดียวกัน (ยกเว้นบางคนพยายามเรียกวิธีการไฟล์ในรายการ แต่นี่เป็นเรื่องที่แตกต่างกัน)

คุณสามารถส่งคืนแบบมีเงื่อนไขlistหรือtuple( tupleเป็นรายการที่ไม่เปลี่ยนรูปแบบในไพ ธ อน)

เป็นทางการแม้ทำ:

def do_something():
    if ...: 
        return None
    return something_else

หรือ:

function do_something(){
   if (...) return null; 
   return sth;
}

ส่งคืนชนิดที่แตกต่างกันเนื่องจากทั้งไพ ธ อนNoneและจาวาสคริปต์nullเป็นประเภทของตนเอง

กรณีการใช้งานทั้งหมดเหล่านี้จะมีคู่ของพวกเขาในภาษาคงที่ฟังก์ชั่นก็จะกลับมาอินเตอร์เฟซที่เหมาะสม

เมื่อส่งคืนออบเจ็กต์ที่มี API แตกต่างกันตามเงื่อนไขเป็นแนวคิดที่ดี

สำหรับว่าการส่งคืน API ที่แตกต่างกันนั้นเป็นความคิดที่ดีหรือไม่ IMO ส่วนใหญ่จะไม่สมเหตุสมผล ตัวอย่างที่สมเหตุสมผลที่นึกถึงคือสิ่งที่ใกล้เคียงกับ@MainMa ที่พูดว่า : เมื่อ API ของคุณสามารถให้รายละเอียดที่แตกต่างกันจำนวนมากอาจทำให้รู้สึกถึงการกลับรายละเอียดที่มากขึ้นเมื่อมี


4
คำตอบที่ดี. เป็นที่น่าสังเกตว่า Java / การพิมพ์แบบคงที่เทียบเท่าคือการให้ฟังก์ชั่นส่งคืนอินเตอร์เฟซมากกว่าประเภทคอนกรีตที่เฉพาะเจาะจงด้วยอินเตอร์เฟซที่เป็นตัวแทนของ API / นามธรรม
mikera

1
ฉันคิดว่าคุณกำลัง nitpicking ใน Python รายการและ tuple อาจเป็น "ประเภท" ที่แตกต่างกัน แต่พวกมันมี "เป็ดประเภท" ที่เหมือนกันนั่นคือสนับสนุนการดำเนินงานชุดเดียวกัน และกรณีเช่นนี้เป็นเหตุผลที่ทำให้มีการเปิดตัวการพิมพ์เป็ด คุณสามารถยาว x = getInt () ใน Java ได้เช่นกัน
Kaerber

“ การส่งคืนชนิดที่แตกต่าง” หมายถึง “ การส่งคืนวัตถุที่มี API แตกต่างกัน” (บางภาษาสร้างวิธีที่วัตถุถูกสร้างขึ้นเป็นส่วนพื้นฐานของ API แต่บางอย่างก็ไม่เป็นเช่นนั้นเพราะเป็นระบบการแสดงความหมาย) ในตัวอย่างรายการ / ไฟล์do_fileของคุณ
Gilles

1
ในตัวอย่างของ Python นั้นคุณสามารถเรียกiter()ทั้งรายการและไฟล์เพื่อให้แน่ใจว่าผลลัพธ์สามารถใช้เป็นตัววนซ้ำได้ทั้งสองกรณี
RemcoGerlich

1
การกลับมาNone or somethingเป็นนักฆ่าเพื่อเพิ่มประสิทธิภาพการทำงานโดยเครื่องมือเช่น PyPy, Numba, Pyston และอื่น ๆ นี่คือหนึ่งใน Python-ism ที่ทำให้ python ไม่เร็วมาก
Matt

9

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

เงื่อนไขข้อผิดพลาดใช้ชนิดของเคสที่สมเหตุสมผลและรูปแบบ null (ทุกอย่างต้องเป็นรูปแบบ) ในภาษาที่พิมพ์แบบคงที่จะทำสิ่งชนิดเดียวกัน สายงานของคุณส่งกลับหรือจะส่งกลับobjectnull

แต่มันเป็นขั้นตอนสั้น ๆ เพื่อบอกว่า "ฉันจะใช้วิธีนี้ในการสร้างรูปแบบโรงงาน " และกลับอย่างใดอย่างหนึ่งfooหรือbarหรือbazขึ้นอยู่กับอารมณ์ของฟังก์ชัน แก้จุดบกพร่องนี้จะกลายเป็นฝันร้ายเมื่อโทรฯ คาดว่าแต่ได้รับfoobar

ดังนั้นฉันไม่คิดว่าคุณจะถูกปิดใจ คุณระมัดระวังในการใช้คุณสมบัติของภาษาอย่างเหมาะสม

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


6

การใช้ Generics ใน Java ช่วยให้คุณกลับมาเป็นประเภทที่แตกต่างกันในขณะที่ยังคงรักษาความปลอดภัยประเภทคงที่ คุณเพียงแค่ระบุประเภทที่คุณต้องการกลับมาในพารามิเตอร์ประเภททั่วไปของการเรียกใช้ฟังก์ชั่น

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

หากคุณต้องการทราบว่าสถานการณ์จำลองการส่งคืนแบบไดนามิกอาจทำงานเมื่อคุณคุ้นเคยกับการทำงานในภาษาที่พิมพ์แบบคงที่ให้ลองพิจารณาdynamicคำหลักใน C # Rob Conery สามารถเขียน Mapper เชิงวัตถุในโค้ด 400 บรรทัดได้โดยใช้dynamicคีย์เวิร์ด

แน่นอนว่าสิ่งที่ทำdynamicจริงๆคือห่อobjectตัวแปรด้วยความปลอดภัยของรันไทม์บางประเภท


4

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


4

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

นี่คือส่วนที่ไม่เหมาะสมของสิ่งที่คุณเสนอ หากฉันไม่รู้ว่าจะคืนค่าประเภทใดฉันก็ไม่สามารถใช้ค่าส่งคืนได้ทันที ฉันต้อง "ค้นพบ" บางสิ่งเกี่ยวกับมัน

total = sum_of_array([20, 30, 'q', 50])
if (type_of(total) == Boolean) {
  display_error(...)
} else {
  record_number(total)
}

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

total = sum_of_array([20, 30, 'q', 50])
display_number(total)

เนื่องจากบรรทัดแรกกลับมาเรียบร้อยแล้วฉันคิดว่าtotalจริง ๆ แล้วมีผลรวมของอาร์เรย์ หากไม่กลับมาสำเร็จเราจะข้ามไปยังหน้าอื่นของโปรแกรม

ลองใช้อีกตัวอย่างหนึ่งที่ไม่ได้เกี่ยวกับการเผยแพร่ข้อผิดพลาดเท่านั้น บางที sum_of_array พยายามที่จะฉลาดและส่งคืนสตริงที่มนุษย์อ่านได้ในบางกรณีเช่น "นั่นคือการรวมกันของตู้เก็บของฉัน!" ถ้าเพียง แต่ถ้าอาร์เรย์คือ [11,7,19] ฉันมีปัญหาในการคิดตัวอย่างที่ดี อย่างไรก็ตามปัญหาเดียวกันใช้ คุณต้องตรวจสอบค่าส่งคืนก่อนที่คุณจะทำอะไรกับมันได้:

total = sum_of_array([20, 30, 40, 50])
if (type_of(total) == String) {
  write_message(total)
} else {
  record_number(total)
}

คุณอาจยืนยันว่ามันจะมีประโยชน์สำหรับฟังก์ชั่นเพื่อกลับจำนวนเต็มหรือลอยเช่น:

sum_of_array(20, 30, 40) -> int
sum_of_array(23.45, 45.67, 67.789044) -> float

แต่ผลลัพธ์เหล่านั้นจะไม่แตกต่างกันเท่าที่คุณกังวล คุณจะปฏิบัติต่อพวกเขาทั้งสองเป็นตัวเลขและนั่นคือทั้งหมดที่คุณสนใจ ดังนั้น sum_of_array จะส่งกลับชนิดของตัวเลข นี่คือสิ่งที่เกี่ยวกับความหลากหลาย

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


ขออภัยฉันทำให้คุณหลงผิดด้วยตัวอย่างที่ไม่ดีของฉัน ลืมฉันพูดถึงมันในตอนแรก ย่อหน้าแรกของคุณอยู่ในจุด ฉันยังชอบตัวอย่างที่สองของคุณ
Daniel Kaplan

4

จริงๆแล้วมันไม่แปลกเลยที่จะคืนค่าประเภทที่แตกต่างกันแม้จะเป็นภาษาที่พิมพ์แบบคงที่ นั่นเป็นเหตุผลที่เรามีประเภทสหภาพตัวอย่างเช่น

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

ในหลายภาษาเงื่อนไขข้อผิดพลาดจะถูกจำลองเป็นรูทีนย่อยที่ส่งคืนชนิดผลลัพธ์หรือชนิดข้อผิดพลาด ตัวอย่างเช่นใน Scala:

def transferMoney(amount: Decimal): Either[String, Decimal]

นี่เป็นตัวอย่างที่โง่แน่นอน ชนิดส่งคืนหมายถึง "ส่งคืนสตริงหรือทศนิยม" ตามแบบแผนประเภทซ้ายเป็นประเภทข้อผิดพลาด (ในกรณีนี้สตริงที่มีข้อความแสดงข้อผิดพลาด) และประเภทที่ถูกต้องคือประเภทผลลัพธ์

สิ่งนี้คล้ายกับข้อยกเว้นยกเว้นข้อเท็จจริงที่ว่าข้อยกเว้นนั้นเป็นโครงสร้างการควบคุมการไหล GOTOพวกเขาเป็นในความเป็นจริงเทียบเท่าในอำนาจที่จะแสดงออก


วิธีการใน Java กลับข้อยกเว้นฟังดูแปลก ๆ " กลับยกเว้น " ... อืม
ริ้น

3
ข้อยกเว้นคือผลลัพธ์หนึ่งวิธีที่เป็นไปได้ของ Java อันที่จริงฉันลืมประเภทที่สี่: Unit(ไม่จำเป็นต้องใช้วิธีการใดเลยในการส่งคืน) จริงคุณไม่ได้ใช้returnคำหลักจริง ๆแต่เป็นผลลัพธ์ที่กลับมาจากวิธีการอย่างไรก็ตาม ด้วยข้อยกเว้นที่ตรวจสอบแล้วจะมีการพูดถึงอย่างชัดเจนในลายเซ็นประเภท
Jörg W Mittag

ฉันเห็น. จุดของคุณดูเหมือนจะมีข้อดี แต่วิธีที่นำเสนอไม่ได้ทำให้ดูน่าสนใจ ... ค่อนข้างตรงกันข้าม
gnat

4
ในหลายภาษาเงื่อนไขข้อผิดพลาดถูกสร้างแบบจำลองเป็นรูทีนย่อยที่ส่งคืนชนิดผลลัพธ์หรือชนิดข้อผิดพลาด ยกเว้นเป็นความคิดที่คล้ายกันยกเว้นพวกเขานอกจากนี้ยังมีการควบคุมการไหลของโครงสร้าง (ความเท่าเทียมกันในการแสดงพลังที่จะGOTOจริง)
Jörg W Mittag

4

ยังไม่มีคำตอบที่กล่าวถึงหลักการของ SOLID โดยเฉพาะอย่างยิ่งคุณควรปฏิบัติตามหลักการการทดแทน Liskov ว่าคลาสใดที่ได้รับประเภทอื่นนอกเหนือจากประเภทที่คาดไว้ยังคงสามารถทำงานกับสิ่งที่พวกเขาได้รับโดยไม่ต้องทำอะไรเพื่อทดสอบว่าจะส่งคืนประเภทใด

ดังนั้นถ้าคุณโยนคุณสมบัติพิเศษบางอย่างลงบนวัตถุหรือห่อฟังก์ชั่นที่ส่งคืนด้วยมัณฑนากรบางชนิดที่ยังคงบรรลุสิ่งที่ฟังก์ชั่นดั้งเดิมตั้งใจจะทำให้สำเร็จคุณก็ดีตราบใดที่ไม่มีรหัสที่เรียกใช้ฟังก์ชันของคุณ พฤติกรรมในเส้นทางรหัสใด ๆ

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

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


3

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

function ensure_fields($consideration)
{
        $args = func_get_args();
        foreach ( $args as $a ) {
                if ( !is_string($a) ) {
                        return NULL;
                }
                if ( !isset($consideration[$a]) || $consideration[$a]=='' ) {
                        return FALSE;
                }
        }

        return TRUE;
}

ฟังก์ชันส่งคืนบูลีนโดยอัตโนมัติ แต่ส่งคืน NULL จากอินพุตที่ไม่ถูกต้อง โปรดทราบว่าตั้งแต่ PHP 5.3 ทุกฟังก์ชั่นภายใน PHP ในลักษณะนี้เช่นกัน นอกจากนี้ฟังก์ชัน PHP ภายในบางฟังก์ชันจะส่งกลับค่า FALSE หรือ INT จากอินพุตเล็กน้อยให้ดู:

strpos('Hello', 'e');  // Returns INT(1)
strpos('Hello', 'q');  // Returns BOOL(FALSE)

4
และนี่คือเหตุผลที่ฉันเกลียด PHP ก็เป็นหนึ่งในเหตุผลต่อไป
Aaronaught

1
การโหวตเพราะใครบางคนไม่ชอบภาษาที่ฉันพัฒนาอย่างมืออาชีพ?!? รอจนกว่าพวกเขาจะพบว่าฉันเป็นยิว! :)
dotancohen

3
อย่าคิดเสมอว่าคนที่แสดงความคิดเห็นและคนที่ downvote เป็นคนเดียวกัน
Aaronaught

1
ฉันชอบที่จะมีข้อยกเว้นแทนnullเพราะ (ก) มันล้มเหลวเสียงดังดังนั้นจึงมีแนวโน้มที่จะได้รับการแก้ไขในขั้นตอนการพัฒนา / การทดสอบ (b) มันง่ายต่อการจัดการเพราะฉันสามารถจับข้อยกเว้นที่หายาก / ไม่ได้ทั้งหมด แอปพลิเคชันทั้งหมดของฉัน (ในส่วนควบคุมด้านหน้าของฉัน) และเพียงแค่บันทึกมัน (ฉันมักจะส่งอีเมลไปที่ทีมงาน dev) ดังนั้นจึงสามารถแก้ไขได้ในภายหลัง และฉันเกลียดที่ห้องสมุด PHP มาตรฐานส่วนใหญ่ใช้วิธี "return null" - ทำให้โค้ด PHP ผิดพลาดมากขึ้นเว้นแต่คุณจะตรวจสอบทุกอย่างด้วยisset()ซึ่งเป็นภาระมากเกินไป
scriptin

1
นอกจากนี้หากคุณกลับมาnullผิดพลาดได้โปรดกรุณาทำให้ชัดเจนใน docblock (เช่น@return boolean|null) ดังนั้นถ้าฉันเจอรหัสของคุณสักวันฉันจะไม่ต้องตรวจสอบฟังก์ชั่น / วิธีร่างกาย
scriptin

3

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

Java 1.4

public static Boolean getBoolean(String property){
    return (Boolean) properties.getProperty(property);
}
public static Integer getInt(String property){
    return (Integer) properties.getProperty(property);
}

Java 1.5+

public static <T> getValue(String property, Class<T> clazz) throws WhateverCheckedException{
    return clazz.getConstructor(String.class).newInstance(properties.getProperty(property));
}
//the call will be
Boolean b = getValue("useProxy",Boolean.class);
Integer b = getValue("proxyPort",Integer.class);

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


downvote อีกตัวโดยไม่มีคำอธิบาย! ขอบคุณชุมชน SE!
ร้อน

2
มีความเห็นอกเห็นใจ +1 คุณควรลองเปิดคำตอบของคุณด้วยคำตอบที่ชัดเจนและตรงกว่าและงดเว้นจากการแสดงความคิดเห็นกับคำตอบที่แตกต่างกัน (ฉันจะให้ +1 อีกเนื่องจากฉันเห็นด้วยกับคุณ แต่ฉันมีเพียงหนึ่งที่จะให้.)
DougM

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

1
ฉันพยายามที่จะซิงโครไนซ์อีกเล็กน้อย: "แม้กระทั่งในภาษาที่พิมพ์แบบคงที่ Generics ถูกนำมาใช้เพื่อแก้ปัญหานี้มันชัดเจนว่าการเขียนฟังก์ชั่นที่คืนค่าประเภทต่าง ๆ ในภาษาไดนามิกไม่ใช่ความคิดที่ไม่ดี" ดีขึ้นแล้ว? ตรวจสอบคำตอบของ Robert Harvey หรือความคิดเห็นของ BRPocock และคุณจะรู้ว่าตัวอย่างของ Generics เกี่ยวข้องกับคำถามนี้
thermz

1
อย่ารู้สึกแย่ @thermz Downvotes มักพูดเกี่ยวกับผู้อ่านมากกว่าผู้เขียน
Karl Bielefeldt

2

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


1

Perl นี้ใช้มากเพราะสิ่งที่ฟังก์ชั่นไม่ขึ้นอยู่กับของบริบท ตัวอย่างเช่นฟังก์ชั่นอาจกลับอาร์เรย์ถ้าใช้ในบริบทรายการหรือความยาวของอาร์เรย์ถ้าใช้ในสถานที่ที่คาดว่าค่าสเกลาร์ จากบทช่วยสอนที่เป็นที่นิยมสำหรับ "perl context"ถ้าคุณทำ:

my @now = localtime();

จากนั้น @now เป็นตัวแปรอาร์เรย์ (นั่นคือสิ่งที่หมายถึง @) และจะมีอาร์เรย์เช่น (40, 51, 20, 9, 0, 109, 5, 8, 0)

หากคุณเรียกใช้ฟังก์ชันในลักษณะที่ผลลัพธ์จะต้องเป็นสเกลาร์โดยที่ ($ ตัวแปรเป็นสเกลาร์):

my $now = localtime();

จากนั้นมันจะทำสิ่งที่แตกต่างอย่างสิ้นเชิง: $ ตอนนี้จะเป็นเช่น "ศุกร์ 9 มกราคม 20:51:40 2009"

อีกตัวอย่างหนึ่งที่ฉันนึกได้คือการนำ REST API ไปใช้ซึ่งรูปแบบของสิ่งที่ส่งคืนนั้นขึ้นอยู่กับสิ่งที่ลูกค้าต้องการ เช่น HTML หรือ JSON หรือ XML แม้ว่าในทางเทคนิคแล้วจะเป็นสตรีมทั้งหมดของไบต์ความคิดก็คล้ายกัน


คุณอาจต้องการพูดถึงวิธีเฉพาะใน perl - wantarray ... และอาจเชื่อมโยงไปยังการสนทนาหากดีหรือไม่ดี - การใช้ wantarray พิจารณาเป็นอันตรายที่ PerlMonks

โดยบังเอิญฉันกำลังจัดการกับสิ่งนี้กับ Java Rest API ที่ฉันกำลังสร้าง ฉันทำให้เป็นไปได้สำหรับทรัพยากรหนึ่งคืน XML หรือ JSON Stringต่ำสุดประเภทส่วนร่วมในกรณีที่เป็น ตอนนี้ผู้คนกำลังขอเอกสารเกี่ยวกับประเภทการส่งคืน เครื่องมือ java สามารถสร้างมันขึ้นมาโดยอัตโนมัติถ้าฉันส่งคืนคลาส แต่เพราะฉันเลือกStringฉันไม่สามารถสร้างเอกสารโดยอัตโนมัติได้ ในการเข้าใจถึงปัญหาย้อนหลัง / คุณธรรมของเรื่องฉันหวังว่าฉันจะคืนหนึ่งประเภทต่อวิธี
Daniel Kaplan

อย่างเคร่งครัดจำนวนนี้เป็นรูปแบบของการบรรทุกเกินพิกัด กล่าวอีกนัยหนึ่งก็คือมีหลายฟังก์ชั่นภายนอกและขึ้นอยู่กับรายละเอียดของการโทรเลือกหนึ่งหรือรุ่นอื่น
เอียน

1

ในดินแดนไดนามิกมันคือทั้งหมดที่เกี่ยวกับการพิมพ์เป็ด สิ่งที่ต้องเปิดเผย / เปิดเผยต่อสาธารณะที่มีความรับผิดชอบมากที่สุดคือการห่อประเภทต่าง ๆ ที่อาจเป็นไปได้ใน wrapper ที่ให้พวกเขามีอินเตอร์เฟสเดียวกัน

function ThingyWrapper(thingy){ //a function constructor (class-like thingy)

    //thingy is effectively private and persistent for ThingyWrapper instances

    if(typeof thingy === 'array'){
        this.alertItems = function(){
            thingy.forEach(function(el){ alert(el); });
        }
    }
    else {
        this.alertItems = function(){
            for(var x in thingy){ alert(thingy[x]); }
        }
    }
}

function gimmeThingy(){
    var
        coinToss = Math.round( Math.random() ),//gives me 0 or 1
        arrayThingy = [1,2,3],
        objectThingy = { item1:1, item2:2, item3:3 }
    ;

    //0 dynamically evaluates to false in JS
    return new ThingyWrapper( coinToss ? arrayThingy : objectThingy );
}

gimmeThingy().alertItems(); //should be same every time except order of numbers - maybe

บางครั้งมันอาจจะเหมาะสมที่จะเซ่อประเภทต่าง ๆ โดยไม่มีเสื้อคลุมทั่วไป แต่โดยสุจริตใน 7ish ปีของการเขียน JS มันไม่ใช่สิ่งที่ฉันพบว่ามันสมเหตุสมผลหรือสะดวกในการทำบ่อยมาก ส่วนใหญ่เป็นสิ่งที่ฉันทำในบริบทของสภาพแวดล้อมปิดเช่นการตกแต่งภายในของวัตถุที่มีการรวมตัวกัน แต่มันไม่ใช่สิ่งที่ฉันทำบ่อยพอที่ตัวอย่างจะข้ามไปยังใจ

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

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