Есть ли статические языки с утиным типом?

Могу ли я указать интерфейсы при объявлении члена?

Поразмыслив над этим вопросом какое-то время, мне пришло в голову, что язык со статическим типом утки действительно может работать. Почему предопределенные классы не могут быть привязаны к интерфейсу во время компиляции? Пример:

public interface IMyInterface
{
  public void MyMethod();
}

public class MyClass  //Does not explicitly implement IMyInterface
{
  public void MyMethod()  //But contains a compatible method definition
  {
    Console.WriteLine("Hello, world!");
  }
}

...

public void CallMyMethod(IMyInterface m)
{
  m.MyMethod();
}

...

MyClass obj = new MyClass();
CallMyMethod(obj);     // Automatically recognize that MyClass "fits" 
                       // MyInterface, and force a type-cast.

Знаете ли вы какие-либо языки, поддерживающие такую ​​функцию? Было бы полезно на Java или C#? Есть ли в нем какие-то фундаментальные изъяны? Я понимаю, что вы можете создать подкласс MyClass и реализовать интерфейс или использовать шаблон проектирования адаптера для достижения того же, но эти подходы кажутся ненужным шаблонным кодом.

Сила классов Java: сравнение с языком C
Сила классов Java: сравнение с языком C
Абстракция" - это процесс упрощения сложных сущностей или концепций реального мира с целью их применения в форме программирования. В Java класс...
18
0
4 018
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

Я не вижу в этом смысла. Почему бы не указать явно, что класс реализует интерфейс и что с ним покончено? Реализация интерфейса - это то, что сообщает другим программистам, что этот класс должен вести себя так, как это определяет интерфейс. Простое наличие у метода того же имени и подписи не дает никаких гарантий того, что намерением дизайнера было выполнить аналогичные действия с методом. Может быть, но зачем оставлять это для интерпретации (и неправильного использования)?

Причина, по которой вы можете успешно «уйти» с этим в динамических языках, больше связана с TDD, чем с самим языком. На мой взгляд, если язык предлагает возможность давать такого рода указания другим, кто использует / просматривает код, вы должны его использовать. Это на самом деле улучшает ясность и стоит нескольких дополнительных символов. В случае, если у вас нет доступа для этого, адаптер служит той же цели, явно объявляя, как интерфейс относится к другому классу.

Я согласен, в этом суть интерфейса, чтобы описать что-то. Используйте его всякий раз, когда можете, это не шаблонный код, это хорошая практика.

Mark 14.11.2008 08:52

Почему этот шаблон успешен в динамических языках, но считается плохой практикой в ​​статических языках? При чем тут TDD, уникальное для динамической типизации? Как упоминал @mipadi, почему языки объектно-ориентированного программирования не могут поддерживать своего рода систему вывода типов в стиле Haskell?

Cybis 14.11.2008 10:12

Исходя из моего ограниченного понимания, дженерики Java - это всего лишь обходной способ сделать то, что составляет утиный тип.

James McMahon 15.01.2009 02:27

@ Джеймс: извините, но нет. Дженерики Java - это то, что составляет автоматическое приведение типов к типу Object и обратно. Вы не можете написать код, подобный показанному выше.

R. Martinho Fernandes 06.04.2010 06:23

Мне неизвестен какой-либо язык или фреймворк, который распознал бы концепцию «Коллекции вещей, реализующих IFoo и IBar, unless everything one wanted to hold in the collection also implemented some interface IFooBar», которая наследует IFoo и IBar. Если общего типа не существует, единственное, что может сделать код, - это определить коллекцию как содержащую один тип , и приведение по мере необходимости к другому.С точки зрения статики, утиная типизация позволит определить тип коллекции как {IFoo,IBar}, а ее элементы будут рассматриваться как удовлетворяющие обоим ограничениям.

supercat 13.06.2014 22:48

что, если код уже скомпилирован, а у вас нет исходного кода, потому что он не ваш? как вы сделаете так, чтобы он унаследовал интерфейс? нельзя ожидать, что другие будут все время принимать правильные решения

symbiont 30.07.2016 12:18

Предварительная версия Visual Basic 9 поддерживала статическую утиную типизацию с использованием динамических интерфейсов, но в них была сокращена функция * для своевременной доставки.

Ответ принят как подходящий

Языки со статической типизацией по определению проверяют типы в время компиляции, а не время выполнения. Одна из очевидных проблем с описанной выше системой заключается в том, что компилятор будет проверять типы при компиляции программы, а не во время выполнения.

Теперь вы можете встроить в компилятор больше интеллекта, чтобы он мог типы выводить, вместо того, чтобы явно указывать программисту типы объявлять; компилятор может увидеть, что MyClass реализует метод MyMethod(), и обработать этот случай соответственно, без - необходимость явного объявления интерфейсов (как вы предлагаете). Такой компилятор может использовать вывод типа, например Хиндли-Милнер.

Конечно, некоторые статически типизированные языки, такие как Haskell, уже делают что-то похожий по отношению к тому, что вы предлагаете; компилятор Haskell может выводить типы (в большинстве случаев) без необходимости их явного объявления. Но очевидно, что у Java / C# такой возможности нет.

