Haskell: почему принято называть вспомогательную функцию «go»?

84

Я goмногое вижу, когда читаю материал или исходный код Haskell, но я никогда не чувствовал себя комфортно в этом - (я полагаю, это имеет негативный оттенок "goto" в моем сознании). Я начал изучать Haskell с LYAH, и именно здесь я уловил тенденцию использовать accи stepпри написании складок. Откуда взялось условное обозначение письма go?

Самое главное, что именно goдолжно означать это имя ?

Дэн Бертон
источник
4
loopВместо этого я обычно вызываю свою функцию .
августа
2
Никогда не встречал goв материалах по Haskell, которые я читал. Можете привести пример / ссылку?
Ionuț G. Stan
@ Ionuț Например, объяснение пакета Enumerator в книге Yesod . (Почему книга «Йесод» посвящена этой теме, я не знаю, но это не относится к делу)
Дэн Бертон
Как бы то ни было, я видел многих программистов на C / C ++, которые также называют свои вспомогательные функции «go», когда не могут придумать лучшего имени.
ShreevatsaR
FWIW, явная хвостовая рекурсия - это функциональная версия goto во многих отношениях, включая возможность обфускации. Однако правила статической типизации и области видимости помогают свести путаницу к минимуму. Что касается выбора имени, мне нравится ответ @Michael Snoyman ниже о длине и пригодности. Кроме того, когда есть только одна вспомогательная функция, ее имя кажется в значительной степени несущественным, поэтому я обычно просто выбираю либо «идти», либо «цикл», потому что мне нужно что-то выбрать, и оба они имеют смысл. Я предпочитаю «идти» для ленивых циклов и «цикл» для строгих.
mokus

Ответы:

138

Хм! Немного археологии!

Примерно с 2004 года я использовал goв качестве общего имени для хвостовых рекурсивных рабочих циклов, когда выполнял преобразование рабочий / оболочка рекурсивной функции. Я начал широко использовать его bytestring, например

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 #-}

был с bytestringавгуста 2005 года.

Об этом написали в RWH и, вероятно, оттуда популяризировали. Кроме того, в библиотеке слияния потоков мы с Дунканом Куттсом много начали этим заниматься.

Из источников GHC

Однако идиома уходит корнями в далекое прошлое. foldrв GHC.Base задается как:

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

вероятно, именно здесь я и взял уловку (я думал, что это из диссертации Энди Гилла, но не нашел там никакого применения go). В Gofer его нет в такой форме, поэтому я думаю, что он впервые появился в базе кода GHC.

К 2001 году Саймон Марлоу использовал goв некоторых кодах системного уровня, так что мы могли бы возложить вину на GHC, и эта подсказка ведет нас к исходному тексту GHC , goкоторый широко используется в рабочих функциях:

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 и Глазго

Раскапывая старые версии GHC, мы видим, что в GHC 0.29 эта идиома не появляется, но в серии GHC 3.02 (1998 г.) она goпоявляется везде. Например, Numeric.lhsв определении showInt, датированном 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'
      }}

это реализация, отличная от той, что дана в отчете H98 . Однако, копаясь в реализации "Numeric.lhs" , мы обнаруживаем, что это не то же самое, что версия, которая была добавлена ​​к GHC 2.06 в 1997 году, и очень интересный патч от Sigbjorne Finne появляется в апреле 1998 года, добавляя goцикл к Numeric.lhs.

Это говорит о том, что по крайней мере к 1998 году Sigbjorne добавлял goциклы в библиотеку «std» GHC, в то время как одновременно многие модули в ядре компилятора GHC имели goциклы. Если копать дальше, то этот очень интересный коммит, сделанный Уиллом Партейном в июле 1996 года, добавляет цикл "go" в GHC - хотя код исходит от Simon PJ!

Я назову это идиомой Глазго, придуманной людьми из Глазго, которые работали над GHC в середине 90-х, такими как Саймон Марлоу , Сигбьорн Финн , Уилл Партейн и Саймон Пейтон Джонс .

Дон Стюарт
источник
4
+1 за «общее имя для хвостовых рекурсивных рабочих циклов», что в целом верно для большинства применений, которые я видел. Для функции fя лично обычно использую f'как имя для такого рода вещей, хотя использование goв качестве своего рода идиомы, близкой к ключевому слову, - это то, что я мог бы попытаться подобрать. Интересно отметить, что showIntиспользуется идиома, чтобы не оценивать один и тот же охранник несколько раз.
Дэн Бертон
1
Кстати, "что именно должно подразумевать это название?" Я бы сказал, что это намекает на gotoпередачу управления вспомогательной функции.
Дон Стюарт
26
Смутно припоминаю, что это Саймон Пи-изм. Я предпочитаю использовать, loopесли я не изменяю код, который уже использует это goсоглашение. Я всегда думал, что это должно означать буквально «идти», как «идти по кругу».
Саймон Марлоу
6
Я всегда думал о «go» как о команде для рабочей функции начать ее грязную рекурсивную работу. В любом случае, лично я взял его с одного из слайдов слияния потоков, потому что добавление галочки к имени функции всегда приводило к тому, что я мог забыть эту галочку.
Генрих Апфельмус
4
Я считаю, что он возник еще до Haskell. go - популярное название для именованной схемы размещения ( google.com/… , google.com/search?q=scheme+%22let+go%22+-let's+car+cdr )
AtnNn
17

Очевидно, ответ Дона правильный. Позвольте мне добавить небольшую деталь (поскольку, похоже, вы прямо имеете в виду мое письмо): go - это хорошо, потому что это всего две буквы.

Да, и причина, по которой книга Yesod посвящает так много контента пакету enumerator, заключается в том, что я уже написал трехчастное руководство по enumerator в виде серии сообщений в блоге, поэтому решил, что могу также включить его в книгу. Пакет enumerator используется во многих местах Yesod, поэтому он актуален.

Майкл Снойман
источник
6
+1 "go", состоящее всего из двух букв (и все еще значимого), - это факт, который легко недооценить. Пока я комментировал использование в книге Yesod слова «go» (которое было отличным выбором названия для этих примеров, imho), на самом деле я читал ответ StackOverflow, в котором использовалось «go», когда я чувствовал, что должен задать вопрос. Я сразу вспомнил пример с книгой Йесод, потому что он запомнился. Хорошая вещь!
Дэн Бертон
11

Я ожидал, что эта идиома будет применима не только к линейным структурам (и, следовательно, «циклам»), но также и к ветвящимся (древовидным) структурам.

Интересно, как часто goпаттерн соответствует параметрам накопления и, в более общем плане, стратегиям кодирования продолжения, которые Митч Ванд исследовал в статье « Стратегии преобразования программ, основанных на продолжении» (одна из моих самых любимых статей). В этих случаях goфункция имеет особое значение, которое затем можно использовать для получения эффективного кода из элегантной спецификации.

Конал
источник
Я думаю, стоит упомянуть, что часто бывает трудно придумать хорошие имена для этих функций по той простой причине, что они обычно ссылаются на переменные в охватывающей области видимости и, следовательно, на самом деле не являются полными. Описательное имя могло бы выглядеть глупо: что-то вроде add_xили consOnto_xs.
dfeuer