Пользователь
121,5
рейтинг
18 ноября 2014 в 22:09

Разработка → Golang и ООП tutorial

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



Что такое ООП?


Как правило, вопрос об объектно-ориентированности Go происходит от пугающе популярного заблуждения «ООП-язык должен иметь классы», которых в Go, как известно нет. Чуть более глубокие размышления приводят вопрошающих и отвечающих к формулировкам вроде «если язык поддерживает инкапсуляцию, полиморфизм и наследование — то он ООП», на что Алан Кей (Alan Kay), который, собственно, изобрел ООП, смотрит с нескрываемым непониманием.

Рискну предположить, что для большинства читателей каноном ООП-языка является C++, про который сам Алан Кей говорит следующее:
I made up the term 'object-oriented', and I can tell you I didn't have C++ in mind

— Alan Kay, OOPSLA '97

(Видео, кстати, отличное, посмотрите, если еще не.)

И сразу же, о том, что же подразумевалось под ООП:
OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.


Вобщем, если углубиться в тему, и не настаивать, что ваша любимая книжка по ООП самая верная, то идея была была все же в переносе опыта восприятия реального мира в модели разработки кода. В мире есть объекты, у объектов есть свойства и поведение, объекты могут взаимодействовать. Точка. Мы так понимаем мир, в конце концов. Все остальные определения и понятия, появившиеся потом — лишь способы реализовать эту простую концепцию.

Go не любит late binding, но любит messaging, а концепция «свойств и поведения» объектов реализована великолепно, и это дает повод называть Go великолепным ОО-языком.

Субъективно — после полутора лет работы с Go, иметь принципиальное разделение на «свойства» и «поведение» кажется самым естественным способом представления сущностей, и удивляешься, почему к этому не пришли в мейнстримовых языках раньше. Классы с методами и свойствами в одной куче мне искренне кажутся сейчас неудобным и старым атавизмом.

ООП на примере


Возьмем какой-нибудь приближенный к жизни пример, вместо обычных Dog и Animal ) Например, вы — бывший напарник Сноудена и пишете систему, мониторящую приватную информацию известных личностей :) Вам нужно получить все сообщения, в которых упоминалась фраза «если бы бабушка». Сообщения могут быть голосовыми, skype, e-mail, твиттер сообщениями или какими-нибудь еще.

В class-oriented языках, мы бы скорее всего создали класс MessageSource с виртуальным методом LookupWord(), который бы возвращал объекты класса Message, Для работы, к примеру, с Twitter или Skype мы бы наследовались от MessageSource, имплементировали LookupWord() и назвали бы класс TwitterSource для Twitter, и SkypeSource — для Skype. Привычно и понятно всем, кто привык к классам.

В Go мы можем выразить это более прямолинейно — отдельно описать свойства, а отдельно поведение, и использовать композицию вместо привычного наследования, которого в Go нет.

Структуры

Сначала опишем свойства — для этого создадим структуры, содержащие необходимые поля для свойств объектов. Структуры в Go — основной способ создания своих типов данных.

type Message struct {
	Data      []byte
	MimeType  string
	Timestamp time.Time
}

type TwitterSource struct {
    Username string
}

type SkypeSource struct {
	Login         string
	MSBackdoorKey string
}

Конечно, реальная имплементация будет содержать больше интересного, но идея понятна. Message описывает данные о сообщении, TwitterSource и SkypeSource — данные об источниках сообщений.

Видимость

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

Если мы разрабатываем API и хотим скрыть поле — просто называем его username, вместо Username. И никто не запрещает нам создать геттеры и сеттеры. В Go принято использовать форму X() и SetX() для геттера и сеттера, соответственно.

Интерфейсы

Теперь займемся поведением. Первое, что приходит на ум — это источник сообщений должен уметь искать слова. Отлично, так и запишем:

// Finder represents any source for words lookup.
type Finder interface {
    Find(word string) ([]Message, error)
}

Это распространненая практика, когда интерфейс с одним методом называют именем этого метода + 'er': Find -→ Finder. Большинство интерфейсов в Go описывают 1-2 метода, не больше.

Теперь, любой тип данных, для которого будут определены методы Find() с вышеприведенной сигнатурой — автоматически будут удовлетворять интерфейс Finder. Duck typing — если плавает, как утка, выглядит, как утка, и крякает, как утка — то это утка.

