LINUX.ORG.RU

golang: одноимённые методы в разных интерфейсах

 


1

2

Вопрос 1

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

Я пытаюсь написать видео плейер со звуком, и мне нужно реализовать в одной структре оба интерфейса. У каждого из них есть метод Извлечь() без параметров.

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

Мои действия в golang?

Вопрос 2 (он про Яр, конечно же)

Есть билиотека ::словари::, в ней есть тип контейнер, и у него метод контейнер%дай(строка). По соглашению, если ц - типа контейнер, то ц.дай превращается в контейнер%дай(ц). Т.е. примерно то же, что утиная интерфейсизация в golang, только интерфейс в данном случае состоит из одной функции. Я это придумал никак не глядя на голанг, а всего лишь пытаясь сделать костыль для неудобного обращения в лиспе к полям структур.

В библиотеке ::словари:: есть также generic код:

опр функ Напечатай-все-значения(о) тело
  для каждого ключа к (о) // мифический синтаксис
    л-печать(о.дай(к))
  кнд кно
Мы здесь экономим то, что у нас вообще не вводится понятие интерфейса. В голанге так сделали, я их понимаю и считаю, что это круто. Иногда утиная интерфейсизация - это круто. Более того, если эту функцию сделать inline, то она будет при известном типе о подставлять сразу соответствующий-контейнер%дай, а при неизвестном - будет динамически искать соответствующую данному типу функцию *%дай.

Но это - мина, которая позже рванёт.

Также есть (чужая) библиотека «рефлексия», в ней есть тип рефлексирующий-субъект, у которого тоже есть рефлексирующий-субъект%дай(строка), но по смыслу это означает «дай метаинформацию такого-то типа».

И теперь мы делаем рефлексивный словарь. И оказываемся в тупике, т.к. нам нужно сделать две функции рефлексивный-словарь%дай с разным смыслом.

Второй пример я привёл для того, чтобы оценивать жизненность предлагаемых решений в golang. Аналогия не полная, но всё же нужно иметь в виду, что мы можем отдавать наш объект и в тот код, который написан авторами интерфейса и который не может ссылаться на переименованный метод. Насколько подходит вариант с делегированием интерфейсов полям структуры - я не совсем понял, вот и разберёмся в этой теме.

★★★★★

Последнее исправление: cetjs2 (всего исправлений: 5)

https://groups.google.com/forum/#!topic/golang-nuts/4V3eI4dp95c

Да, я думал, что только в России на форумах полно идиотов (ЛОР является чудесным исключением - спасибо тебе, ЛОР!) Оказывается, и «цивилизованном мире» идиоты преобладают.

Но это было писано в 2009 году. С тех пор проблема так и осталась нерешённой?

den73 ★★★★★
() автор топика

Не думаю, что как-то решается, учитывая, что интерфейсы в Go реализуются неявно, и перегружать методы нельзя. Оборачивай структуру во врапперы для которых и реализуй интерфейсы, но еще лучше просто не используй неудачные ЯП.

nonimous
()

И какая реализация метода должна вызываться при вызове экземпляр.Извлечь()? Мои действия: делегировать реализацию интерфейсов отдельным полям структуры.

Virtuos86 ★★★★★
()
Ответ на: комментарий от Virtuos86

Ну, как костыль сойдёт вроде, но нужно не забывать отправлять правильное поле в правильную функцию. Иначе мы попытаемся извлечь аудио блоки из очереди видео блоков (для метода «Извлечь» нас спасёт тип буфера, но метод «ОчиститьОчередь» точно может быть вызван ошибочно, т.к. ничего не вернёт).

den73 ★★★★★
() автор топика
Ответ на: комментарий от cdshines

А кто сказал, что «should»? Это лишь чья-то точка зрения. Вот в голанге тоже говорят, что интерфейсы не «should» быть одноимёнными. Но это же не значит, что это правда.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

И там даже есть пункт «drawbacks». Я нахожу наследование полезным, но тут дело вовсе не в этом. Пр-во имён методов в golang глобально, и нет никаких инструментов, чтобы его разбить на части. Это явное препятствие для масштабирования.

den73 ★★★★★
() автор топика

This

Используй Ada, бро!

anonymous
()
Ответ на: комментарий от beastie

Чё-то ты не то написал, https://play.golang.org/p/XBSGIev6lr Про switch на эту тему кто-то уже упоминал, но я так и не понял, в виду невежества :)

den73 ★★★★★
() автор топика

Вообще, мне кажется, смысл этих утиных интерфейсов в том, чтобы можно было обучить чужой объект новым трюком. Отдать малыша в секцию футбола. Можно, конечно, сделать это и иными способами. Вот в C# есть «методы расширения»,

