Могу ли я указать интерфейсы при объявлении члена?
Поразмыслив над этим вопросом какое-то время, мне пришло в голову, что язык со статическим типом утки действительно может работать. Почему предопределенные классы не могут быть привязаны к интерфейсу во время компиляции? Пример:
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 и реализовать интерфейс или использовать шаблон проектирования адаптера для достижения того же, но эти подходы кажутся ненужным шаблонным кодом.

Я не вижу в этом смысла. Почему бы не указать явно, что класс реализует интерфейс и что с ним покончено? Реализация интерфейса - это то, что сообщает другим программистам, что этот класс должен вести себя так, как это определяет интерфейс. Простое наличие у метода того же имени и подписи не дает никаких гарантий того, что намерением дизайнера было выполнить аналогичные действия с методом. Может быть, но зачем оставлять это для интерпретации (и неправильного использования)?
Причина, по которой вы можете успешно «уйти» с этим в динамических языках, больше связана с TDD, чем с самим языком. На мой взгляд, если язык предлагает возможность давать такого рода указания другим, кто использует / просматривает код, вы должны его использовать. Это на самом деле улучшает ясность и стоит нескольких дополнительных символов. В случае, если у вас нет доступа для этого, адаптер служит той же цели, явно объявляя, как интерфейс относится к другому классу.
Почему этот шаблон успешен в динамических языках, но считается плохой практикой в статических языках? При чем тут TDD, уникальное для динамической типизации? Как упоминал @mipadi, почему языки объектно-ориентированного программирования не могут поддерживать своего рода систему вывода типов в стиле Haskell?
Исходя из моего ограниченного понимания, дженерики Java - это всего лишь обходной способ сделать то, что составляет утиный тип.
@ Джеймс: извините, но нет. Дженерики Java - это то, что составляет автоматическое приведение типов к типу Object и обратно. Вы не можете написать код, подобный показанному выше.
Мне неизвестен какой-либо язык или фреймворк, который распознал бы концепцию «Коллекции вещей, реализующих IFoo и IBar, unless everything one wanted to hold in the collection also implemented some interface IFooBar», которая наследует IFoo и IBar. Если общего типа не существует, единственное, что может сделать код, - это определить коллекцию как содержащую один тип , и приведение по мере необходимости к другому.С точки зрения статики, утиная типизация позволит определить тип коллекции как {IFoo,IBar}, а ее элементы будут рассматриваться как удовлетворяющие обоим ограничениям.
что, если код уже скомпилирован, а у вас нет исходного кода, потому что он не ваш? как вы сделаете так, чтобы он унаследовал интерфейс? нельзя ожидать, что другие будут все время принимать правильные решения
Предварительная версия Visual Basic 9 поддерживала статическую утиную типизацию с использованием динамических интерфейсов, но в них была сокращена функция * для своевременной доставки.
Языки со статической типизацией по определению проверяют типы в время компиляции, а не время выполнения. Одна из очевидных проблем с описанной выше системой заключается в том, что компилятор будет проверять типы при компиляции программы, а не во время выполнения.
Теперь вы можете встроить в компилятор больше интеллекта, чтобы он мог типы выводить, вместо того, чтобы явно указывать программисту типы объявлять; компилятор может увидеть, что MyClass реализует метод MyMethod(), и обработать этот случай соответственно, без - необходимость явного объявления интерфейсов (как вы предлагаете). Такой компилятор может использовать вывод типа, например Хиндли-Милнер.
Конечно, некоторые статически типизированные языки, такие как Haskell, уже делают что-то похожий по отношению к тому, что вы предлагаете; компилятор Haskell может выводить типы (в большинстве случаев) без необходимости их явного объявления. Но очевидно, что у Java / C# такой возможности нет.
Спасибо. Я только начал изучать Haskell около недели назад, и пока это кажется довольно крутым. Однако это немного требует обучения - никогда раньше не разбирался в функциональных языках. В любом случае, его система вывода типа очень похожа на то, о чем я говорю.
В C#, похоже, есть вывод типа. Например, об этом говорится в этой статье. developer.com/net/csharp/article.php/3601646/…
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 имеют статическую типизацию и динамическую / позднюю / утиную типизацию.
Я не имею в виду сочетание статической и динамической типизации (похоже, это то, о чем вы говорите, учебное пособие). Я имею в виду вывод типов - компилятор может сразу увидеть, что MyClass и IMyInterface совместимы, без явного указания взаимосвязи программистом.
Виноват. Бу не сделает этого за тебя.
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» создается для каждого типа, который вы ему передаете, поэтому на самом деле это не вывод типа.
Кроме того, я не знаю хорошей IDE C++ с intellisense, которая не запуталась бы в шаблонах (Visual Studio не всегда перечисляет все варианты, а иногда и полностью останавливается).
Честно говоря, исходный постер вообще не упоминал вывод типа как таковой. Я просто немного изменил его код и показал, что он может работать на C++ практически как есть.
Я не могу понять, почему люди до сих пор думают, что шаблоны C++ - это просто расширенные макросы, это полный механизм программирования по Тьюрингу, а не инструмент для замены текста.
Макросы @swegi настоящий (синтаксические) также являются полными по Тьюрингу. И еще более. Например, в Clojure вы можете делать практически все во время компиляции.
Помимо прочего, шаблоны C++ даже не являются «продвинутыми» макросами. На самом деле это в лучшем случае очень простые макросы. Ни один язык (или метаязык), на котором вы должны использовать собственный if, не может всерьез считаться продвинутой средой программирования.
@Leushenko, начиная с C++ 11, необязательно: en.cppreference.com/w/cpp/types/conditional.
Похоже на миксы или черты характера:
http://en.wikipedia.org/wiki/Mixin
http://www.iam.unibe.ch/~scg/Archive/Papers/Scha03aTraits.pdf
Структурные типы в 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, в которой объясняется, как это работает? А еще лучше привести сюда пример?
Как хотите (но только со ссылкой на пример)
В последней версии моего языка программирования Цапля он поддерживает нечто подобное с помощью оператора принуждения структурных подтипов под названием 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, только строже)
Кристалл - это язык со статической утиной типизацией. Пример:
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
^
Я согласен, в этом суть интерфейса, чтобы описать что-то. Используйте его всякий раз, когда можете, это не шаблонный код, это хорошая практика.