Go closures

Eu tinha duas funções quase idênticas para analisar um XML com XPath:

func xpathContent(root xml.Node, xpath string) string {
result
, _ := root.Search(xpath)

if len(result) > 0 {
return strings.TrimSpace(result[0].Content())
} else {
return ""
}
}

/* and */

func xpathAttribute
(root xml.Node, xpath string, attr string) string {
result
, _ := root.Search(xpath)

if len(result) > 0 {
return strings.TrimSpace(result[0].Attr(attr))
} else {
return ""
}
}

Claro que há muita repetição , eles se diferenciam apenas para o primeiro ramo da ifcondição. Isso significa que podemos envolver toda a lógica circundante em uma função separada e executar dinamicamente uma parte arbitrária de código (extração de conteúdo ou atributo).

Vamos reescrever com os fechamentos em mente:

type xpathFn func(node xml.Node) string

func xpathSearch
(root xml.Node, xpath string, fn xpathFn) string {
result
, _ := root.Search(xpath)

if len(result) > 0 {
return fn(result[0])
} else {
return ""
}
}

Em primeiro lugar, precisamos declarar um novo tipo de função, para que possamos passá-la como argumento. O segundo passo é extrair a lógica comum em uma terceira função e substituir a parte que queremos que seja dinâmica, com a invocação ( fn(result[0])).

Agora podemos usar este novo componente para reescrever as funções anteriores:

func xpathContent(root xml.Node, xpath string) string {
return xpathSearch(root, xpath, func(node xml.Node) string {
return strings.TrimSpace(node.Content())
})
}

func xpathAttribute
(root xml.Node, xpath string, attr string) string {
return xpathSearch(root, xpath, func(node xml.Node) string {
return strings.TrimSpace(node.Attr(attr))
})
}

Veja como invocamos xpathSearcho terceiro argumento é anônimo xpathFne sua returnvontade se tornou o valor de retorno, apenas se a condição de pesquisa for satisfeita.

Outro aspecto que merece atenção é o escopo da função mais interna: tem acesso aos vars externos, como attr.