https://metanit.com/sharp/tutorial/3.18.php

И там, похоже, как раз и есть решение описанной проблемы, к-рая в golang, как я понял, всё же не решена.

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

Т.е. я уже стал склоняться к тому, что единственный выход здесь - это иметь _разные_ слова ::словари:методы:дай и ::рефлексия:методы:дай . Соответственно, мы определяем не просто функцию контейнер%дай, а функцию с именем [контейнер ::словари:методы:дай] . Дальше вопрос - жизненно ли это с т.з сложности обращения? Вот этого пока не понял. Ведь смысл записи а.б в том, что всё пр-во имён методов типа переменной а подтягивается для поиска смысла б и это очень славная автоматизация. А с моими разными словами получается какая-то мутная ситуация с пр-вами имён. Вроде есть понятие словарь%дай, но чтобы им воспользоваться, нужно ещё проследить, чтобы правильное слово «дай» было проимпортировано. Т.е. смысл записи а.б частично сходит на нет.

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 2)

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

Дык а в чем у тебя проблема ? Вон beastie дал тебе отличный пример. Если ты хочешь явно позвать метод родителя из ребенка при встраивании:


type foo struct {
...
}

type bar struct {
...
}

type baz struct {
  foo
  bar
}

func main() {
  var t baz

  t.foo.Do()
  t.bar.Do()
}

не?

joy4eg ★★★★★
()
Ответ на: комментарий от joy4eg

Пример от beastie или неправильный, или я что-то не понял -см. мой ответ - я закомментарил код, который вроде должен был выбирать интерфейс, и всё продолжило работать как ни в чём не бывало.

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

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

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

что отвечают на стартовый топик Кокс И Пик (и присоединившийся к ним Мюландер?)?

anonymous
()

Ту да трэш го гоуз ;D

anonymous
()
Ответ на: комментарий от den73

если в референс языка сложно понять порядок разрешения имён - посмотри эталонную реализацию -> будет яснее как оно в данной конкретной реализации -> golang сырцы в системе контроля версий видно как менялся данный(разрешения имён одноименных методов у поглошённых агрегатов с «похожими» интерфейсами) сырец.

последнее не по важности: что отвечают в оффи рассылках?

anonymous
()
Ответ на: комментарий от den73

то ли я не пойму о чём вы, то ли.. вот в хаскеле делаешь qualified import

import qualified Foo.Mod as FMod
import qualified Bar.Mod as BMod

FMod.f1 
BMod.f1


Т.е. здесь есть два разных модуля с одинаковыми названиями (Mod из пакета Foo и Mod из пакета Bar) и в них встречаются одинаковые названия ф-ций. Для решения проблемы переименовываешь модули при импорте да и всё. Тогда в модуле в котором сделан такой импорт, все функции из Bar.Mod имеют префикс BMod, а функции из Foo.Mоd. И нет проблемы.

В скале кстати кажется так же сделано, емнип.

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

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 3)
Ответ на: комментарий от AndreyKl

Если я правильно понял, то:

Пусть модуль Foo определяет интерфейс AFoo с единственным методом без параметров, Hello

Также в нём есть функция SayHello(o:AFoo), которая вызывает о.Hello

Проблема в том, что теперь любой объект из любого модуля, у которого определена функция Hello, считается реализующим интерфейс AFoo. Даже если та функция Hello по смыслу имеет в виду совершенно другое.

В Хаскеле, если я правильно понял, имя Hello относится к какому-то модулю, поэтому данной проблемы нет. Другое по смыслу Hello просто нужно поместить в другой модуль, это не трудно. А в Golang это имя глобально, и в этом-то проблема (опять же, если я правильно понял). Никто не может поручиться, что завтра другой программист где-нибудь в Японии не создаст своё Hello в какой-то библиотеке, к-рая мне послезавтра понадобится.

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

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Проблема в том, что теперь любой объект из любого модуля, у которого определена функция Hello, считается реализующим интерфейс AFoo. Даже если та функция Hello по смыслу имеет в виду совершенно другое.

О, зело «интересное» решение. По-моему, конечно.

Может тебя наведёт на мысли: в скале можно добавлять методы в класс.

Скажем, есть у нас класс (псевдокод)

class MyClass
  method1: MyType
  method2: MyType



и мы в другом совершенно месте программы, даже не имея исходников MyClass, можем сделать так:

object Helpers {
  implicit class MyClassWithMethod3(myCl: MyClass) {
    def method3(): Unit = {
      myCl.method2()
      println("executed method2 and printed this message")
    }
  }
}


после чего импортируем Helpers._ можем писать код вроде
import Helpers._

MyClass obj = new MyClass
obj.method3()


