Які статично набрані мови підтримують типи перетину для повернення значень функції?


17

Початкова примітка:

Це питання було закрито після декількох редагувань, оскільки мені не вистачало належної термінології, щоб точно сказати, що я шукав. Тоді Сем Тобін-Хохштадт опублікував коментар, який змусив мене розпізнати саме це: мови програмування, які підтримують типи перетину для повернення значень функції.

Тепер, коли питання було знову відкрито, я вирішив його покращити, переписавши його (сподіваюся) більш точним чином. Тому деякі відповіді та коментарі нижче можуть більше не мати сенсу, оскільки вони посилаються на попередні зміни. (Будь ласка, перегляньте історію редагування питання в таких випадках.)

Чи є якісь популярні статично і сильно набрані мови програмування (такі як Haskell, загальна Java, C #, F # тощо), які підтримують типи перетину для повернення значень функції? Якщо так, що і як?

(Якщо я чесно, мені дуже хотілося б, щоб хтось демонстрував спосіб вираження типів перетину мовою основної мови, такою як C # або Java.)

Я наведу короткий приклад того, як можуть виглядати типи перетину, використовуючи псевдокод, схожий на 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. (Тобто, на відміну від дженериків, абонент не може вибрати конкретний тип T- функція робить. З цього я б припустив, що Tце насправді не універсальний тип, а екзистенційний.)

PS: Я знаю, що можна просто визначити a interface IXY : IX, IYі змінити тип повернення fnна IXY. Однак це насправді не те саме, тому що часто ви не можете підключити додатковий інтерфейс IXYдо визначеного раніше типу, Aякий реалізується лише IXта IYокремо.


Виноска. Деякі ресурси щодо типів перетину:

Стаття у Вікіпедії для "Тип системи" має підрозділ про типи перетину .

Доповідь Бенджаміна Ч. Пірса (1991), "Програмування з типами перетину, типами союзу та поліморфізмом"

Девід П. Каннінгем (2005), "Типи перетину на практиці" , який містить тематичне дослідження мови Форсайта, про яке йдеться у статті Вікіпедії.

Питання про переповнення стека, "Типи об'єднань та перетину", які отримали кілька хороших відповідей, серед них і цей, який дає приклад псевдокоду типів перетину, подібних до моїх вище.


6
Як це неоднозначно? Tвизначає тип, навіть якщо він просто визначений у оголошенні функції як "деякий тип, який розширює / реалізує IXта IY". Той факт, що фактичне повернене значення є особливим випадком, що ( Aабо Bвідповідно) тут не є особливим, ви можете так само добре досягти цього, використовуючи Objectзамість цього T.
Йоахім Зауер

1
Ruby дозволяє повернути все, що завгодно, з функції. Те саме для інших динамічних мов.
thorsten müller

Я оновив свою відповідь. @Joachim: Я знаю, що термін "амбітний" не дуже чітко сприймає цю концепцію, тому є прикладом для уточнення наміченого значення.
stakx

1
Рекламна PS: ... яка змінює ваше запитання на "яка мова дозволяє розглядати тип Tяк інтерфейс, Iколи він реалізує всі методи інтерфейсу, але не оголосив цей інтерфейс".
Ян Худек

6
Закрити це питання було помилкою, оскільки є точна відповідь, що таке типи об'єднань . Типи союзів доступні такими мовами, як (Типізована ракетка) [ docs.racket-lang.org/ts-guide/] .
Сем Тобін-Хохштадт

Відповіді:


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()

шкала на основі точок має справжні типи перетину, але не для поточної / колишньої.
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 не можуть з’являтися на сайті виклику, тому що ви не знаєте, який саме з них отримаєте.
Ян Худек

Так, ви маєте рацію - це не еквівалент оригінальному прикладу, оскільки вам потрібно надати конкретний тип. Якби ми могли використовувати підстановку з перетинами, ми мали б це. Здається, ми не можемо ... і я не знаю, чому ні (див. Це ). Але, все ж у Java є такі види перетину ...
redjamjar

1
Я розчарований, що я чомусь ніколи не дізнавався про типи перехрестя за 10 років, які я робив на Java. Тепер, коли я весь час використовую Flowtype, я вважав, що вони є найбільшою відсутністю на Яві, лише щоб знайти, що я ніколи навіть не бачив їх у дикій природі. Люди серйозно їх недооцінюють. Я думаю, якби вони були більш відомими, тоді рамки введення залежності, як Spring, ніколи не стали б такими популярними.
Енді

8

Оригінальне запитання задавали "неоднозначного типу". На це відповідь:

Неоднозначного типу, очевидно, немає. Абонент повинен знати, що вони отримають, тому це неможливо. Будь-яка мова, яку можна повернути, - це базовий тип, інтерфейс (можливо, автоматично генерується як у типі перетину) або динамічний тип (а динамічний тип в основному просто тип з методом виклику, отримання та встановлення).

Інтерфейс:

Так що в основному ви хочете, щоб повернути інтерфейс , IXYякий успадковується IX і IY хоча цей інтерфейс не був оголошений ні в одному Aабо B, можливо , тому , що не було визнано , коли були визначені ці типи. В такому разі:

  • Очевидно, що динамічно набрано.
  • Я не пам'ятаю ні статично типізований мова мейнстрім матиме можливість створювати інтерфейс (це об'єднання типу Aі Bчи типу перетину IXі IY) сам.
  • Йдіть , оскільки це класи реалізують інтерфейс, якщо вони мають правильні методи, не оголошуючи їх ніколи. Тож ви просто оголосите інтерфейс, який отримує двоє там, і поверніть його.
  • Очевидно, будь-яка інша мова, де тип може бути визначений для реалізації інтерфейсу за межами визначення цього типу, але я не думаю, що я пам’ятаю інший, ніж GO.
  • Це неможливо в будь-якому типі, де реалізація інтерфейсу має бути визначена в самому визначенні типу. Однак ви можете обійтись у більшості з них, визначивши обгортку, яка реалізує два інтерфейси, і делегує всі методи обгорнутому об'єкту.

PS сильно типізований мова є той , в якому об'єкт даного типу не може розглядатися як об'єкт іншого типу, в той час як слабо типізований мова є той , який має переінтерпретіровать відтінок. Таким чином, всі динамічно типізовані мови сильно набираються , тоді як слабо типізовані мови складаються, C і C ++, а всі три вводяться статично .


Це неправильно, немає нічого неоднозначного щодо типу. Мова може повертати так звані "типи перетину" --- це лише мало, якщо якісь основні мови роблять це.
redjamjar

@redjamjar: Питання було різним формулюванням, коли я відповів на нього і попросив "неоднозначний тип". Ось чому це починається саме з цього. Запитання було значно переписано з тих пір. Я розширю відповідь, щоб згадати як оригінальну, так і поточну формулювання.
Ян Худек

вибачте, я пропустив це, очевидно!
redjamjar

+1 для згадки про Golang, який, мабуть, є найкращим прикладом загальної мови, що дозволяє це робити, навіть якщо спосіб зробити це трохи чітко.
Жуль

3

Мова програмування Go вид має, але тільки для типів інтерфейсів.

У 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

Ви можете зробити те, що хочете, використовуючи обмежений екзистенціальний тип, який можна кодувати будь-якою мовою за допомогою генеричних та обмежених поліморфізмів, наприклад, 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пізніше.

Список літератури:


Підхід працює, якщо додати загальний тип обгортки об'єктів з параметрами T, IA, IB, з обмеженими на інтерфейси T, що інкапсулює посилання типу 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 | Yі тип перетину як X & Y.

Ще краще, Цейлон має багато складних міркувань щодо таких типів, зокрема:

  • основні моменти: наприклад, Consumer<X>&Consumer<Y>це той самий тип, що як Consumer<X|Y>би Consumerпротилежний у X, і
  • нерозбірливість: наприклад, 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функцій, і ви можете зробити перевірений низхідний, скажімо,

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

Ось як працює поліморфізм у С ++.


І звичайно, можна використовувати тип anyабо variantтип, як передбачено збільшення шаблонів. Таким чином, обмеження не залишається.
Дедуплікатор

Це не зовсім те , що ставив питання, хоча, який був спосіб вказати , що повертається тип проходить два ідентифікованих суперкласу в той же час, тобто class Base1{}; class Base2{}; class Derived1 : public Base1, public Base2 {}; class Derived2 : public Base1, public Base2 {}... тепер який тип ми можемо визначити , що дозволяє повернутися або Derived1або , Derived2але ні Base1ні Base2безпосередньо?
Жуль

-1

Пітон

Це дуже, дуже сильно набрано.

Але тип не оголошується при створенні функції, тому повернені об'єкти "неоднозначні".

У вашому конкретному питанні кращим терміном може бути "Поліморфний". Ось загальним випадком використання в Python є повернення типів варіантів, які реалізують загальний інтерфейс.

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

Оскільки Python сильно набраний, отриманий об'єкт буде екземпляром Thisабо Thatне може (легко) бути примушений або переданий іншому типу об'єктів.


Це досить оману; в той час як тип об'єкта є незмінним, значення можуть бути перетворені між типами досить легко. Стригувати, наприклад, тривіально.
Джеймс Янгмен

1
@JamesYoungman: Що? Це справедливо для всіх мов. Усі мови, які я коли-небудь бачив, мають тональну конверсію зліва, справа та по центру. Я взагалі не отримую вашого коментаря. Чи можете ви докладно?
S.Lott

Я намагався зрозуміти, що ти маєш на увазі під "Python сильно набраний". Можливо, я неправильно зрозумів, що ви мали на увазі під «сильно набраним». Чесно кажучи, Python має мало характеристик, які я б асоціював із сильно набраними мовами. Наприклад, він приймає програми, в яких тип повернення функції не сумісний з використанням абонентом значення. Наприклад, "x, y = F (z)", де F () повертається (z, z, z).
Джеймс Янгмен

Тип об’єкта Python неможливо (без серйозної магії) змінити. Немає оператора "лиття", як у Java та C ++. Це робить кожен об'єкт сильно набраним. Імена змінних та імена функцій не мають типового прив'язки, але самі об'єкти набираються сильно. Ключова концепція тут - не наявність декларацій. Ключова концепція - це доступність операторів литового складу. Також зауважте, що це видається мені фактичним; Однак модератори можуть заперечити це.
S.Lott

1
Операції C і C ++ лиття також не змінюють тип свого операнда.
Джеймс Янгмен
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.