Спасибо. Я только начал изучать Haskell около недели назад, и пока это кажется довольно крутым. Однако это немного требует обучения - никогда раньше не разбирался в функциональных языках. В любом случае, его система вывода типа очень похожа на то, о чем я говорю.

Cybis 14.11.2008 10:14

В C#, похоже, есть вывод типа. Например, об этом говорится в этой статье. developer.com/net/csharp/article.php/3601646/…

John K 24.10.2009 07:31

Boo определенно является статическим языком с утиным типом: http://boo.codehaus.org/Duck+Typing

Отрывок:

Boo is a statically typed language, like Java or C#. This means your boo applications will run about as fast as those coded in other statically typed languages for .NET or Mono. But using a statically typed language sometimes constrains you to an inflexible and verbose coding style, with the sometimes necessary type declarations (like "x as int", but this is not often necessary due to boo's Type Inference) and sometimes necessary type casts (see Casting Types). Boo's support for Type Inference and eventually generics help here, but...

Sometimes it is appropriate to give up the safety net provided by static typing. Maybe you just want to explore an API without worrying too much about method signatures or maybe you're creating code that talks to external components such as COM objects. Either way the choice should be yours not mine.

Along with the normal types like object, int, string...boo has a special type called "duck". The term is inspired by the ruby programming language's duck typing feature ("If it walks like a duck and quacks like a duck, it must be a duck").

Статический язык с уткой - это не то же самое, что статический утиный ввод. Как существующий VB, так и будущий C# 4.0 имеют статическую типизацию и динамическую / позднюю / утиную типизацию.

Mark Cidade 14.11.2008 06:41

Я не имею в виду сочетание статической и динамической типизации (похоже, это то, о чем вы говорите, учебное пособие). Я имею в виду вывод типов - компилятор может сразу увидеть, что MyClass и IMyInterface совместимы, без явного указания взаимосвязи программистом.

Cybis 14.11.2008 06:49

Виноват. Бу не сделает этого за тебя.

torial 16.11.2008 08:33

F# поддерживает статическую утиную типизацию, но с одной уловкой: вы должны использовать ограничения членов. Подробности доступны в этом запись в блоге.

Пример из цитируемого блога:

let inline speak (a: ^a) =
    let x = (^a : (member speak: unit -> string) (a))
    printfn "It said: %s" x
    let y = (^a : (member talk: unit -> string) (a))
    printfn "Then it said %s" y

type duck() =
    member x.speak() = "quack"
    member x.talk() = "quackity quack"
type dog() =
    member x.speak() = "woof"
    member x.talk() = "arrrr"

let x = new duck()
let y = new dog()
speak x
speak y

Как насчет использования шаблонов в C++?

class IMyInterface  // Inheritance from this is optional
{
public:
  virtual void MyMethod() = 0;
}

class MyClass  // Does not explicitly implement IMyInterface
{
public:
  void MyMethod()  // But contains a compatible method definition
  {
    std::cout << "Hello, world!" "\n";
  }
}

template<typename MyInterface>
void CallMyMethod(MyInterface& m)
{
  m.MyMethod();  // instantiation succeeds iff MyInterface has MyMethod
}

MyClass obj;
CallMyMethod(obj);     // Automatically generate code with MyClass as 
                       // MyInterface

На самом деле я не компилировал этот код, но считаю, что это работоспособная и довольно тривиальная C++ - изация исходного предложенного (но неработающего) кода.

Кто-то упомянул шаблоны C++, но я думаю, что этот ответ был удален. В любом случае, шаблоны - это просто расширенные макросы. Новый «CallMyMethod» создается для каждого типа, который вы ему передаете, поэтому на самом деле это не вывод типа.

Cybis 24.11.2008 01:31

Кроме того, я не знаю хорошей IDE C++ с intellisense, которая не запуталась бы в шаблонах (Visual Studio не всегда перечисляет все варианты, а иногда и полностью останавливается).

Cybis 24.11.2008 01:34

Честно говоря, исходный постер вообще не упоминал вывод типа как таковой. Я просто немного изменил его код и показал, что он может работать на C++ практически как есть.

John Zwinck 24.11.2008 04:16

Я не могу понять, почему люди до сих пор думают, что шаблоны C++ - это просто расширенные макросы, это полный механизм программирования по Тьюрингу, а не инструмент для замены текста.

swegi 22.12.2009 23:35

Макросы @swegi настоящий (синтаксические) также являются полными по Тьюрингу. И еще более. Например, в Clojure вы можете делать практически все во время компиляции.

Display Name 26.07.2014 17:35

Помимо прочего, шаблоны C++ даже не являются «продвинутыми» макросами. На самом деле это в лучшем случае очень простые макросы. Ни один язык (или метаязык), на котором вы должны использовать собственный if, не может всерьез считаться продвинутой средой программирования.

Leushenko 14.08.2014 03:41

@Leushenko, начиная с C++ 11, необязательно: en.cppreference.com/w/cpp/types/conditional.