Скала при этом неявно сделает необходимые преобразования. А программист работает с классом MyClass так как будто он имеет метод method3, хотя в оригинальном исходном коде MyClass method3 отсутствует.

Преобразование будет работать только там где сделан import Helpers._

---------

В хаскеле не то что бы имя hello относится к модулю. Если ты банально импортируешь два модуля с одинаковыми функциями то при вызове возникнет конфликт имёт. Эта ситуация простая, решается переименованием при импорте как я показал выше.

Но реализация указанной задачи - сделать свой тип представителем некоторого интерфейса (скажем интерфейс AFoo содержи функцию hello) тоже решается: просто указыаешь что твой класс является инстансом AFoo, при этом ты можешь указать свою реализацию hello, а можешь использовать дефолтную от класса AFoo (если таковая предоставляется классом AFoo):
instance MyClass AFoo where
  hello = println "hello from MyClass"


instance MyClass AFoo -- используем реализацию по умолчанию


Понятное дело, что если кто то где то в каком то классе (ClassB) определил ф-цию hello то этот класс не считается представителем AFoo пока явно не объявишь ClassB представителем AFoo. И использовать ClassB вместо представителя AFoo без объявления представителем не получится, даже если их ф-ции hello совпадают по типам.

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 2)
Ответ на: комментарий от AndreyKl

Преобразование будет работать только там где сделан import Helpers._

Спасибо! В C# вроде тоже подобное делается и тоже с ограничением на import. Похоже, что нужно в этом ключе и действовать, вопрос лишь в нюансах. Например, чем вводится имя в область видимости.

Но всё же - неужели в голанге именно так, как я понимаю? Я всё жду, что мне кто-нибудь мозг на эту тему прочистит :) Иначе очень уж ламерский этот голанг.

Мне на самом деле нравится утиность из голанга, но нужна возможность начать с утиности (не отвлекаться на архитектуру, пока продукт в альфе) и потом перейти к явной декларации.

Пока в Яре есть только родовые функции (не принадлежащие классу) и методы расширения. Обычных методов нет. Чтобы создать метод, пишешь функцию ТипₒИмя-метода. Такой механизм понадобился для доступа к полям лисповых структур, но придётся от этого уходить из-за вот этой проблемы конфликтов.

Понятное дело, что если кто то где то в каком то классе (ClassB) определил ф-цию hello то этот класс не считается представителем AFoo пока явно не объявишь ClassB представителем AFoo.

den73 ★★★★★
() автор топика

Я пытаюсь написать видео плейер со звуком, и мне нужно реализовать в одной структре оба интерфейса. У каждого из них есть метод Извлечь() без параметров.

...

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

В Go методы не принадлежат интерфейсам, они принадлежат структурам, соответственно нельзя для одной структуры реализовать метод с одинаковым именем и сигнатурой дважды. Интерфейсы описываются как бы «сбоку», поверх этого, по мере необходимости.

korvin_ ★★★★★
()
Ответ на: комментарий от den73
module A (Foo(..)) where 

class Foo a where
  foo :: a -> Int
module B (Foo(..)) where

class Foo(..) where
  foo :: a -> Int
module C where
import qualified A as A
import qualified B as B

data C = C

instance A.Foo C where foo _ = 0
instance B.Foo C where foo _ = 1

test :: (Int,Int)
test = (A.foo C, B.foo C)
qnikst ★★★★★
()

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

urxvt ★★★★★
()
Ответ на: комментарий от den73
type audioWrapper struct {
}

func (w *audioWrapper) Extract() {
 // Do something
}

type videoWrapper struct {
}

func (w *videoWrapper) Extract() {
 // Do something
}

type decoder struct {
   Audio *audioWrapper
   Video *videoWrapper
}

func (d *decoder) Decode(){
   d.Audio.Extract()
   d.Video.Extract()
}

anonymous
()
Ответ на: комментарий от den73

вот в haskell так, как оно будет, подозреваю, что если в Go есть квалифицированные имена то можно как-то так же.

qnikst ★★★★★
()
Ответ на: комментарий от qnikst

если в Go есть квалифицированные имена

Есть.

то можно как-то так же.

Нельзя. Методы в Go не являются top(package)-level-декларациями.

korvin_ ★★★★★
()
Ответ на: комментарий от den73

Дык я тоже думаю, что должно бы

Плохо думаешь.

korvin_ ★★★★★
()

Корвина в игнор, на тему галку, голанг в топку :)

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Спасибо!

Пжл, рад что пришлось ко двору.

Про голанг ничего не скажу, кроме того что по твоему описанию это странно выглядит.

А вот про утиность скажу: кажется, что нужна фича вроде 'пометить как «да и хрен с ним»'.