Методы

Методы в Go — это функции, определенные для конкретного типа. Можно представлять методы, как методы класса, а можно, как обычные функции с указателем (или копией) на this/self. Объект, который будет «this», указывается в скобочках между ключевым словом func и названием.

func (s TwitterSource) Find(word string) ([]Message, error) {
    return s.searchAPICall(s.Username, word)
}

func (s SkypeSource) Find(word string) ([]Message, error) {
    return s.searchSkypeServers(s.MSBackdoorKey, s.Login, word)
}

Только не используйте имена this и self — это ненужная калька с других языков, в Go так не пишут.

Создав эти методы, мы, фактически получили два объекта, которые могут использоваться как Finder.

Немного анимации для разнообразия:



Теперь соберем все вместе:

type Sources []Finder

func (s Sources) SearchWords(word string) []Message {
    var messages []Message
    for _, source := range s {
        msgs, err := source.Find(word)
        if err != nil {
            fmt.Println("WARNING:", err)
            continue
        }
        messages = append(messages, msgs...)
    }

    return messages
}

Мы добавили новый тип Sources — массив из Finder-ов — всего, что может давать нам возможность поиска. Метод SearchWords() для этого типа возвращает массив с сообщениями.

Литералы

В Go любой объект можно инициализировать при декларации с помощью литералов:

var (
    sources = Sources{
        TwitterSource{
            Username: "@rickhickey",
        },
        SkypeSource{
            Login: "rich.hickey",
            MSBackdoorKey: "12345",
        },
    }

    person = Person{
        FullName: "Rick Hickey",
        Sources: sources,
    }
)


Встраивание

Личность, в представлении NSA состоит из имени и источников приватной информации о ней ) Добавив безымянное поле типа Sources мы используем embedding — «встраивание», которое без лишних манипуляций позволяет объекту типа Person напрямую использовать функции Sources:

type Person struct {
    FullName string
    Sources
}

func main() {
    msgs := person.SearchWords("если бы бабушка")
    fmt.Println(msgs)
}

Если мы переопределим метод SearchWords() для Person, то он будет иметь приоритет, но у нас все равно остается возможность вызвать Sources.SearchWords().

Вывод

Сейчас, вывод программы будет выглядеть примерно так:

$ go run main.go
WARNING: NSA cannot read your skype messages ;)
[{[82 101 109 101 109 98 101 114 44 32 114 101 109 101 109 98 101 114 44 32 116 104 101 32 102 105 102 116 104 32 111 102 32 78 111 118 101 109 98 101 114 44 32 208 181 209 129 208 187 208 184 32 208 177 209 139 32 208 177 208 176 208 177 209 131 209 136 208 186 208 176 46 46 46] text/plain 2014-11-18 23:00:00 +0000 UTC}]


Это стандартная схема строкового представления структур. В стандартной библиотеке широко используется простой интерфейс Stringer, который определяет один метод — String() string. Любой объект, который реализует этот метод, автоматически становится Стрингером и при использовании в стандартных функциях вывода, будет отображаться тем способом, который реализован этим методом.

К примеру:

func (m Message) String() string {
	return string(m.Data) + " @ " + m.Timestamp.Format(time.RFC822)
}

Теперь вывод fmt.Println(msg) приобретет более приятный вид:

$ go run main.go
WARNING: NSA cannot read your skype messages ;)
[Remember, remember, the fifth of November, если бы бабушка... @ 18 Nov 14 23:00 UTC]


Код целиком: play.golang.org/p/PYHwfRmDmK

Выводы


Так является ли Go ОО-языком?
Безусловно, возможно даже одним из самых ОО-х, в понимании Алана Кея.

Но даже если нет, и Алан Кей не одобрит подход Go — какая разница, если OO-дизайн Go вас восхищает и дает возможность эффективно переносить проблемную область на язык разработки?

Keep calm & write you next program in Go.

