Haskell: por que a convenção para nomear uma função auxiliar “go”?

83

Vejo gomuito quando leio material ou fonte de Haskell, mas nunca me senti muito confortável com isso - (acho que tem a conotação negativa de "goto" em minha mente). Comecei a aprender Haskell com LYAH, e foi aí que peguei a tendência de usar acce stepescrever dobras. De onde vem a convenção para escrever go?

Mais importante, o que exatamente o nome goimplica?

Dan Burton
fonte
4
Em loopvez disso, geralmente chamo minha função .
agosto
2
Nunca vi goem nenhum material Haskell que li. Você pode dar um exemplo / referência?
Ionuț G. Stan
@ Ionuț Por exemplo, a explicação do livro Yesod sobre o pacote Enumerator . (Não sei por que o livro de Yesod dedica tanto material ao tópico, mas isso não vem ao caso)
Dan Burton
Pelo que vale a pena, tenho visto muitos programadores C / C ++ que também nomeiam suas funções auxiliares "ir" quando não conseguem pensar em um nome melhor.
ShreevatsaR
FWIW, recursão de cauda explícita é a versão funcional de goto de várias maneiras, incluindo o potencial de ofuscação. A tipagem estática e as regras de escopo ajudam a reduzir a confusão ao mínimo. E quanto à escolha do nome, gosto da resposta de @Michael Snoyman abaixo sobre comprimento e adequação. Além disso, quando há apenas uma função auxiliar, seu nome parece bastante irrelevante, então geralmente escolho apenas 'ir' ou 'loop' porque tenho que escolher algo e ambos fazem sentido. Eu tendo a preferir 'ir' para loops preguiçosos e "loop" para os restritos.
mokus

Respostas:

137

Hmm! Alguma arqueologia!

Desde cerca de 2004, usei gocomo nome genérico para loops de trabalho recursivos de cauda, ​​ao fazer uma transformação de trabalhador / wrapper de uma função recursiva. Comecei a usá-lo amplamente em bytestring, por exemplo

