язык гоу
Defer в Си
Мне очень нравится конструкция defer в «Гоу» — очень удобно для управления ресурсами, можно рядом с получением
Оказывается в прошлом году её предложили добавить и в Си (чему я очень рад, потому что в последнее время больше всего, пожалуй, пишу именно на нём),
Управление «Алисой»
Хочу попробовать решить одну прикладную задачу, пока не буду описывать какую. Для этого надо научиться управлять яндексовской «Алисой» из локальной сети. В идеале — проговаривать произвольную фразу. Почитал интернет, посмотрел как это делается.
Официальный документации нет, но хорошие люди провели обратную разработку и задача выглядит как будто бы не очень сложно. Получаем токен, в сети через mDNS по имени _yandexio._tcp.local. ищем нужную колонку, находим, команды отдаём по протоколу WebSocket.
Так как я сейчас стараюсь больше времени проводить за книгой, чем за компьютером, всё, что успел — освоить модуль zeroconf для Гоу, чтобы по имени колонки получать её
Получившийся код выглядит так:
import "github.com/grandcat/zeroconf"
// …тут остальные импорты…
const Service = "_yandexio._tcp"
const Domain = "local."
func getAlisaAddr(instance string) (*zeroconf.ServiceEntry, error) {
resolver, err := zeroconf.NewResolver(nil)
if err != nil {
log.Fatalln("Failed to initialize resolver:", err)
}
entries := make(chan *zeroconf.ServiceEntry)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
err = resolver.Lookup(ctx, instance, Service, Domain, entries)
if err != nil {
return nil, err
}
entry := <-entries
cancel()
<-ctx.Done()
if entry == nil {
return nil, fmt.Errorf(
"Failed to lookup for service instance '%s'",
instance
)
}
return entry, nil
}Теперь надо разобраться где взять токен и как выглядят команды.
Модуль PHP на Go
Потешную штуку показали на днях —
Пока ехал домой, думал что бы такое обернуть.
Ссылка давно протухла, но «Вебархив», к счастью, имеет копию страницы — статья называется «Ridiculous
Программа там на Си, я её минимально подпилил, чтобы она нормально компилировалась, завернул через cgo в Гоу, а оттуда при помощи
Заодно набросал небольшой бенчмарк и залил всё на гитхаб, если
Зато попробовал
Структуры в Си и CGO
По горячим следам хочу написать какая забытая мною особенность языка Си вчера ввела меня в заблуждение и почему несмотря на то, что я работал с сишной структурой, CGO так как будто не считало.
Для начала посмотрим как определяются и передаются структуры в Си. Полную спецификацию я приводить не буду, посмотрим на два распространённых способа. Первый используется в этом небольшом примере:
#include <stdio.h>
#include <math.h>
struct point {
double x, y;
};
double point(struct point p) {
return sqrt(pow(p.x, 2) + pow(p.y, 2));
}
int main() {
printf("%g\n", point((struct point){1, 2}));
}Тут можно обратить внимание на две особенности —
Это происходит потому, что имена структур обитают в своём собственном пространстве имён, на его использование и указывает struct. Там же живут имена объединений (union) и перечислений (enum). Имена функций находятся в общем пространстве, так что два таких определения с одним именем друг другу не мешают.
Структуры, объявленные таким образом видны через CGO компилятора Гоу как C.struct_name (C.struct_point в данном случае). К сожалению, тут есть особенность, о которую я споткнулся — если запрашиваемая структура не определена, то Гоу не выдаст ошибки, мы просто получим пустую структуру:
package main
import ("C"; . "fmt")
func main() {
Println(C.struct_undefined{}) // ошибки не будет
}Но программисты — люди ленивые, писать всюду struct неудобно. К счастью есть способ этого не делать — можно завести структуру, а потом дать ей имя в пространстве всех остальных типов. Это делается при помощи ключевого слова typedef позволяющего создавать алиасы типов:
struct point {
double x, y;
};
typedef struct point point;Теперь у нас есть два имени для одного типа — один в пространстве структур и прочего, второе — в общем пространстве. Можно даже сделать их одинаковыми, что очень удобно. Если второе имя нам ни к чему (бывают случаи, когда оно необходимо, но я не буду их тут рассматривать), то можно определить анонимную структуру и сразу дать имя этому типу в общем пространстве, так тоже будет работать.
Это нас подводит ко второму способу работы со структурами:
#include <stdio.h>
#include <math.h>
typedef struct {
double x, y;
} point;
double veclen(point p) {
return sqrt(pow(p.x, 2) + pow(p.y, 2));
}
int main() {
printf("%g\n", veclen((point){1, 2}));
}Ключевое слово struct перед типом уже не нужно, раз все имена в общем пространстве. Это же касается и CGO.
Как раз на эти грабли я и наступил — позабыл про эти все особенности. Когда я ещё писал на Си, всегда пользовался вторым способом, с именем в общем пространстве и привык к тому, что это и есть структуры.
Структуры и CGO
Обновление: в комментариях в Фейсбуке мне бывший коллега попенял, что я забыл Си и что поведение правильное — тут объявляется анонимная структура, а потом к ней определяется тип. Верно, практики на Си у меня за последние годы почти не было, вымылось всё из памяти. Так что текст ниже читайте с учётом этого обновления заметки.
Каждый раз расстраиваюсь, когда сталкиваюсь с CGO.
Документация говорит нам, что сишные структуры доступны в Гоу через конструкцию C.struct_name, а её размер — через C.sizeof_struct_name.
Я так и пытался сделать и получал ошибки:
package main
/*
typedef struct {
int x, y;
} test;
*/
import "C"
import . "fmt"
func main() {
Println(C.struct_test{}) // «{}»? Что за…
Println(C.sizeof_struct_test) // could not determine kind of name for C.sizeof_struct_test
}Упрощённый код выше кратко иллюстрирует проблему. В первом случае никакая ошибка не выводится (что ещё больше сбивает с толку), но структура
Оказывается, и я набрёл на это случайно, в ходе экспериментов, struct просто не надо писать, тогда всё будет работать:
Println(C.test{}) // «{0 0}»
Println(C.sizeof_test) // 8Если будет время, ещё поизучаю этот вопрос — посмотрю историю коммитов и всё такое, возможно была
Аналог defer в языке R
В Гоу есть интересная конструкция (defer) — её содержимое выполняется, когда заканчивает работу функция, в которой эта конструкция объявлена, очень удобно память освобождать или другие ресурсы. Я накидал небольшой пример, чтобы было понятно:
package main
import (
"bufio"
"fmt"
"os"
)
func readFile(filename string) (out []string) {
file, _ := os.Open(filename)
defer file.Close()
b := bufio.NewReader(file)
for {
if str, err := b.ReadString('\n'); err != nil {
break
} else {
out = append(out, str)
}
}
return out
}
func main() {
var lines []string
for _, line := range readFile("/etc/passwd") {
if line[0] != '#' {
lines = append(lines, line)
}
}
fmt.Println(lines)
}В примере одна функция построчно читает файл и возвращает массив строк. Её использует основная функция (main), чтобы прочитать файл с информацией о пользователях в системе, удалив оттуда комментарии — строки, начинающиеся с «#». Обработку ошибок я не стал делать — и без этого длинновато вышло.
В фунции чтения файла блок defer вызовется как только мы покинем функцию и закроет используемый файл. Такой способ лучше, чем отсутствующий «Гоу» блок try…finally, так как позволяет указать способ освобождения ресурса очень близко к началу его использования.
Есть языки, которые, на мой взгляд, решают эту задачу ещё лучше (например, Пайтон со своим with), но у Гоу своя атмосфера и выбранное решение отвечает духу языка.
«Эр» на «Гоу» совсем не похож, у языков совершенно разные идеологии, поэтому я очень удивился, обнаружив в «Эр» тот же подход, что выбран в «Гоу».Тот же пример на «Эре»:
readFile <- function(filename) {
f <- file(filename, 'r')
on.exit({
close(f)
})
return(readLines(f))
}
lines <- readFile('/etc/passwd')
print(lines[!grepl('^#', lines)])Конструкция on.exit делает тут ровно то же, что defer в «Гоу». Нельзя не заметить, что программа стала гораздо компактнее, но в многословности Гоу обвинить нельзя —
Goracle
В прошлом году немного рассказывал о взаимодействии языка «Гоу» и «Оракла» — я тогда выбирал из двух драйверов и выбрал в итоге ГОракл. Всё в нём хорошо, да вот незадача — некоторые запросы не выполнялись, падало всё с невнятной ошибкой
Разбирательство привело меня к мысли, что дело в числовых значениях — если запрос возвращал только строки, всё было нормально. Я тогда преобразовал всё при помощи TO_CHAR, разбирая внутри «Гоу» строку в число и успокоился.
На прошлой неделе мне пришлось озаботиться увеличением производительности получившего и разобрался в проблеме чуть детальнее. Не скажу, что понимаю в чём дело (в код драйвера не смотрел), но выглядит всё так, как будто драйвер падает, если на месте числа из «Оракла» может прийти NULL. Например, агрегатные функции (я пробовал сумму и среднее) падения не вызывают. Опять же, если «защитить» все числа функцией NVL — тоже всё хорошо.
Так что если вы видите при использовании этого драйвера ошибки похожие на те, что ниже
ERR=&oracle.Error{Code:22060, Message:"[-1] ORA-22060: argument [2] is an invalid or uninitialized number
ORA-22060: argument [2] is an invalid or uninitialized numberПопробуйте обернуть все числовые возвращаемые значения функцией NVL, должно помочь.
99 бутылок: Go
48. Go — язык программирования, придуманный в «Гугле». Я на нём программирую, время от времени, и довольно неплохо его уже знаю. Замечательный, на мой взгляд, язык. Этакий «Си с человеческим лицом». Особенно интересно добавлено туда ООП, да и параллельные вычисления сделаны хорошо. Единственно — не нравится реализация обработки ошибок через panic/defer.
Язык статический, строгой типизации, компилируемый. Кстати, одна из особенностей языка — исключительно быстрая компиляция.
Некоторые ребята считают, что он похож на «Паскаль». Думаю это
package main
import "strconv"
func main() {
for i := 99; i>0; i-- {
b := bottles(i) + " of beer"
println(b + " on the wall, " + b + ".")
println("Take one down and pass it around, " + bottles(i-1) + " of beer on the wall.\n")
}
println("No more bottles of beer on the wall, no more bottles of beer.")
println("Go to the store and buy some more, 99 bottles of beer on the wall.")
}
func bottles(beer int) string {
switch (beer) {
case 0:
return "no bottles"
case 1:
return "1 bottle"
}
return strconv.Itoa(beer) + " bottles"
}