Злоупотребление синтаксисом Go для создания DSL(предметно-ориентированного языка)
Go частый выбор для создания внутреностей высокопроизводительных систем, но так он так же разработан с некоторым количеством функций которые хороши для создания высокоуровневых абстракций. Вам не нужно переключаться на динамические языки такие как Ruby или Python, чтобы получить удовольствие от API или объяснительный синтакс.
Довольно часто для выражения API используется DSL(Предметно ориентированный язык). DSL это язык внутри языка который компилируется или интерпритируется внутри языка, в нашем случае Go. Если API хорошо спроектирован, DSL выглядит как его собственная спецификация специально созданная для конкретной задачи. DSL такие как CSS, SQL созданы как отдельный язык с их собственными анализаторами, но на данный момент мы сосредоточимся на одном, который построим с помощью компилятора Go, и используем внутри Go кода.
DSL используется для инфраструктурной автоматизации, модель объявления данных, построения запросов и тонны других приложений. Он может быть приятен для написания DSL для задач типа "подключил и настроил" потому, что они предлагают декларативный синтакс. Чаще чем инперативное описание, вся логика и операции нужны чтобы создать у приложения определенное состояние. DSL позволяет вам объявить желаемое состояние,параметры этого состояния и их реализацию ниже, всего лишь в шаге друг от друга. Конечный код тоже будет старатся выглядеть проще для понимания.
Мы собираемся посмотреть на то, как построить API, которое будет понятно для Go комплиятора, но выглядеть оно будет как отдельный язык. Название этой статьи выбрано потому, что мы собираемся нарушить дух Go только слегка, чтобы поправить букву закона так как нам хочется. Я надеюсь вы собираетесь быть рассудительны при применении сомнительных практик обсуждаемых далее. Я так же надеюсь изучая их, вы будете вдохновлены думать креативно о том, как писать выразительные Go API с которыми будет приятно работать и легко понимать.
Пример использования.
Мы собираемся построить просто DSL для создания HTTP посредника. Эта область отличный кандидат для DSL потому что оно наполнено общими, хорошо понимаемыми и часто переиспользуемыми шаблонами как ограничени доступа, частого ограничения, обработки сессии и так далее. Лучше всего для читалей и писателей кода который реализует эти шаблоны, если код будет читаться как декларативный файл настроек, чем как императивное изобретение колеса.
Личные типы.
Один из тонких но мощных моментов написания Go кода с образным чувством личности типов. Большую часть времени когда мы объявляем свой тип в Go, мы объявляем структуру или интерфейс. Мы так же можем объявить новый личный тип для существующих, ссылаясь на них с именем которое мы выбрали.
Это может быть чем-то простым как создание нового имени простопу типу, такому как string
для названий хостов:
type Host string
Мы также можем создать тип для набора:
type HostList []Host
type HostSet map[Host]interface{}
Теперь где нибудь в нашем коде, переменная типа HostList
будет []Host
, или на самом деле []string
под капотом, но с более понятным имененм.
Выгоду от таких типов, кроме как внешнего вида и сохраненных нажатий клавиш, это то что эти типы могут быть расширены с помощью их собственных типов. На пример:
func (s HostSet) Add(n Host) {
s[n] = struct{}{}
}
func (s HostSet) Remove(n Host) {
delete(s, n)
}
func (s HostSet) Contains(n Host) bool {
_, found := s[n]
return found
}
Как мы можем использовать HostSet
как если бы это был более сложная структура контейнера доступная через методы:
func main() {
s := make(HostSet)
s.Add("golang.org")
s.Add("google.com")
s.Add("gopheracademy.org")
s.Remove("google.com")
hostnames := HostList{
"golang.org",
"google.com",
"gopheracademy.org",
}
for _, n := range hostnames {
fmt.Printf("%s? %v\n", n, s.Contains(n))
}
}
Вот что мы получим на выходе:
golang.org? true
google.com? false
gopheracademy.org? true
Что мы тут имеем? Мы созадли абстракцию над простой мапой, мы можем использовать её как набор - у нее есть Add
и Remove
операции, и у нее есть проверка Contains
- созданные нами обороты речи, которые могут быть использованны нами в коде. Он лучше изолирован, чем простая передача map[string]interface{}
и надеясь что значение "набор имен хостов" соблюдается при доступе к map. Это решение более гибкое и явное, чем скажем:
func SetContains(s map[string]interface{}, hostname string) bool {
_, found := s[hostname]
return found
}
func main() {
s := make(map[string]interface{})
if SetContains(s, hostname) {
// do stuff
}
}
Поэкспериментируем чуток с созданием новых типов, частично для различных типов срезов и мапов, и даже для каналов. Какие обороты вы можете создать чтобы заставить работать эти типы проще и понятнее?
Высокоуровневые функции.
Go включает некоторые идеи из функционального программирования которое бесценно для создания выразительных и декларативных API. Go предлагает возможность присваивать функции переменных, для передачи функций как аргумент в другую функцию и для создания анонимных функций и закрытий. Используя высокоуровневые функции которые создают, изменяют, или строят поведение других функций, вы можете легко объединять кусочки логики и функциональности во что-то более сложное целое используя несколько выражений, чаще чем повторение или создание клубка условной логики.
Давайте построим наш пример выше по-новому. Добавим метод в HostList
который принимает функцию как входной параметр и возвращает новый HostList
:
func (l HostList) Select(f func(Host) bool) HostList {
result := make(HostList, 0, len(l))
for _, h := range l {
if f(h) {
result = append(result, h)
}
}
return result
}
Этот метод HostList
имеет эффективность создания нового HostList
для которого предоставленное условие (func f) верно. Создадим простое выражение функции для вставки в f
:
// import “strings”
func IsDotOrg(h Host) bool {
return strings.HasSuffix(string(h), ".org")
}
И используем его в наше новом методе HostList
myHosts := HostList{"golang.org", "google.com", "gopheracademy.org"}
fmt.Printf("%v\n", myHosts.Select(IsDotOrg))
Вывод будет таким:
[golang.org gopheracademy.org]
Select
возвращает только те элементы myHosts
для которых переданная функция IsDotOrg
будет возвращать true
, то есть для тех имен у которых есть ".org".
func(Host) bool
немного кривовата как параметричечкий тип и создает подпись метода Select
сложно для чтения, поэтому давайте используем наш трюк для типов, чтобы сделать его аккуратным.
type HostFilter func(Host) bool
Он делает Select
более читаемым:
func (l HostList) Select(f HostFilter) HostList {
//...
}
и добавляет выгоду с которой мы можем объявить некоторые методы HostFilter
:
func (f HostFilter) Or(g HostFilter) HostFilter {
return func(h Host) bool {
return f(h) || g(h)
}
}
func (f HostFilter) And(g HostFilter) HostFilter {
return func(h Host) bool {
return f(h) && g(h)
}
}
Если мы хотим объявить функцию которая может использовать HostFilter
метода, к сожалению нам нжуно пойти другим путём, чтобы это сделать. Чтобы функция имела верный приемни HostFilter
метода, недостаточно совпадать описанию HostFilter
, нам нужно объявить функцию как HostFilter
явно.
var IsDotOrg HostFilter = func(h Host) bool {
return strings.HasSuffix(string(h), ".org")
}
Но теперь стало ясно что мы начали делать хорошо для нашей угрозы злоупотребления синтаксисом Go. Объявление функции с помощью передачи анонимной функции в переменную дает неясное ощущение. Отметим что это не требование использовать высокоуровневую функцию или преимущество типа для подписи функции - любоая func(Host) bool
может быть назначения переменной HostFilter
или параметру. Это безрассудное объявление нужно только чтобы можно было использовать функции такие как IsDotOrg
как приемник HostFilter
метода
Однако выгода, заключается в том, что использование методов в HostFilter
функции позволяет получить нам интересный синтаксис:
var HasGo HostFilter = func (h Host) bool {
return strings.Contains(string(h), "go")
}
var IsAcademic HostFilter = func(h Host) bool {
return strings.Contains(string(h), "academy")
}
func main() {
myHosts := HostList{"golang.org", "google.com", "gopheracademy.org"}
goHosts := myHosts.Select(IsDotOrg.Or(HasGo))
academies := myHosts.Select(IsDotOrg.And(IsAcademic))
fmt.Printf("Go sites: %v\n", goHosts)
fmt.Printf("Academies: %v\n", academies)
}
Запустим:
Go sites: [golang.org google.com gopheracademy.org]
Academies: [gopheracademy.org]
Мы можем увидеть язык приобретает форму выражений типа myHosts.Select(IsDotOrg.Or(HasGo))
. Он читается как английский, что-то подобное вы можете услышать на болотах Дагобы. Декларативный синтаксис, начинает появляться - выражения говорять больше о желаемом результате(“select the elements of myHosts that are .orgs or contain ‘Go’) нежели шаги которые требуется чтобы до этого добраться. Мы использовали высокоуровневые функции, Select
, And
и Or
, для построения поведения от этих трех различных кусоков кода в полностью динамичном виде.
Это очень могущественный вид выражения поведения, но все эти методы очереди могут стать запутанными.
// etc.
myHosts.Select(IsDotOrg.Or(HasGo).Or(IsAcademic).Or(WelcomesGophers).And(UsesSSL)
Поэтому возмож мы должны очитсть вещи используя вариативные метод:
var HostFilter Or = func (clauses ...HostFilter) HostFilter {
var f HostFilter = nil
for _, c := range clauses {
f = f.Or(c)
}
return f
}
И затем переписать очередь вызовов выше таким образом:
myHosts.Select(Or(IsDotOrg, HasGo, IsAcademic, WelcomesGophers).And(UsesSSL))
Предупредение: эти функциональные сущности самые опасные свойства Go - вселюбой неGo код который я читал или писал должен злоупотреблять этими возможностями, создание анонимных функци и передача их через слой за слой с косвенным обращениемые Go.
AnotherОдноко, warning:динамика theseфункционального functional-styleстиля constructsпрограммирования areбесценна, someкогда ofстроишь theсвой mostязык dangerouslyвнутри powerfulGo. featuresВысокоуровневые ofфункции, Goфункции которые управляют другими функциями и возвращают целую новую фунцкию, дают возможно сочинять или параметризовать поведение. Цель создания DSL - allупростить ofрешение theв trulyклассе unreadableпроблем Goвыставив I’ve ever read and most of the truly unreadable Go I’ve ever written got to be that way by abusing these features, creating anonymous functions and passing them through layer after layer of indirection.
Nevertheless, the dynamism of functional-style programming is invaluable when building our own language inside of Go. Higher-order functions, functions that operate on other functions and return whole new functions, afford us the ability to compose or parameterize behaviors. The purpose of creating aпользоватлю DSL isнесколько toползеных simplifyидей theдля solutionsрешения toэтих aпроблем, classрасширив ofих problemsи byобъединив exposingэти toидеи theв DSL’sкакой-то userсмысл. aСозданное fewдинамическое conceptsповедение usefulсозднное forс solvingпомощью thoseвысокоуровневых problemsфункций andэто empoweringодна themиз configureвозможностей andдоставить combineэту those concepts in meaningful ways. Composable dynamic behaviors created through higher-order programming are one way deliver that functionality.функциональность.
Пример по серьёзнее
We’llМы buildпостроим onнашу ourработу workнад onименами hostnamesхостов toтак, makeчтобы somethingсделать aчто-то littleближе closerк toтому, whatчто weмы mightвозможно useиспользуем inв aреальном realприложении. application.Ипмортируем Importingпакет the net/http
, package,и let’sдавайте createсоздадим anotherдругой type identity:тип:
1
type RequestFilter func(*http.Request) bool
Мы можем использовать RequestFilter
в простом HTTP сервере для вычисления удовлетворяет http.Request
услови, как мы это делали с HostFilter
выше. Мы можем использовать эти условия, чтобы определить обработать или отбросить запрос.
WeМы canперейдем useот aимен RequestFilterхосто inк aработе simpleс HTTPнабором serverip toадресов. evaluateБудем whether a given http.Request satisfies a particular condition, as we did with HostFilter above. We can use those conditions to determine whether to handle or reject the request.
We’ll shift from working with hostnames as above to working with ranges of IP addresses. We’ll useиспользовать CIDR blocks,блоки e.g.типа "192.168.0.0/16", whichкоторый identifiesопределяет aнабор rangeip ofадресов, IPsв fromданном случае, от 192.168.0.0 throughдо 192.168.255.255. We’llСоздадим createRequestFilter
aкоторый RequestFilterфильтрует thatзапросы filtersоснованные requestsна based on IP.ip.
FromИз thenet
netпакета, package,будем we’llиспользовать useParseCIDR
theфункцию, и PaeseIP
чтобы анализировать входящие запросы. Значения которые возвращает ParseCIDR
function- toIPNet
parseкоторый theимеет CIDRs,удобный andметод theContains
, ParseIPон functionбудет toнам parseговорить IPвходит addressesли fromip incomingв requests.наш One of the return values from ParseCIDR is an IPNet which conveniently has a Contains method that will do the work of telling us whether the incoming IP matches the range in ourсписок CIDR block.блоков.
SoДавайте let’sимпортируем alsonet
importпакет theи netнапишем packageRequestFilter
andкоторый writeпринимает aвариативный RequestFilter that takes a variadic input ofнабор CIDR blocksблоков inв формате string
: form:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func CIDR(cidrs ...string) RequestFilter {
nets := make([]*net.IPNet, len(cidrs))
for i, cidr := range cidrs {
// TODO: handle err
_, nets[i], _ = net.ParseCIDR(cidr)
}
return func(r *http.Request) bool {
// TODO: handle err
host, _, _ := net.SplitHostPort(r.RemoteAddr)
ip := net.ParseIP(host)
for _, net := range nets {
if net.Contains(ip) {
return true
}
}
return false
}
}
NoteЗаметим, thatчто the net/http
packageпакет alreadyуже containsсодержит a type forтип HTTP handlers,обработчика, HandlerFunc:HandlerFunc
:
1
type HandlerFunc func(ResponseWriter, *Request)
А мы будем использовать высокоуровневую функцию и наш RequestFilter
для изменения http.HandlerFuncs
, объявим тип для функции которая обрабатывает http.HandlerFuncs
:
and we’ll be using higher-order functions and our RequestFilters to modify http.HandlerFuncs, so let’s declare a type for functions that operate on http.HandlerFuncs:
1
type Middleware func(http.HandlerFunc) http.HandlerFunc
И давайте сделаем несколько функций чтобы построить промежуточный слой используя RequestFilter
:
and let’s make some functions to build Middleware that uses the RequestFilter:
1
2
3
4
5
6
7
8
9
10
11
12
func Allow(f RequestFilter) Middleware {
return func(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if f(r) {
h(w, r)
} else {
// TODO
w.WriteHeader(http.StatusForbidden)
}
}
}
}
SoТеперь now,для forпримера example,вы youможете couldизменить modifyhttp anобработчик HTTPMyHandler
handlerтак, MyHandlerчтобы toон onlyпрининмал acceptтолько requestsзапросы fromот 127.0.0.1
, withследующим something like:образом:
1
filteredHandler := Allow(CIDR("127.0.0.1/32"))(MyHandler)
Давайте попробуем запустить простой сервер:
Let’s try it by running a simple server:
1
2
3
4
5
6
7
8
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello\n")
}
func main() {
http.HandleFunc("/hello", Allow(CIDR("127.0.0.1/32")(hello))
log.Fatal(http.ListenAndServe(":1217", nil))
}
IfЕсли youмы hitперейдем yourпо newссылке endpointс fromлокальной yourмашины: local machine at http://0.0.0.0:1217/
, hello,helloyouто shouldвы seeдолжны “Hello”увидеть in"Hello" response;в ifответ, youесли hitвы itзайдете fromна anotherэту IPссылку address,с youдругого shouldip, seeто aувидите ошибку 403 Forbidden
.error.error
ForДля fun,прикола, let’sдавайте addдобавим anotherдругой kindвид ofRequestFilter
RequestFilterкоторый thatреализует implementsреально aпросто reallyмеханизм naive authentication mechanism:аутентификации
1
2
3
4
5
func PasswordHeader(password string) RequestFilter {
return func(r *http.Request) bool {
return r.Header.Get("X-Password") == password
}
}
andИ oneодин basedна onоснове HTTP method:метода:
1
2
3
4
5
6
7
8
9
10
func Method(methods ...string) RequestFilter {
return func(r *http.Request) bool {
for _, m := range methods {
if r.Method == m {
return true
}
}
return false
}
}
Ну и промежуточный слой который что-то просто логирует
and a Middleware that performs some simple logging:
1
2
3
4
5
6
func Logging(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("[%v] - %s %s\n", time.Now(), r.Method, r.RequestURI)
f(w, r)
}
}
Который мы можем попробовать обновив наш сервер:
which we can try with an update to our server:
1
2
3
4
func main() {
http.HandleFunc("/hello", Logging(Allow(CIDR("127.0.0.1/32")(hello)))
log.Fatal(http.ListenAndServe(":1217", nil))
}
RunЗапустите thisсервер andи visitпосетите страницу http://localhost:1217/hello
aнесколько fewраз timesв inбраузере yourи browserв andконсоли inсервера theвы console where the server is running you should see:увидите:
1
2
3
[2016-12-14 07:42:12.022266374 -0500 EST] - GET /hello
[2016-12-14 07:42:14.537985456 -0500 EST] - GET /hello
[2016-12-14 07:42:24.220089221 -0500 EST] - GET /hello
Синтаксис декларативный как есть, но метод цепочки может быть немного неуклюж. Метод должен быть упорядочем в правильном порядке чтобы вести себя правильно, а результат может быть слолжно читать.
ThisМы syntaxможем isиспользовать fairlyструктуры declarativeдля asдальнейшего is, but the method chaining can get a little awkward. Methods have to be chained in the right order to behave correctly, and the result can be difficult to read.
We can use a struct to further flesh out ourраскрыватя DSL andи giveдать ourнашим usersпользвателям anдаже evenболее cleanerясную wayвозможность toобъявить declareих theirконфигурацию middlewareпромежуточного configuration:слоя.
1
2
3
4
5
6
7
type Filters []RequestFilters
type Stack []Middleware
type Endpoint struct {
Handler http.HandlerFunc
Allow Filters
Middleware Stack
}
Затем, мы можем выразить эндпоинт выше тем же ограничением как:
then we could express the endpoint above with the same restrictions as:
1
2
3
4
5
6
7
8
9
var MyEndpoint = Endpoint{
Handler: hello,
Allow: Filters{
CIDR("127.0.0.1/32"),
},
Middleware: Stack{
Logging,
},
}
Который в разы проще писать, читать и изменять. Нам просто нужно добавить несколько методов в нашу структуру и типы превращаются из декларативных в удобные http.HandlerFunc
Which is much easier to write, read, and modify. We just need to add a few methods to our struct and type identities turn this declarative description into a usable http.HandlerFunc:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Combine creates a RequestFilter that is the conjunction
// of all the RequestFilters in f.
func (f Filters) Combine() RequestFilter {
return func(r *http.Request) bool {
for _, filter := range f {
if !filter(r) {
return false
}
}
return true
}
}
// Apply returns an http.Handlerfunc that has had all of the
// Middleware functions in s, if any, to f.
func (s Stack) Apply(f http.HandlerFunc) http.HandlerFunc {
g := f
for _, middleware := range s {
g = middleware(g)
}
return g
}
// Builds the endpoint described by e, by applying
// access restrictions and other middleware.
func (e Endpoint) Build() http.HandlerFunc {
allowFilter := e.Allow.Combine()
restricted := Allow(allowFilter)(e.Handler)
return e.Middleware.Apply(restricted)
}
}И наконец, изменим сервер чтобы использовать новый сервер:
and, finally, modify the server to use the endpoint built this way:
1
2
3
4
func main() {
http.HandleFunc("/hello", mw.MyEndpoint.Build())
log.Fatal(http.ListenAndServe(":1217", nil))
}
Чтобы увидеть выходу этого миниDSL что мы создали, добавим еще один промежуточный слой:
To see the benefit of this mini-DSL we’ve created, let’s add one more kind of middleware:
1
2
3
4
5
6
7
8
func SetHeader(key, value string) Middleware {
return func(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *RequestFilter) {
w.Header().Set(key, value)
f(w, r)
}
}
}
И затем добавим его, вместе с другим RequestFilter
в наш эндпоинт:
And then add it, along with another RequestFilter, to our endpoint:
1
2
3
4
5
6
7
8
9
10
11
12
var MyEndpoint = Endpoint{
Handler: hello,
Allow: Filters{
CIDR("127.0.0.1/32"),
PasswordHeader("opensesame"), // added
Method("GET"), // added
},
Middleware: Stack{
Logging,
SetHeader("X-Foo", "Bar"), // added
},
}
МЫ добавили существенности в сложность MyEndpoint
без добавления множества сложности в его объявление.
We’veЭтот addedполезный significantlyDLS toудобен theдля complexityпостроения of MyEndpoint without adding much complexity to its declaration.
This is a useful DSL for building singleодного HTTP endpoints,эндпоинт, butно frequentlyчасто we’llмы wantхотим moreбольше thanчем justпросто oneодин onсервис. aМы service.добавим We’llеще addодин oneэлемент lastв element to our demoнаше DSL, aспособ wayсоздать toнесколько createмаршрутов severalи routesих andендпоинты theirза endpoints at once:раз:
1
2
3
4
5
6
7
8
9
10
type Routes map[string]Endpoint
func (r Routes) Serve(addr string) error {
mux := http.NewServeMux()
for pattern, endpoint := range r {
mux.Handle(pattern, endpoint.Build())
}
return http.ListenAndServe(addr, mux)
}
}И наш сервис превращается в:
and then our service becomes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func main() {
routes := Routes{
"/hello": {
Handler: hello,
Middleware: Stack{
Logging,
},
},
"/private": {
Handler: hello,
Allow: Filters{
CIDR("127.0.0.1/32"),
PasswordHeader("opensesame"),
},
Middleware: Stack{
Logging,
},
},
"/test": {
Handler: hello,
Middleware: Stack{
Logging,
SetHeader("X-Foo", "Bar"),
},
},
}
log.Fatal(routes.Serve(":1217"))
}
Обратите внимание, что Go автоматически определяет тип структурных литералов конечной точки в карте маршрутов, избавляя нас от лишнего набора текста и беспорядка.
Note that Go automatically infers the type of the Endpoint struct literals in the Routes map, saving us even more typing and clutter.
This HTTP middlewareпромежуточный слой DSL showsпоказывает howкак muchмного canможет beудаваться accomplishedв inотносительно aмаленьком relatively small amount ofнаборе Go, butно it’sэто aпростой toyпример. example.Вот Hereнесколько areзадания someдля ideas for exercises to extend it and to make theраширения DSL evenи moreдля powerful:того чтобы сделать его более мощным.
- Реализовать дополнительный
ImplementReqeustFilters
additionalкак RequestFilters,ограничитель likeчастоты, aвозможно rate-limiter, perhaps usingиспользовать golang.org/x/time/rate orили juju/ratelimit, orили aболее moreсложный robustмеханизм authenticationаутентификации
mechanism- Реазиловать
Implementдругой anotherпромежуточный Middlewareслой
Modify- Изменить
theструктуру Endpointэндпоинта structчтобы toвключить includeполу aDeny
Denyдля fieldтипа ofFilters
, typeэто Filters,будет thatотвергать rejectsзапрос theесли requestодно ifиз anyполей ofRequestFiltesr
its- RequestFilterstrue
is- Каждый
trueэндпоинт Eachв ofконечном theпримере endpointsвключает inлогирование theв finalпромежуточном sampleслое, includedдобавьте Logging in its middleware; add to theв DSL aсредство facilityдля toприменения applyнабора aобщих setограничений ofили commonпромежуточный restrictionsслой orдля middlewareвсех toэнпоинтов.
all- Создать
ofспособ theдля endpoints.стека Createпромежуточного aслоя, wayчтобы forсоздать this middleware stack to create a context.Context
andи toработать workс withобработчиками handlersкоторые thatих acceptпринимают.
them.
ToРезюмируем, recap,мы weиспользовать usedтипы typeдля identitiesсоздания toабстрактнций createповерх abstractionsнаборов overпростых simpleтипов collectionи typesфункций andотдельных functionsподписей, ofи particularмы signatures,берем and we took advantage ofпреимущество Go syntaxсвойств featuresсинтаксиса likeтакие variadicкак functionsвариативные andфункции inferredи typesприведение toтипов writeдля aнаписания smooth,спокойного unclutteredи syntax.ненагроможденного Theсинтаксиса. heavyТяжелый liftingподъем inв creating ourсоздании DSL wasбыл performedпроизведен byс higher-orderпомощью functionsфункций thatвысшего letпорядка usкоторый createпозволили parameterizedпараметризовать behaviorsповедения thatобъединеные couldи beнастроены combinedво andвремя configuredработы. atМы runtime.использовали Weнесколько employedопасных aпрактик fewнаписания dangerousкода, codingно practicesчем toбольше doмы it,применяем butих asтолько longкогда asсокращаем weсложность applyдля themконечного onlyпользователя, whenтем reducingкрепче complexityмы forможем end-usersспать is the right tradeoff, we can all sleep at night.ночью.
TheGo, который вы получаете из коробки ориентирован на детали, минималистичный, и может быть довольно подробным. Go youдает getинструменты, outоднако, ofчтобы theпостроить boxвашу isсобственную detail-oriented,абстракцию, minimalistic,ваш andсобственный canвысокоуровневый becomeязык verbose.для Goнаписания givesкода youкоторый theсодержательный, tools,элегантный, however,и toвыразительный buildкак upи yourлюбой ownдругой abstractions-который yourвы ownнаходите high-levelв language-динамичном toили writeчистом codeфункциональном thatязыке, isно asэто pithy,дает elegant,нам andдоступ expressiveко asвсем anyсвойствам you’llкоторый findмы inлюбим a dynamic or purely functional language, but that still gives us access to all of the features we love aboutв Go.