Т.е. у меня часто такой ход работы: я пишу одну часть (предполагая что будет другая, связанная). Потом принимаюсь за вторую. И вот когда пишу втору, я часто ломаю интерфейсы. Потому что оказывается что несмотря на то что пытался спланировать, всё таки на 100% у меня не вышло. Может это я такой, не знаю.. но всё таки: т.е. для того чтобы скомпилять это дело в рабочее состояние надо пойти и поправить первый модуль. Иногда такие правки тривиальны (вроде того что называлось afoo, а теперь aFoo). Иногда довольно нудны. Вот когда они нудны, хочется забить и не отвлекаться, потому что сейчас сосредоточен на другом. Было бы удобно если бы можно было добавить коммент и сказать «да и хрен с ним». И система попробовала бы собрать «как может».

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

Кажется, это сократило бы цикл разработки.

В php и javascript вот такая вот возможность делает цикл «поправил-поглядел» очень коротким. Правда там возникает другой ад - пока ты по всем веткам не пройдёшь, быть уверенным что ничего не сломалось нельзя. Очень трудозатратно, просто ад. Без тестов как без рук. Но (юнит)-тесты вместо типов конечно тоже не выход потому что тесты откровенно говоря то ещё говно (из-за необходимости нетривиальной поддержки, фактически такой же как для кода, т.е. стоимость разработки и поддержки растёт так будто кодовая база выросла вдвое или около того), и не факт что риски падают сильнее. Поэтому я поклонник типов и чем типы «круче» (типа интуционисткой теории), тем лучше.

Да, я к чему - думаю что утиная типизация более проблема чем решение. Она конечно позволяет тебе разрабатывать модули без оглядки на другие модули и сокращает цикл «написал-запустил». Но на системе в целом сказывается очень негативно на мой взгляд.

А вот фича «ну его», выглядит для меня интересной. Но это моё мнение конечно.

AndreyKl ★★★★★
()
Ответ на: комментарий от anonymous

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

Это безусловно нужно. Но в определённые моменты бывает удобным сказать «не сейчас, давай сначала с этим разберёмся, а потом вернёмся к тому ворпосу».

Это сильно отличается от полного отсутствия типов и (модели акторов, как бы ортогональная она ни была здесь, ты же всё равно её влепишь, так что сразу отмету).

Полное отсутствие типов это «не ну я точно так не сделаю» и ничего кроме. С этим - к мамке, рассказывай что ты так не сделаешь.

Между динамической типизацией и системой типов даже как в си, я выберу систему как в си. Не говоря уже о окамле/хаскеле/идрисе/агде.

AndreyKl ★★★★★
()
Ответ на: комментарий от AndreyKl

И вот когда пишу втору, я часто ломаю интерфейсы.

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

den73 ★★★★★
() автор топика
Ответ на: комментарий от AndreyKl

Да тестировал, работало. Хотя я думаю, это зависит и от отправителя. А на англоязычный тоже пробовал?

den73 ★★★★★
() автор топика

Но это - мина, которая позже рванёт.

Также есть (чужая) библиотека «рефлексия», в ней есть тип рефлексирующий-субъект, у которого тоже есть рефлексирующий-субъект%дай(строка), но по смыслу это означает «дай метаинформацию такого-то типа».

Делай типажи как в расте, они статически определены.

O02eg ★★★★★
()
Ответ на: комментарий от O02eg

Я так понял, что нельзя привести, но мне, конечно же, лень читать спеки голанга. Те, кто знают, пишут, что нельзя.

Делай типажи как в расте, они статически определены.

Что делать - это уже следующий вопрос, т.к. мне нравится идея отсутствия явной декларации интерфейса и, может быть нужно что-то чуть другое. Но всё равно, спасибо за совет.

den73 ★★★★★
() автор топика
Ответ на: комментарий от AndreyKl

Это безусловно нужно. Но в определённые моменты бывает удобным сказать «не сейчас, давай сначала с этим разберёмся, а потом вернёмся к тому ворпосу».

-fdefer-type-errors может облегчить тебе жизнь.

qnikst ★★★★★
()
Ответ на: комментарий от anonymous

И где тут вывод двух разных строчек, показывающий, что вызвались разные интерфейсы?

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

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Как будет нормальный доступ к почте -отпишу,сейчас за городом.

AndreyKl ★★★★★
()
Ответ на: комментарий от den73

И где тут вывод двух разных строчек, показывающий, что вызвались разные интерфейсы?

Тут вызван был метод, который удовлетворяет одновременно обоим интерфейсам. Интерфейс - это поведение. Го не вызывает методы интерфейса, го вызывает метод структуру, которая удовлетворяет определнному инетерфейсу.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.