ภาษาที่พิมพ์แบบคงที่รองรับประเภทจุดตัดสำหรับค่าที่ส่งคืนของฟังก์ชันหรือไม่


17

หมายเหตุเริ่มต้น:

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

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

มีภาษาการเขียนโปรแกรมที่ได้รับความนิยมและคงที่หรือไม่ (เช่น Haskell, Java ทั่วไป, C #, F # และอื่น ๆ ) ที่รองรับประเภทจุดตัดสำหรับค่าส่งคืนฟังก์ชันหรือไม่ ถ้าเป็นเช่นนั้นจะทำอะไรและอย่างไร

(ถ้าฉันซื่อสัตย์ฉันชอบที่จะเห็นใครบางคนสาธิตวิธีการแยกประเภทในภาษาหลักเช่น C # หรือ Java)

ฉันจะให้ตัวอย่างรวดเร็วของประเภทจุดตัดที่อาจมีลักษณะโดยใช้บาง pseudocode คล้ายกับ C #:

interface IX { … }
interface IY { … }
interface IB { … }

class A : IX, IY { … }
class B : IX, IY, IB { … }

T fn()  where T : IX, IY
{
    return … ? new A()  
             : new B();
}

นั่นคือฟังก์ชั่นfnส่งกลับตัวอย่างของประเภทบางTซึ่งโทรเท่านั้นที่รู้ว่ามันใช้อินเตอร์เฟซและIX IY(นั่นคือแตกต่างจาก generics ผู้โทรไม่ได้เลือกชนิดที่เป็นรูปธรรมของT- ฟังก์ชั่นทำจากนี้ฉันจะสมมติว่าที่Tจริงแล้วไม่ใช่ประเภทสากล แต่เป็นประเภทที่มีอยู่)

PS:ฉันรู้ว่าหนึ่งก็สามารถกำหนดinterface IXY : IX, IYและเปลี่ยนประเภทการกลับมาของการfn IXYอย่างไรก็ตามนั่นไม่ใช่สิ่งเดียวกันเพราะบ่อยครั้งที่คุณไม่สามารถเชื่อมต่ออินเทอร์เฟซเพิ่มเติมIXYกับประเภทที่กำหนดไว้ก่อนหน้านี้Aซึ่งใช้IXและIYแยกกันเท่านั้น


เชิงอรรถ: ทรัพยากรบางอย่างเกี่ยวกับประเภทสี่แยก:

บทความวิกิพีเดีย"ประเภทระบบ"มีส่วนย่อยเกี่ยวกับประเภทสี่แยก

รายงานโดย Benjamin C. Pierce (1991), "Programming with Intersection Types, Union Type, Polymorphism"

David P. Cunningham (2005), "Intersection Types in practice"ซึ่งมีกรณีศึกษาเกี่ยวกับภาษา Forsythe ซึ่งถูกกล่าวถึงในบทความ Wikipedia

สแต็คโอเวอร์โฟลคำถาม"ยูเนี่ยนประเภทและประเภทการตัดกัน"ซึ่งได้คำตอบที่ดีหลายประการในหมู่พวกเขาอันนี้ซึ่งให้ตัวอย่างรหัสเทียมของประเภทการแยกที่คล้ายกับของฉันด้านบน


6
ความคลุมเครือนี้เป็นอย่างไร Tกำหนดประเภทแม้ว่ามันจะถูกกำหนดไว้ในการประกาศฟังก์ชั่นเป็น "บางประเภทที่ขยาย / นำไปใช้IXและIY" ความจริงที่ว่าเกิดขึ้นจริงค่าตอบแทนเป็นกรณีพิเศษที่ ( AหรือBตามลำดับ) ไม่ได้อะไรเป็นพิเศษที่นี่คุณสามารถเช่นเดียวกับดีบรรลุว่าด้วยการใช้แทนObject T
Joachim Sauer

1
Ruby อนุญาตให้คุณส่งคืนสิ่งที่คุณต้องการจากฟังก์ชัน เหมือนกันสำหรับภาษาไดนามิกอื่น ๆ
thorsten müller

ฉันอัพเดตคำตอบแล้ว @ โจอาคิม: ฉันรู้ว่าคำว่า "คลุมเครือ" ไม่ได้จับแนวคิดที่เป็นปัญหาอย่างถูกต้องดังนั้นตัวอย่างเพื่อชี้แจงความหมายที่ตั้งใจไว้
stakx

1
โฆษณา PS: ... ซึ่งเปลี่ยนคำถามของคุณเป็น "ภาษาใดที่อนุญาตให้ใช้งานประเภทTอินเตอร์เฟสเป็นอินเทอร์เฟซIเมื่อใช้วิธีการทั้งหมดของอินเทอร์เฟซ แต่ไม่ได้ประกาศอินเทอร์เฟซนั้น"
Jan Hudec

6
มันเป็นความผิดพลาดที่จะปิดคำถามนี้เพราะมีคำตอบที่ถูกต้องซึ่งเป็นประเภทยูเนี่ยน ยูเนี่ยนประเภทที่มีอยู่ในภาษาเช่น (พิมพ์ไม้) [ docs.racket-lang.org/ts-guide/]
Sam Tobin-Hochstadt

คำตอบ:


5

Scala มีสี่แยกประเภทที่สร้างขึ้นในภาษา:

trait IX {...}
trait IY {...}
trait IB {...}

class A() extends IX with IY {...}

class B() extends IX with IY with IB {...}

def fn(): IX with IY = if (...) new A() else new B()

สกาล่าตาม dotty จะมีประเภทแยกจริง แต่ไม่ใช่สำหรับสกาล่าปัจจุบัน / อดีต
Hongxu Chen

9

ในความเป็นจริงแล้วคำตอบที่ชัดเจนคือ: Java

ในขณะที่มันอาจทำให้คุณประหลาดใจที่ได้รู้ว่า Java รองรับประเภทจุดตัด ... จริง ๆ แล้วผ่านตัวดำเนินการชนิด "&" ที่ถูกผูกไว้ ตัวอย่างเช่น:

<T extends IX & IY> T f() { ... }

ดูลิงค์นี้เกี่ยวกับขอบเขตหลายประเภทใน Java และยังได้จาก Java API


มันจะใช้ได้ไหมถ้าคุณไม่รู้ชนิดของเวลาคอมไพล์? <T extends IX & IY> T f() { if(condition) return new A(); else return new B(); }คือหนึ่งสามารถเขียน และคุณจะเรียกฟังก์ชั่นในกรณีเช่นนี้ได้อย่างไร? ทั้ง A และ B ไม่สามารถปรากฏที่ไซต์การโทรได้เพราะคุณไม่รู้ว่าคุณจะได้รับอันไหน
Jan Hudec

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

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

8

คำถามเดิมขอให้ "ประเภทที่ไม่ชัดเจน" สำหรับคำตอบนั้นคือ:

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

อินเทอร์เฟซที่อนุมาน:

ดังนั้นโดยทั่วไปคุณต้องการให้ส่งคืนอินเทอร์เฟซIXYที่ได้มาIX และ IYแม้ว่าอินเทอร์เฟซนั้นไม่ได้ถูกประกาศในAหรือBอาจเป็นเพราะไม่ได้ประกาศเมื่อมีการกำหนดประเภทเหล่านั้น ในกรณีนั้น:

  • สิ่งใดก็ตามที่พิมพ์แบบไดนามิกจะเห็นได้ชัด
  • ฉันจำไม่ได้ว่าภาษากระแสหลักที่มีการพิมพ์แบบคงที่จะสามารถสร้างอินเทอร์เฟซได้ (เป็นยูเนี่ยนประเภทAและBหรือแยกประเภทของIXและIY) ตัวเอง
  • GOเพราะคลาสใช้อินเทอร์เฟซหากมีวิธีที่ถูกต้องโดยไม่ต้องประกาศ ดังนั้นคุณเพียงแค่ประกาศอินเทอร์เฟซที่ได้มาทั้งสองที่นั่น
  • เห็นได้ชัดว่าภาษาอื่นใดที่สามารถกำหนดประเภทให้ใช้อินเทอร์เฟซนอกเหนือจากคำจำกัดความของประเภทนั้น แต่ฉันไม่คิดว่าฉันจะจำอื่นได้นอกจาก GO
  • เป็นไปไม่ได้ในทุกประเภทที่จะต้องมีการใช้งานอินเทอร์เฟซในนิยามประเภทเอง อย่างไรก็ตามคุณสามารถหลีกเลี่ยงสิ่งเหล่านี้ได้โดยการกำหนด wrapper ที่ใช้สองอินเตอร์เฟสและมอบหมายวิธีการทั้งหมดให้กับวัตถุที่ถูกห่อ

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


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

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

ขอโทษฉันพลาดแน่นอน!
redjamjar

+1 สำหรับการกล่าวถึง Golang ซึ่งอาจเป็นตัวอย่างที่ดีที่สุดของภาษาทั่วไปที่อนุญาตสิ่งนี้แม้ว่าวิธีการทำจะเป็นสิ่งที่ไม่แน่นอน
จูลส์

3

ชนิดของGo Programming Languageมีสิ่งนี้ แต่สำหรับประเภทอินเตอร์เฟสเท่านั้น

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

ตัวอย่าง:

package intersection

type (
    // The first component type.
    A interface {
        foo() int
    }
    // The second component type.
    B interface {
        bar()
    }

    // The intersection type.
    Intersection interface {
        A
        B
    }
)

// Function accepting an intersection type
func frob(x Intersection) {
    // You can directly call methods defined by A or B on Intersection.
    x.foo()
    x.bar()

    // Conversions work too.
    var a A = x
    var b B = x
    a.foo()
    b.bar()
}

// Syntax for a function returning an intersection type:
// (using an inline type definition to be closer to your suggested syntax)
func frob2() interface { A; B } {
    // return something
}

3

คุณอาจจะสามารถทำสิ่งที่คุณต้องการโดยใช้ชนิดที่มีอยู่ที่ถูกผูกไว้ซึ่งสามารถเข้ารหัสในภาษาใด ๆ กับ generics และ polymorphism ที่ล้อมรอบเช่น C #

ประเภทที่ส่งคืนจะเป็นสิ่งที่ต้องการ (ในรหัส psuedo)

IAB = exists T. T where T : IA, IB

หรือใน C #:

interface IAB<IA, IB>
{
    R Apply<R>(IABFunc<R, IA, IB> f);
}

interface IABFunc<R, IA, IB>
{
    R Apply<T>(T t) where T : IA, IB;
}

class DefaultIAB<T, IA, IB> : IAB<IA, IB> where T : IA, IB 
{
    readonly T t;

    ...

    public R Apply<R>(IABFunc<R, IA, IB> f) {
        return f.Apply<T>(t);
    }
}

หมายเหตุ: ฉันยังไม่ได้ทดสอบสิ่งนี้

ประเด็นก็คือIABจะต้องสามารถใช้ IABFunc สำหรับประเภทผลตอบแทนใด ๆRและIABFuncจะต้องสามารถทำงานกับTชนิดย่อยที่ทั้งสองIAและIBและ

เจตนาของการDefaultIABเป็นเพียงการตัดที่มีอยู่Tซึ่งเชื้อและIA IBโปรดทราบว่านี่จะแตกต่างจากของคุณIAB : IA, IBในที่DefaultIABสามารถเพิ่มไปยังที่มีอยู่เสมอTภายหลัง

อ้างอิง:


วิธีการใช้งานได้ถ้ามีการเพิ่มประเภท object-wrapper ทั่วไปด้วยพารามิเตอร์ T, IA, IB, โดยที่ T ถูก จำกัด ให้กับอินเตอร์เฟสซึ่ง encapsulate การอ้างอิงของ type T และอนุญาตให้Applyเรียกใช้งานได้ ปัญหาใหญ่คือไม่มีวิธีใช้ฟังก์ชั่นที่ไม่ระบุตัวตนเพื่อใช้ส่วนต่อประสานดังนั้นการสร้างเช่นนั้นเป็นความเจ็บปวดที่แท้จริงที่จะใช้
supercat

3

TypeScriptเป็นภาษาที่พิมพ์อีกประเภทหนึ่งที่รองรับประเภทการแยกT & U(รวมถึงประเภทการรวมT | U) นี่คือตัวอย่างที่อ้างถึงจากหน้าเอกสารประกอบเกี่ยวกับประเภทขั้นสูง :

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{};
    for (let id in first) {
        (<any>result)[id] = (<any>first)[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    }
    return result;
}

2

ประเทศศรีลังกาได้รับการสนับสนุนอย่างเต็มที่สำหรับชั้นแรกยูเนี่ยนและแยกประเภท

คุณเขียนประเภทสหภาพเป็นและชนิดแยกเป็นX | YX & Y

ยิ่งไปกว่านั้น Ceylon ยังมีเหตุผลที่ซับซ้อนมากมายเกี่ยวกับประเภทเหล่านี้ ได้แก่ :

  • เงินต้นอินสแตนซ์:ตัวอย่างเช่นConsumer<X>&Consumer<Y>เป็นชนิดเดียวกันกับConsumer<X|Y>ถ้าConsumercontravariant ในXและ
  • disjointness:ตัวอย่างเช่นObject&NullชนิดเดียวกันกับประเภทNothingด้านล่าง

0

ฟังก์ชั่น C ++ ทั้งหมดมีประเภทผลตอบแทนคงที่ แต่ถ้าพวกเขาส่งคืนพอยน์เตอร์พอยน์เตอร์สามารถมีข้อ จำกัด ชี้ไปที่ประเภทที่แตกต่างกัน

ตัวอย่าง:

class Base {};
class Derived1: public Base {};
class Derived2: public Base{};

Base * function(int derived_type)
{
    if (derived_type == 1)
        return new Derived1;
    else
        return new Derived2;
}

พฤติกรรมของตัวชี้ที่ส่งคืนจะขึ้นอยู่กับvirtualฟังก์ชั่นที่กำหนดไว้และคุณสามารถทำ downcast ที่เลือกด้วยพูด

Base * foo = function(...);dynamic_cast<Derived1>(foo).

นั่นเป็นวิธีที่ polymorphism ทำงานใน C ++


และแน่นอนหนึ่งสามารถใช้anyหรือvariantประเภทเช่นการเพิ่มแม่แบบให้ ดังนั้นข้อ จำกัด ไม่อยู่
Deduplicator

นี่ไม่ใช่คำถามที่ถาม แต่สำหรับวิธีการระบุว่าประเภทการส่งคืนขยายซูเปอร์class Base1{}; class Base2{}; class Derived1 : public Base1, public Base2 {}; class Derived2 : public Base1, public Base2 {}คลาสที่ระบุสองรายการพร้อมกันคือ... ตอนนี้เราสามารถระบุประเภทที่อนุญาตให้ส่งคืนได้Derived1หรือDerived2ไม่Base1ไม่Base2โดยตรง
จูลส์

-1

หลาม

มันพิมพ์ได้ดีมาก ๆ

แต่ชนิดจะไม่ถูกประกาศเมื่อสร้างฟังก์ชั่นดังนั้นวัตถุที่ส่งคืนจึง "คลุมเครือ"

ในคำถามเฉพาะของคุณคำที่ดีกว่าอาจเป็น "Polymorphic" นั่นคือกรณีการใช้งานทั่วไปใน Python คือการส่งคืนชนิดของตัวแปรที่ใช้อินเตอร์เฟสทั่วไป

def some_function( selector, *args, **kw ):
    if selector == 'this':
        return This( *args, **kw )
    else:
        return That( *args, **kw )

เนื่องจาก Python ถูกพิมพ์อย่างรุนแรงวัตถุที่เป็นผลลัพธ์จะเป็นตัวอย่างของThisหรือThatและไม่สามารถถูกบังคับหรือส่งไปยังวัตถุประเภทอื่นได้อย่างง่ายดาย


นี่เป็นสิ่งที่ทำให้เข้าใจผิดมาก ในขณะที่ชนิดของวัตถุนั้นไม่เปลี่ยนรูปสวยมากค่าสามารถแปลงระหว่างประเภทได้ค่อนข้างง่าย เพื่อ str ตัวอย่างเช่นเล็กน้อย
James Youngman

1
@JamesYoungman: อะไรนะ นั่นเป็นเรื่องจริงสำหรับทุกภาษา ทุกภาษาที่ฉันเคยเห็นมี to_string การแปลงทางซ้ายขวาและตรงกลาง ฉันไม่ได้รับความคิดเห็นของคุณเลย คุณสามารถทำอย่างละเอียด?
S.Lott

ฉันพยายามเข้าใจสิ่งที่คุณหมายถึงโดย "งูใหญ่พิมพ์" บางทีฉันอาจเข้าใจผิดว่าคุณหมายถึงอะไร ตรงไปตรงมา Python มีคุณสมบัติบางอย่างที่ฉันจะเชื่อมโยงกับภาษาที่พิมพ์อย่างมาก ตัวอย่างเช่นมันยอมรับโปรแกรมที่ประเภทการคืนค่าของฟังก์ชั่นเข้ากันไม่ได้กับการใช้ค่าของผู้โทร ตัวอย่างเช่น "x, y = F (z)" โดยที่ F () ส่งคืน (z, z, z)
James Youngman

ประเภทของวัตถุ Python ไม่สามารถเปลี่ยนได้ (ไม่มีเวทมนตร์ร้ายแรง) ไม่มีโอเปอเรเตอร์ "cast" เนื่องจากมีกับ Java และ C ++ ทำให้แต่ละวัตถุพิมพ์อย่างมาก ชื่อตัวแปรและชื่อฟังก์ชั่นไม่มีการเชื่อมต่อประเภท แต่วัตถุเองพิมพ์อย่างยิ่ง แนวคิดหลักที่นี่ไม่ได้มีการประกาศ แนวคิดหลักคือการให้บริการของผู้ประกอบการหล่อ โปรดทราบด้วยว่าสิ่งนี้ดูเหมือนว่าฉันจะเป็นจริง ผู้ดำเนินรายการอาจโต้แย้งว่า
S.Lott

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