divan0 @divan0
карма
128,0
рейтинг 121,5
Пользователь
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (65)

  • +2
    Спасибо за интересную статью, заинтересовался Go.

    А я открыл для себя ООП без классов в Lua. Тоже очень красивая по-своему система.
  • +3
    И отличным дополнением к данной теме будет эта хабрастатья: Является ли Go языком ООП?
  • +2
    если бы бабушка... писала на Go, как бы изменился мир…

    А вообще интересен разбор и анализ межпроцессового обмена данных в Go (не межпотоковый, го-рутинный, а именно межпроцессовый).
    • +1
      На самом деле это тема не совсем Go-specific, различных вариантов много:
      начиная от golang.org/pkg/net/rpc/ и github.com/OneOfOne/netchan
      и заканчивая github.com/gdamore/mangos на любой вкус, как говорится.
    • +1
      Ну как бы любой интерфейс, который вам предоставляет ОС. В случае с Linux это может быть простейший локальный сокет. С Windows я думаю все по сложнее, но тем не менее реализуемо.
  • 0
    Go не любит late binding, но любит messaging, а концепция «свойств и поведения» объектов реализована великолепно, и это дает повод называть Go великолепным ОО-языком.

    Что за глупости? Что значит «не любит late binding, но любит messaging»? Интерфейсы и вызов методов конкретных типов через интерфейсы и есть late binding.
    • +2
      Интерфейсы и вызов методов конкретных типов через интерфейсы и есть late binding.

      И да, и нет.

      Late-binding — это больше о внутренней реализации, и в Go (по крайней мере, в gc-компиляторах: 6g, 8g) используется смешанный вариант — что-то среднее между early-binding и late-binding: таблицы методов для interface- и conrete-типов генерируются на этапе компиляции, а финальная таблица создается на лету, при этом кешируется после первого просчета.
      Так что тут в Go mixed-binding ;)
      Вот тут подробно расписано: research.swtch.com/interfaces

      Касательно messaging — насколько я знаю, Алану Кею были близки идеи CSP, и реализация в Go, наверняка близка, к тому, что он хотел бы увидеть в OO-языках, но я не углублялся, чтобы ненужные холивары не разводить :)
  • –21
    Golang это недо ООП язык.
    у ООП есть чёткое определение: инкапсуляция, полиморфизм, наследование.
    Golang реализует только часть.
    • +18
      «Статью не читай, сразу комментруй» что ли? Это в первом же абзаце обсуждается.
  • 0
    Пример какой-то не полный. Зачем Person включает Sources и зачем ему FullName, не понятно. Но в целом очень хорошо, спасибо.
    • 0
      Просто чтобы продемонстрировать embedding.
      А с примером, да, сумбурно немного — оказалось, не так это просто придумывать красивые, показательные, простые и, одновременно, ёмкие примеры :)
  • +2
    Вообще например такие фичи как embedding и использование в нем не только непосредственно объектов, но и указателей/ссылок на объекты, это одна из тех удивительно простых вещей, про которые думаешь когда впервые узнаешь: ну почему это еще в Си не было сделано, это же так просто и красиво!
  • +3
    func (s TwitterSource) Find(word string)
    

    Все таки receiver лучше через указатель передавать, а то копироваться будет.

    func (s *TwitterSource) Find(word string)
    


    • +2
      Пусть копируется )
      Это дешево, и для этого примера не релевантно.
      • +8
        Товарищи минусующие, я, конечно, понимаю, что у вас есть свое представление о дешевизне копирования и я даже готов послушать ваши аргументы, но эта тема поднималась в Go-комьюнити с 2009-го года не раз и ответ на вопрос «использовать поинтер или значение в качестве ресивера» даже вынесено в FAQ — рекомендую познакомиться: golang.org/doc/faq#methods_on_values_or_pointers.

        Для тех кому вправду интересно: pointer в качестве ресивера используется если значение наверняка нужно менять или если тип ресивера — большая структура. Для базовых и простых типов, которые не нужно менять — рекомендуется использовать значение. Там есть еще парочка моментов, которые стоит учитывать, но в целом как-то так.

        В примере статьи значение менять не нужно и тип очень простой. Минусуйте дальше :)
  • 0
    Правильный краткий ответ на вопрос: «нет, не является, но это не важно» :)
    А язык хороший.
  • +2
    Хм, прикольная игрушка на фотке. Сразу же нашел в Украинском магазине и заказал.
    • 0
      Подскажите магазин(prooflink)
    • +1
      Да, gopher у них удачный очень вышел. Мне больше намного нравится, чем Google-овский — на конференции dotGo в Париже раздавали. Оно такое чувство, будто его камазом сбили и переехали ) А этот крутой. Я его, кстати, подарил девочке из Будапешта, которая движок Prezi писала — она на Lviv IT Arena делала презентацию, как Go в Prezi используется, и это была одна из самых мимимишных презентаций, которые я видел )))

    • +3
      Я, кстати, как-то этой игрушке фотосет знатный устроил :) Вот такие карточки получились:









  • +1
    Некоторые ругают Go, что нельзя
    func (s string) Find(word string) ([]Message, error) {...}

    а надо
    type Plaintext string
    func (s Plaintext) Find(word string) ([]Message, error) {...}
    • +1
      Я думаю, это правильное решение. Допустим, разрешили built-in типам добавлять свои методы. Что будет, если чей-то package, включенный в мой код переопределяет метод String() для int? Или два разных package-а, реализуют одинаковый метод для built-in типа. Как такие вещи разруливать?

      Кроме того, новое поведение у типа — подразумевает, что этот тип представляет некую новую сущность. Ну там «источник данных», а не просто «строка». Тоесть это разные сущности, и правильнее дать им разные имена.
    • 0
      А вы не подскажете, как внутри

      func (s Plaintext) Find(word string) ([]Message, error) {...}
      

      вызвать имплементацию Find у string, если такая имеется?
      что-то не нашел в интернетах.
      • 0
        «имплементацию Find у string» — это поиск подстроки, я так понимаю? Операций на строках достаточно много и они вынесены в стандартную библиотеку, в package strings: golang.org/pkg/strings
        Find в данном случае вам нужно или Contains (содержит или нет) или Index (позиция, с которой содержит).
        • 0
          не важно, что такое Find. Важно, что мы хотим вызвать одноименный метод.

          type AliasOfT T
          
          func (s T) MethodABCD() { .../* надо вызвать это */ ...}
          
          func (s AliasOfT) MethodABCD() { super.MethodABCD() }
          


          Что нужно написать вместо super.MethodABCD(), чтобы вызвать MethodABCD() для T?
          Если мы вызовем просто MethodABCD(), мы рекурсивно закрутимся.
          • 0
            Приведите к нужному типу и вызывайте: T(s).MethodABCD()

            Но вообще немного weird дизайн. Я такого использования не встречал, это калька с class-oriented языков.
            • +1
              Спасибо.

              Давеча boltdb заюзывал и надо было *DB разными методами разукрасить, специфичными для проекта.
              Мне так понадобилось метод (… *DB) Close() «переопределить».

              В итоге, я не догадался, и выкрутился контейнером над *DB. Оказалось потом не зря.
          • +1
            func (s AliasOfT) MethodABCD() { T(s).MethodABCD() }
            • 0
              спасибо
  • +1
    В Go используется правило регистра первой буквы имени — если название начинается заглавной буквы — это public-доступ, если со строчной — private. Вначале может показаться посягательством на свободу слова неудобством, но с первых строк на Go, понимаешь, насколько это было удобное решение.

    … и если ты хочешь сделать публичный метод приватным или приватный публичным — нужно пройтись по всему проекту и сменить регистр. Оооок, очень удобно. И правильно, нечего без IDE программировать на серьёзных языках.
    • 0
      Ну да, рефакторить код лучше не ручками :) GoRename конкретно этот use-case делает очень простым.
      • +3
        Изобрели проблему и героически её побороли. Оооок.
        • +4
          Если вы чаще, чем раз в месяц меняете туда сюда видимость методов, то, наверное да, Go вам не подходит. А если еще и «я так не привык» для вас затмевает все остальные преимущества, которые дает Go (и которые даже не затрагивались в этой статье) — то тем более )
          • 0
            Очевидно, сам Go здесь ни при чём — это разработчики на Go придумали такую, кхм, странную концепцию. А Двораком не пользуетесь?
            • +4
              Извините, что игнорирую ваш сарказм, но расскажу один момент.

              Когда я только познакомился с Go, самым странным для меня было то, что компилятор выдавал ошибку на неиспользованный импорт или неиспользованную переменную. И, более того, изменить это поведение нельзя было — ну там, какой-то опцией командной строки например.
              WTF, подумал я — что за ограничение свободы слова и посягательство на базовые права человека? Да какое право они имеют мне диктовать, оставлять или нет неиспользованные импорты? Хочу и оставляю, я свободный человек! Да и вообще, это же офигеть, как неудобно. Что это они вообще придумали, идиоты. Я то знаю как нужно — я же привык как в других языках.

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

              Роб Пайк, кстати, рассказывал подробно по всем дизайнерским решениям языка — что за этим стояло, сколько месяцев или лет они обсуждали все возможные решения и почему пришли именно к такому. У меня есть огромное доверие к Кену и Робу — думаю, их опыт в программировании и работы с разными программистами и командами больше моего в огромное количество раз, и когда мне вначале, еще не испробованная фича кажется «неправильной» — скорее всего, я просто сноб и боюсь изменить свои убеждения. Но хорошо, что я не боюсь :) Те вещи, за которые я бы раньше на C++ или Python даже не взялся, сейчас я пишу за пару дней. Чего и вам желаю. :)
              • 0
                github.com/bradfitz/goimports крайне облегчает жизнь
                • +1
                  Ну, тогда еще не было goimports :)
                  Кстати, я им немного попользовался и вернулся обратно на go fmt (при сохранении всмісле) — во-первых, как-то уже привык добавлять импорты сам перед тем, как использовать какие-то вещи из них — не добавив, банально автодополнение не сработает, а удалять через :GoDrop легче простого, не сходя с нужной строки. А во-вторых, там есть баг, до которого у меня руки не дошли разобраться — но imports в некоторых случаях, неверно угадывает путь и вставляет неправльные import-ы.
                  • 0
                    Я его использую совместно с go fmt.
                    Насчет бага — да столкнулся.
                    • 0
                      Зачем совместно? goimports ведет себя также, как и gofmt + исправляет импорты.
            • 0
              Концепция кстати вполне интуитивная. Немного контринтуитивно, что вот так например нельзя.
              func foo() 
              {...
              }
              • +1
                Это другая тема, это про точки с запятой. Опять же, это все в спецификации, которая читается целиком за час.

                Формально, в Go используются точки с запятой, но они не обязательны — лексер их сам расставляет, руководствуясь простыми правилами. В данном примере лексер ставит точку с запятой после ')' — поэтому и ошибка. Этот пример, кстати, там же, в спеке языка :)
                golang.org/doc/effective%5Fgo.html#semicolons
                • 0
                  Спасибо, читал:)) Все же
                  -встроенные типы особенней самодельных
                  -встроенный sort() может быть полиморфным, а самодельный нет
                  -на вопрос «When generics?» Rob Pike отвечает «We are done.» и представляет go generate
                  это все прекрасно аргументировано и с этим можно нормально работать и Pike — commander и голова. Но расстановка семиколонок и навязанный стандарт gofmt — для меня слишком proprietary and opinionated. Go сейчас мой любимый язык, более того Plan9 acme by Rob Pike — мой рабочий редактор, однако диктат отцов основателей местами чрезмерный что ли.
                  • +1
                    Согласен, что встроенные типы особым статусом наделены, но неспроста же.
                    Насчет generics и «commander и голова» — ну, это же не «Пайк единолично не хочет generics» :) Хочет же, хоть и неохотно ) Но не видит пока-что способа действительно красиво это сделать.
                    Я лично пока-что не понимаю, какой есть use-case, когда необходим generics. Те примеры, которые я видел решаются интерфейсами, а иногда через reflection (что не слишком красиво, согласен). Я продолжаю искать в реальных задачах способ наткнутся на ситуацию, когда упрусь в необходимость generics — но пока что этого не случалось, и адекватных ответов по этой теме я не видел. Люди, как правило, просто хотят решать задачу тем же путем, которым они привыкли в прошлых языках.

                    Acme гляну, интересно )
                    • 0
                      Reflection'ы же медленные, и с кешированием код получается несколько громоздким.
                      • +1
                        Согласен. Но все равно хочу увидеть задачу, в которой Go by design без дженерикс не справляется.
                        Вот, к примеру, хорошая реализация B-Tree, которая зачастую в других языках решается с помощью generics. По-моему, очень красиво и элегантно:
                        godoc.org/bitbucket.org/santucco/btree
                    • 0
                      Я лично пока-что не понимаю, какой есть use-case, когда необходим generics.

                      func Sort(func compare(a,b T) bool, []T) []T
                      func Filter(func fine(a T) bool, []T) []T
                      func Map(func apply(a T) Ta, []T) []Ta
                      func Fold(func combine(a, b T) Tc, []T) Tc

                      Стандартный набор функциональщины (сортировка, фильтр, мапинг, свертка) на слайсе в первую очередь
                      • 0
                        Воот — вот об этом я и говорил:
                        Люди, как правило, просто хотят решать задачу тем же путем, которым они привыкли в прошлых языках.


                        Посмотрите, как реализован stdlib-овский sort, или приведенный выше B-Tree.
                        Это просто другой подход к проблеме. Можно спорить о том, какие минусы и какие плюсы этого подхода, и даже спорить — а считать ли этот подходит тоже как вид generics, но уж точно на «Go тут фейлится» не подходит.
                        • 0
                          Ну и не убедительные примеры
                          • stdlib sort использует reflection, что медленно и велосипед
                          • godoc.org/bitbucket.org/santucco/btree построен на encoding/binary, то есть не typesafe, а скорее untyped
                          а почему не реализовать полноценный Хиндли-Милнер в компиляторе? В Rust же реализован. Я так понимаю Pike декларировал абстрактный механизм препроцессора go generate и колбасьте что хотите, а компилятор должен быть быстрым))
                          • +1
                            stdlib sort использует reflection, что медленно и велосипед

                            В каком месте и для чего sort использует reflection? Желательно прямо строчку кода укажите :)

                            godoc.org/bitbucket.org/santucco/btree построен на encoding/binary, то есть не typesafe, а скорее untyped

                            нет, encoding/binary используется исключительно для чтения/записи в с storage (io.ReadWriter) — это задача сериализации/десереализации данных. Сам же b-tree алгоритм построен исключительно на использовании интерфейсов.

                            Поймите, generics — это написание алгоритмов в type-независимой манере. Go дает возможность это делать с помощью интерфейсов — ваш тип должен реализовать методы, необходимые для работы алгоритма — и наслаждайтесь. В С++ эту проблему решили иначе — темплейтами, в Java — еще иначе — и везде за счет потерь в скорости, читаемости и/или эффективности. И люди, привыкшие к тем или иным решениям, просто чувствуют себя без них неудобно в новом языке — отсюда и эти «Пайк, спаси, введи темплейты». Но это неправильно.

                            Или вот еще — когда для каких-то особых случаев людям советуют использовать reflect или interface{} с кастингом — они начинают рассказывать про «это медленно» и вообще, мы хоть это за zero costs получить. Блин, конечно медленно — а в в других языках, generics, думаете бесплатно получается?
                            В конце-концов да, тем кто прямо без темплейтов жить не может — (уже почти) есть go generate и были какие-то ужасные (как по мне) решения вроде gonerics.

                            Вобщем я все еще в поиске реальных use-кейсов, где Go без имплементации C++/Java-style generics не справляется красиво.
                          • 0
                            А насчет Хиндли-Милнер — не знаю, и мне сложно проанализировать, поскольку с языками его использующего не работал и не сильно понимаю ценность.
                            • +1
                              Хиндли-Милнер нужен, чтобы
                              //имея
                              func Btree(node T) Btree T {...}
                              //написать
                              var bt Btree int 
                              //так же легко как
                              var sl [] int
                              //
                              //и typesafe
                              type T; //type parameter
                              func twotime([]T) []T {...}
                              //или
                              func twotime(bt Btree T) bt Btree T {...}
                              //вместо untyped
                              func twotime(sl []interface{}) []interface{} {...}
                              

                              C примерами кстати убедили, невнимательно исходники читал, виноват))
                              • 0
                                Вот тут Russ Cox рассказывает про Hindley-Milner, почему это не есть так гуд, как это кажется в теории:
                                To take one example, Hindley-Milner type inference is very well understood and I think generally accepted in the programming language community as a good feature. But new ML programmers inevitably hit the situation where changing one part of a program causes a mysterious type error in a seemingly unrelated part. The solution is that in practice one writes type signatures for most functions. Hindley-Milner type inference is beautiful research, but it’s not as beautiful in practice. In Go, the only type inference is that if you say var x = e, the type of x comes from e. This rule is simple and predictable and doesn’t suffer from the kinds of “spooky action at a distance” of more complex type inference algorithms. I miss having more complex inference sometimes, but Go’s simple rule handles at least 90% of the cases you care about, with none of the unpredictability or mystery.
                  • +3
                    Но расстановка семиколонок и навязанный стандарт gofmt — для меня слишком proprietary and opinionated.

                    А по-моему, gofmt — одна из самых замечательных вещей в Go. Никаких тебе больше code style guides. Никаких споров о tabs vs spaces и о размерах отступов. Не надо больше думать, переносить фигурную скобку на следующую строчку или нет.
                    Весь код единообразен и глаз быстро привыкает именно к такому форматированию, что крайне положительно сказывается на легкости чтения.
                    • 0
                      Подпишусь. Сколько нервов и споров уходило в других языках на «как надо расставлять скобочки», и сколько на маты, когда смотришь код людей, которые класть хотели на форматирование.
                      В Go в итоге 99.9% кода гарантированно читабельно и аккуратно.
        • +1
          В Python похожая реализация, private поля начинаются с двух подчеркиваний. Дело привычки.
          • –3
            Что? В какой момент в питоне появились приватные поля? Не вводите людей в заблуждение. Во-первых, в большинстве проектов «приватные» поля принято начинать с одного нижнего подчеркивая, а не с двух. Во-вторых, два нижних подчеркивания стараются использовать только для служебных «магических» методов вроде __len__(). И в-третьих, и те и другие все равно можно изменять, просто с двумя подчеркиваниями добавляется имя класса (что кстати ломает нормальное использование в дочерних классах).
            • 0
              Вы не правы. 1) С одного подчеркивания обычно называются «внутренние» поля, которые не нужны клиенту, но могут быть нужны наследникам. По идеологии тут ближайшее — protected поле, хотя это только соглашение имен, защиты на уровне языка нет.

              2) Использование двух подчеркиваний ведет к автоматическому переименованию поля к виду _<classname>__<fieldname>, таким образом исключается возможность обратиться к полю родителя, не используя интроспекции (так как self.__field у дочернего класса приведет к обращению к self._child__field).
              Извне пользоваться прямым именем, начинающимся с __ также запрещено.
              Таким образом, полем в явном виде можно пользоваться только внутри класса. Такое поведение делает такие поля приватными.

              3) Магические методы имеют имя __<имя>__, приватные не имеют двух подчеркиваний в конце.

              см. PEP8 (__double_leading_underscore)
              • 0
                По поводу пункта 2. Создайте класс Foo и определите поле self.__bar в __init__(). Создайте foo = Foo() а потом спокойно присвойте foo._Foo__bar любому значению. Все прекрасно работает без всякой интроспекции (кстати, что такое интроспекция в питоне? гм, getattr() с __dict__-ом чтоли?). Так что ничего они не приватные. Приватность — это невозможность изменить поле извне, в т.ч. в дочерних классах. И раз уж об этом зашла речь, окей, в том числе для этого придумали __slots__.

                PEP8 это, конечно, здорово, но в реальном мире, в котором написаны к примеру matplotlib, twisted и tornado никто никогда в здравом уме такую свинью с двумя подчеркиваниями разработчикам не подкладывает. Можете привести пример известного, крупного проекта, где «приватные» поля начинаются как пепе?
                • 0
                  Хотя нет, даже возня со __slots__ не поможет. Нельзя добиться приватности в Питоне, не тот язык)
                • 0
                  > foo._Foo__bar
                  Это не прямое обращение по имени, а скорее хак. Да, это не интроспекция, согласен.

                  Но не в этом суть, это просто механизм языка, который позволяет сделать переменную в рамках одного класса, которая не наследуется.
                  Если что, в Java тоже можно достать приватное поле. От этого они не менее приватные.

                  Ну вот специально поискал по OpenStack'у, нашел кучу примеров использования. У вас какое-то предубеждение к этому) Вполне нормальный механизм.
                • 0
                  между прочим, поискал еще по matplotlib, нашел как пример:
                  github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/backends/backend_svg.py#L97
                  Не повсеместно, но присутствует ;)

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