chbaker0 12.03.2017 01:18

Похоже на миксы или черты характера:
http://en.wikipedia.org/wiki/Mixin
http://www.iam.unibe.ch/~scg/Archive/Papers/Scha03aTraits.pdf

Структурные типы в Scala делают что-то подобное.

См. Статически проверенный «утиный ввод» в Scala

Большинство языков в семействе машинного обучения поддерживают структурные типы с логическим выводом и схемы типов с ограничениями, вызывающую терминологию разработчика языков, которая кажется наиболее вероятной тем, что вы имели в виду под фразой «статическая утиная типизация» в исходном вопросе.

Наиболее популярные языки этого семейства, которые приходят на ум, включают: Haskell, Objective Caml, F# и Scala. Тот, который больше всего соответствует вашему примеру, конечно, будет Objective Caml. Вот перевод вашего примера:

open Printf

class type iMyInterface = object
  method myMethod: unit
end

class myClass = object
  method myMethod = printf "Hello, world!"
end

let callMyMethod: #iMyInterface -> unit = fun m -> m#myMethod

let myClass = new myClass

callMyMethod myClass

Примечание: некоторые использованные вами имена необходимо изменить, чтобы они соответствовали концепции OCaml о семантике регистра идентификаторов, но в остальном это довольно простой перевод.

Также стоит отметить, что ни аннотация типа в функции callMyMethod, ни определение типа класса iMyInterface не являются строго необходимыми. Objective Caml может вывести все, что есть в вашем примере, вообще без каких-либо объявлений типов.

Совершенно новый ответ на этот вопрос, Go имеет именно эту функцию. Я думаю, что это действительно круто и умно (хотя мне будет интересно посмотреть, как это отразится в реальной жизни), и спасибо за то, что подумал об этом.

Как задокументировано в официальной документации (как часть Tour of Go, с примером кода):

Interfaces are implemented implicitly

A type implements an interface by implementing its methods. There is no explicit declaration of intent, no "implements" keyword.

Implicit interfaces decouple the definition of an interface from its implementation, which could then appear in any package without prearrangement.

Не могли бы вы дать ссылку на саму часть документации Go, в которой объясняется, как это работает? А еще лучше привести сюда пример?

Paŭlo Ebermann 08.03.2016 18:31

Как хотите (но только со ссылкой на пример)

Grumdrig 15.03.2016 01:22

В последней версии моего языка программирования Цапля он поддерживает нечто подобное с помощью оператора принуждения структурных подтипов под названием as. Так что вместо:

MyClass obj = new MyClass();
CallMyMethod(obj);

Вы бы написали:

MyClass obj = new MyClass();
CallMyMethod(obj as IMyInterface);

Как и в вашем примере, в этом случае MyClass не обязательно должен явно реализовывать IMyInterface, но если бы это было так, приведение могло произойти неявно, и оператор as можно было бы опустить.

Я написал немного больше о методе, который я называю явным структурным подтипом в эта статья.

D (http://dlang.org) - это статически компилируемый язык, обеспечивающий утиную типизацию с помощью wrap () и unwrap () (http://dlang.org/phobos-prerelease/std_typecons.html#.unwrap).

Новые версии C++ движутся в направлении статической утиной печати. Вы можете когда-нибудь (сегодня?) Написать что-то вроде этого:

auto plus(auto x, auto y){
    return x+y;
}

и он не сможет скомпилировать, если не будет соответствующего вызова функции для x+y.

Что касается вашей критики:

A new "CallMyMethod" is created for each different type you pass to it, so it's not really type inference.

Но это вывод типа (можно сказать, foo(bar), где foo - шаблонная функция) и имеет тот же эффект, за исключением того, что он более эффективен по времени и занимает больше места в скомпилированном коде.

В противном случае вам придется искать метод во время выполнения. Вам нужно будет найти имя, а затем проверить, что у имени есть метод с правильными параметрами.

Или вам нужно будет сохранить всю эту информацию о совпадающих интерфейсах и изучить каждый класс, соответствующий интерфейсу, а затем автоматически добавить этот интерфейс.

В любом случае это позволяет неявно и случайно нарушить иерархию классов, что плохо для новой функции, потому что это противоречит привычкам программистов C# / Java. С шаблонами C++ вы уже знаете, что находитесь на минном поле (и они также добавляют функции («концепции»), чтобы разрешить ограничения на параметры шаблона).

Машинопись!

Ну, хорошо ... Итак, это надмножество javascript и, возможно, не является «языком», но этот вид статической утиной печати жизненно важен в TypeScript.

Я думаю, что Typescript использует структурную типизацию (которая, честно говоря, очень похожа на ducktyping, только строже)

FRR 03.04.2020 01:08

Кристалл - это язык со статической утиной типизацией. Пример:

def add(x, y)
  x + y
end

add(true, false)

Вызов add вызывает эту ошибку компиляции:

Error in foo.cr:6: instantiating 'add(Bool, Bool)'

add(true, false)
^~~

in foo.cr:2: undefined method '+' for Bool

  x + y
    ^

Другие вопросы по теме