foldr :: (Word8 -> a -> a) -> a -> ByteString -> a
foldr k v (PS x s l) = inlinePerformIO $ withForeignPtr x $ \ptr ->
        go v (ptr `plusPtr` (s+l-1)) (ptr `plusPtr` (s-1))
    where
        STRICT3(go)
        go z p q | p == q    = return z
                 | otherwise = do c  <- peek p
                                  go (c `k` z) (p `plusPtr` (-1)) q -- tail recursive
{-# INLINE foldr #-}

era de bytestringagosto de 2005.

Isso foi escrito no RWH e provavelmente foi popularizado a partir daí. Além disso, na biblioteca de fusão de fluxo , Duncan Coutts e eu começamos a fazer muito isso.

Das fontes GHC

O idioma é mais antigo. foldrem GHC.Base é dado como:

foldr k z = go
      where
         go []     = z
         go (y:ys) = y `k` go ys

que foi provavelmente onde aprendi o truque (pensei que fosse da tese de Andy Gill, mas não consigo encontrar nenhuma utilidade gonela). Não é fornecido nesta forma no Gofer, então acho que apareceu pela primeira vez na base de código do GHC.

Em 2001, Simon Marlow estava usando goem alguns dos códigos de nível de sistema, então podemos colocar a culpa em algum lugar no GHC, e essa pista nos leva à fonte do GHC , onde goé amplamente usado nas funções de trabalho:

myCollectBinders expr
  = go [] expr
  where
    go bs (Lam b e)          = go (b:bs) e
    go bs e@(Note (SCC _) _) = (reverse bs, e)
    go bs (Cast e _)         = go bs e
    go bs (Note _ e)         = go bs e
    go bs e                  = (reverse bs, e)

GHC 3.02 e Glasgow

Desenterrando versões antigas do GHC, vemos que no GHC 0.29 esse idioma não aparece, mas pela série GHC 3.02 (1998), o goidioma aparece em todos os lugares. Um exemplo, em Numeric.lhs, na definição de showInt, datado de 1996-1997:

showInt n r
  | n < 0     = error "Numeric.showInt: can't show negative numbers"
  | otherwise = go n r
    where
     go n r =
      case quotRem n 10 of                 { (n', d) ->
      case chr (ord_0 + fromIntegral d) of { C# c# -> -- stricter than necessary
      let
    r' = C# c# : r
      in
      if n' == 0 then r' else go n' r'
      }}

esta é uma implementação diferente daquela fornecida no relatório H98 . Explorando a implementação de "Numeric.lhs" , no entanto, descobrimos que não é o mesmo que a versão que foi adicionada ao GHC 2.06 em 1997, e um patch muito interessante de Sigbjorne Finne aparece, em abril de 1998, adicionando um goloop para Numeric.lhs.

Isso diz que pelo menos em 1998, Sigbjorne estava adicionando goloops à biblioteca "std" do GHC, enquanto, simultaneamente, muitos módulos no núcleo do compilador GHC tinham goloops. Indo mais longe, este commit muito interessante de Will Partain em julho de 1996 adiciona um loop "go" no GHC - o código vem de Simon PJ embora!

Então, vou chamar isso de uma expressão idiomática de Glasgow inventada por pessoas em Glasgow que trabalharam com GHC em meados dos anos 90, como Simon Marlow , Sigbjorn Finne , Will Partain e Simon Peyton Jones .

Don Stewart
fonte
4
+1 para "o nome genérico para loops de trabalho recursivos na cauda" que parece ser geralmente verdadeiro para a maioria dos usos que já vi. Para uma função f, eu normalmente usarei f'como nome para esse tipo de coisa, embora usar gocomo uma espécie de idioma quase-palavra-chave seja algo que eu possa tentar escolher. Interessante notar que showIntusa o idioma para evitar avaliar o mesmo guarda várias vezes.
Dan Burton
1
BTW, para "o que exatamente o nome go supostamente implica?" Eu diria que sugere goto, e em passar o controle para uma função auxiliar.
Don Stewart
25
Minha vaga lembrança é que este é um Simon PJ-ismo. Costumo usar, a loopmenos que esteja modificando o código que já usa a goconvenção. Sempre pensei que a intenção era literalmente "ir", como em "ir ao redor do loop".
Simon Marlow
5
Sempre pensei em "ir" como um comando para a função de trabalho começar seu trabalho recursivo sujo. Em qualquer caso, pessoalmente, peguei em um dos slides de fusão de fluxo porque adicionar marcações ao nome da função sempre teve o problema de eu esquecer a marcação.
Heinrich Apfelmus
4
Acredito que tenha origens anteriores a Haskell. go é um nome popular para o regime de deixa em regime nomeado ( google.com/… , google.com/search?q=scheme+%22let+go%22+-let's+car+cdr )
AtnNn
17

Obviamente, a resposta de Don é a correta. Deixe-me apenas adicionar um pequeno detalhe (já que parece ser a minha escrita que você está se referindo diretamente): go é bom porque tem apenas duas letras.

Ah, e a razão pela qual o livro de Yesod dedica tanto conteúdo ao pacote do enumerador é porque eu já escrevi o tutorial de três partes do enumerador como uma série de postagens de blog, então decidi que também posso incluí-lo no livro. O pacote de enumerador é usado em vários lugares em Yesod, por isso é relevante.

Michael Snoyman
fonte
6
+1 "vai" sendo apenas 2 letras (e ainda significativo) é um fato fácil de subestimar. Enquanto eu comentava sobre o uso de "go" no livro de Yesod (que era uma excelente escolha de nome para esses exemplos, imho), na verdade estava lendo uma resposta StackOverflow que usava "go" quando senti que deveria fazer a pergunta. Lembrei-me imediatamente do exemplo do livro de Yesod, porém, porque era memorável. Coisa boa!
Dan Burton
11

Eu esperava que esse idioma fosse aplicável não apenas a estruturas lineares (e, portanto, "loops"), mas também a estruturas ramificadas (semelhantes a árvores).

Eu me pergunto com que frequência o gopadrão corresponde aos parâmetros de acumulação e, de maneira mais geral, às estratégias de codificação de continuação que Mitch Wand explorou no artigo Estratégias de Transformação de Programas Baseados em Continuação (um dos meus artigos favoritos de todos os tempos). Nesses casos, a gofunção tem um significado particular, que pode ser usado para derivar código eficiente de uma especificação elegante.

Conal
fonte
Acho que vale a pena mencionar que muitas vezes é difícil encontrar bons nomes para essas funções pelo simples motivo de que geralmente se referem a variáveis ​​em um escopo delimitador e, portanto, não são realmente completas. Um nome descritivo provavelmente pareceria bobo: algo como add_xou consOnto_xs.
dfeuer