Spec-Zone.ru › Haskell 9

3. Использование GHCi

GHCi [1] — интерактивная среда GHC, включающая интерактивный отладчик (см. Отладчик GHCi).

GHCi может

  • интерактивно вычислять выражения Haskell
  • интерпретировать программы Haskell
  • загружать скомпилированные модули GHC.

В настоящее время GHCi поддерживает большинство расширений языка GHC.

[1]

«i» означает «Интерактивный»

3.1. Введение в GHCi

Начнём с примера сеанса GHCi. Вы можете запустить GHCi с помощью команды ghci.

$ ghci
GHCi, version 8.y.z: https://www.haskell.org/ghc/  :? for help
ghci>

Возможно, произойдёт небольшая пауза во время загрузки GHCi прелюдии и стандартных библиотек, после чего появится приглашение. Как указано в заголовке, вы можете ввести :?, чтобы увидеть список доступных команд и краткое описание каждой из них. Мы объясним большинство этих команд по ходу дела, а полная документация по всем командам находится в разделе Команды GHCi.

Выражения Haskell можно вводить в приглашении:

ghci> 1+2
3
ghci> let x = 42 in x / 9
4.666666666666667
ghci>

GHCi интерпретирует всю строку как выражение для вычисления. Выражение не может занимать несколько строк — как только вы нажмёте Enter, GHCi попытается вычислить его.

В Haskell выражение let следует за in. Однако в GHCi, так как выражение также может быть интерпретировано в монаде IO, привязку let без сопровождающего in можно обозначить пустой строкой, как в приведённом выше примере.

Начиная с GHC 8.0.1, вы можете привязывать значения и функции к именам без let:

ghci> x = 42
ghci> x
42
ghci>

3.2. Загрузка исходных файлов

Предположим, у нас есть следующий исходный код Haskell, который мы поместим в файл Main.hs:

main = print (fac 20)

fac 0 = 1
fac n = n * fac (n-1)

Вы можете сохранить Main.hs в любом месте, но если вы сохраните его не в текущем каталоге [3], то нам нужно будет перейти в нужный каталог в GHCi:

ghci> :cd dir

где ⟨dir⟩ — каталог (или папка), в котором вы сохранили Main.hs.

Для загрузки файла Haskell в GHCi используйте команду :load:

ghci> :load Main
Compiling Main             ( Main.hs, interpreted )
Ok, modules loaded: Main.
*ghci>

GHCi загрузил модуль Main, и приглашение изменилось на *ghci>, чтобы указать, что текущий контекст для выражений, вводимых в приглашении, — это модуль Main, который мы только что загрузили (мы объясним, что означает *, позже в разделе Что действительно находится в области видимости в приглашении?). Теперь мы можем вводить выражения, использующие функции из модуля Main.hs:

*ghci> fac 17
355687428096000

Загрузка многомодульной программы также проста; просто укажите имя «главного» модуля команде :load (подсказка: :load можно сократить до :l). Главный модуль обычно будет Main, но не обязательно. GHCi обнаружит, какие модули требуются, непосредственно или косвенно, основным модулем, и загрузит их все в порядке зависимости.

[3]

Если вы запустили GHCi из командной строки, то текущий каталог GHCi совпадает с текущим каталогом оболочки, из которой он был запущен. Если вы запустили GHCi из меню «Пуск» в Windows, то текущий каталог, вероятно, будет чем-то вроде C:\Documents and Settings\user name.

-fshow-loaded-modules
Значение по умолчанию:

выключено

С версии:

8.2.2

Обычно GHCi отображает только количество загруженных модулей после команды :load. С этим флагом GHC также будет перечислять имена загруженных модулей. Это было поведением по умолчанию до GHC 8.2.1 и может быть полезно для некоторых пользователей инструментов.

3.2.1. Модули и имена файлов

Вопрос: Как GHC находит имя файла, содержащий модуль ⟨M⟩? Ответ: он ищет файл M.hs, или M.lhs. Это означает, что для большинства модулей имя модуля должно совпадать с именем файла. Если это не так, GHCi не сможет его найти.

Есть одно исключение из этого общего правила: когда вы загружаете программу с помощью :load, или указываете её при вызове ghci, вы можете указать имя файла вместо имени модуля. Этот файл загружается, если он существует, и он может содержать любой модуль. Это особенно удобно, если у вас несколько Main модулей в одном каталоге, и вы не можете назвать их все Main.hs.

Путь поиска исходных файлов задаётся параметром -i в командной строке GHCi, например:

ghci -idir1:...:dirn

или его можно установить с помощью команды :set внутри GHCi (см. Установка параметров командной строки GHC в GHCi) [4]

Следствием того, как GHCi отслеживает зависимости для поиска модулей, является то, что каждый модуль должен иметь исходный файл. Единственное исключение — модули, которые поступают из пакета, включая Prelude и стандартные библиотеки, такие как IO и Complex. Если вы попытаетесь загрузить модуль, для которого GHCi не может найти исходный файл, даже если существуют объектные и интерфейсные файлы для модуля, вы получите сообщение об ошибке.

[4]

Обратите внимание, что в GHCi и режиме --make параметр -i используется для указания пути поиска исходных файлов, а в стандартном режиме пакетной компиляции параметр -i используется для указания пути поиска интерфейсных файлов, см. Путь поиска.

3.2.2. Внесение изменений и перекомпиляция

Если вы внесли изменения в исходный код и хотите, чтобы GHCi перекомпилировал программу, введите команду :reload. Программа будет перекомпилирована по мере необходимости, и GHCi постарается избежать фактической перекомпиляции модулей, если их внешние зависимости не изменились. Этот механизм используется для избежания перекомпиляции модулей в режиме пакетной компиляции (см. Проверка перекомпиляции).

3.3. Загрузка скомпилированного кода

Когда вы загружаете модуль Haskell в GHCi, он обычно преобразуется в байт-код и выполняется с помощью интерпретатора. Однако интерпретируемый код также может выполняться вместе со скомпилированным кодом в GHCi; на самом деле, обычно при запуске GHCi он загружает скомпилированную копию пакета base, который содержит Prelude.

Зачем нам нужно запускать скомпилированный код? Ну, скомпилированный код примерно в 10 раз быстрее, чем интерпретируемый код, но занимает примерно в 2 раза больше времени для создания (возможно, больше, если включена оптимизация). Поэтому целесообразно компилировать части программы, которые не меняются очень часто, и использовать интерпретатор для кода, над которым активно ведётся работа.

При загрузке исходных модулей с помощью :load, GHCi обычно ищет соответствующие файлы объектного кода и, если возможно, будет использовать один из них вместо интерпретации исходного кода. Например, предположим, что у нас есть программа из 4-х модулей, состоящая из модулей A, B, C, и D. Модули B и C оба импортируют только D, а A импортирует как B, так и C:

  A
 / \
B   C
 \ /
  D

Мы можем скомпилировать D, а затем загрузить всю программу следующим образом:

ghci> :! ghc -c -dynamic D.hs
ghci> :load A
Compiling B                ( B.hs, interpreted )
Compiling C                ( C.hs, interpreted )
Compiling A                ( A.hs, interpreted )
Ok, modules loaded: A, B, C, D (D.o).
*ghci>

В сообщениях компилятора мы видим, что для D нет строки. Это связано с тем, что нет необходимости компилировать D, поскольку исходный код и всё, от чего он зависит, не изменились с момента последней компиляции.

Обратите внимание на флаг -dynamic для GHC: GHCi использует динамически подключаемый объектный код (если вы работаете на платформе, которая его поддерживает), и поэтому для использования скомпилированного кода с GHCi он должен быть скомпилирован для динамической загрузки.

В любое время вы можете использовать команду :show modules, чтобы получить список модулей, в настоящее время загруженных в GHCi:

*ghci> :show modules
D                ( D.hs, D.o )
C                ( C.hs, interpreted )
B                ( B.hs, interpreted )
A                ( A.hs, interpreted )
*ghci>

Если теперь мы изменим исходный код D (или сделаем вид: для этого удобно использовать командную строку Unix touch для файла исходного кода), компилятор больше не сможет использовать файл объектного кода, потому что он может быть устаревшим:

*ghci> :! touch D.hs
*ghci> :reload
Compiling D                ( D.hs, interpreted )
Ok, modules loaded: A, B, C, D.
*ghci>

Обратите внимание, что модуль D был скомпилирован, но в этом случае, поскольку его исходный код на самом деле не изменился, его интерфейс остался прежним, и механизм проверки повторной компиляции определил, что A, B и C не нуждаются в повторной компиляции.

Итак, давайте попробуем скомпилировать один из других модулей:

*ghci> :! ghc -c C.hs
*ghci> :load A
Compiling D                ( D.hs, interpreted )
Compiling B                ( B.hs, interpreted )
Compiling C                ( C.hs, interpreted )
Compiling A                ( A.hs, interpreted )
Ok, modules loaded: A, B, C, D.

Мы не получили скомпилированную версию C! Что произошло? Ну, в GHCi скомпилированный модуль может зависеть только от других скомпилированных модулей, и в данном случае C зависит от D, у которого нет файла объектного кода, поэтому GHCi также отклонил файл объектного кода C. Хорошо, давайте также скомпилируем D:

*ghci> :! ghc -c D.hs
*ghci> :reload
Ok, modules loaded: A, B, C, D.

Ничего не произошло! Вот ещё один урок: недавно скомпилированные модули не обнаруживаются командой :reload, только :load:

*ghci> :load A
Compiling B                ( B.hs, interpreted )
Compiling A                ( A.hs, interpreted )
Ok, modules loaded: A, B, C (C.o), D (D.o).

Автоматическая загрузка файлов объектного кода иногда может привести к путанице, потому что неэкспортированные определения верхнего уровня модуля доступны для использования в выражениях в командной строке только при интерпретации модуля (см. Что реально находится в области видимости в командной строке?). По этой причине иногда вам может потребоваться принудительно загрузить модуль с помощью интерпретатора. Это можно сделать, добавив префикс * к имени модуля или имени файла при использовании :load, например

ghci> :load *A
Compiling A                ( A.hs, interpreted )
*ghci>

Когда используется *, GHCi игнорирует любой предварительно скомпилированный объектный код и интерпретирует модуль. Если вы уже загрузили несколько модулей как объектный код и решили, что хотите интерпретировать один из них, вместо перезагрузки всего набора вы можете использовать :add *M для указания того, что вы хотите, чтобы M интерпретировался (обратите внимание, что это может привести к интерпретации и других модулей, так как скомпилированные модули не могут зависеть от интерпретируемых).

Чтобы всегда компилировать всё в объектный код и никогда не использовать интерпретатор, используйте опцию -fobject-code (см. Компиляция в объектный код внутри GHCi).

Подсказка

Поскольку GHCi будет использовать только скомпилированный файл объектного кода, если он уверен, что скомпилированная версия актуальна, хорошим приемом при работе с большой программой является периодическое выполнение ghc --make для компиляции всего проекта (скажем, перед обедом :-), а затем продолжение работы в интерпретаторе. По мере изменения кода изменённые модули будут интерпретированы, но остальная часть проекта останется скомпилированной.

3.4. Интерактивное вычисление в командной строке

При вводе выражения в командной строке, GHCi немедленно вычисляет и выводит результат:

ghci> reverse "hello"
"olleh"
ghci> 5+5
10

3.4.1. Действия ввода-вывода в командной строке

GHCi делает больше, чем просто вычисление выражений в командной строке. Если вы введете выражение типа IO a для некоторого a, тогда GHCi выполнит его как вычисление ввода-вывода.

ghci> "hello"
"hello"
ghci> putStrLn "hello"
hello

Это работает даже если тип выражения более общий, при условии, что он может быть приведен к IO a. Например

ghci> return True
True

Кроме того, GHCi выведет результат действия ввода-вывода, если (и только если):

  • Тип результата является экземпляром Show.
  • Тип результата не является ().

Например, помня, что putStrLn :: String -> IO ():

ghci> putStrLn "hello"
hello
ghci> do { putStrLn "hello"; return "yes" }
hello
"yes"

3.4.2. Использование обозначения do в командной строке

GHCi фактически принимает операторы, а не только выражения в командной строке. Это означает, что вы можете привязывать значения и функции к именам и использовать их в будущих выражениях или операторах.

Синтаксис оператора, принятого в командной строке GHCi, точно такой же, как синтаксис оператора в Haskell do выражении. Однако здесь нет перегрузки монады: операторы, введенные в командной строке, должны быть в монаде IO.

ghci> x <- return 42
ghci> print x
42
ghci>

Оператор x <- return 42 означает «выполнить return 42 в монаде IO, и привязать результат к x». Затем мы можем использовать x в будущих операторах, например, чтобы вывести его, как мы делали выше.

-fprint-bind-result

Если -fprint-bind-result задано, тогда GHCi будет выводить результат оператора только в следующих случаях:

  • Оператор не является привязкой, или это монадическая привязка (p <- e) которая привязывает ровно одну переменную.
  • Тип переменной не полиморфен, не (), и является экземпляром Show.

Конечно, вы также можете привязать обычные выражения без ввода-вывода, используя оператор let:

ghci> let x = 42
ghci> x
42
ghci>

Еще одно важное различие между двумя типами привязок состоит в том, что монадическая привязка (p <- e) строгая (она вычисляет e), тогда как с формой let, выражение не вычисляется немедленно:

ghci> let x = error "help!"
ghci> print x
*** Exception: help!
ghci>

Обратите внимание, что привязки let не выводят автоматически привязанное значение, в отличие от монадических привязок.

Вы также можете определять функции в командной строке:

ghci> add a b = a + b
ghci> add 1 2
3
ghci>

Однако это быстро становится утомительным при определении функций с несколькими условиями или группами взаимно рекурсивных функций, поскольку полное определение должно быть дано на одной строке, используя явные точки с запятой вместо выравнивания:

ghci> f op n [] = n ; f op n (h:t) = h `op` f op n t
ghci> f (+) 0 [1..3]
6
ghci>
:{
:}

Начать или закончить блок многострочной команды GHCi.

Чтобы решить эту проблему, команды GHCi могут быть разбиты на несколько строк, обернув их в :{ и :} (каждая на своей строке):

ghci> :{
ghci| g op n [] = n
ghci| g op n (h:t) = h `op` g op n t
ghci| :}
ghci> g (*) 1 [1..3]
6

Эти многострочные команды могут использоваться с любой командой GHCi, и обратите внимание, что правило выравнивания действует. Основная цель многострочных команд — не заменить загрузку модулей, а сделать определения в файлах .ghci (см. Файлы .ghci и .haskeline) более читаемыми и поддерживаемыми.

Любые исключения, возникшие во время вычисления или выполнения оператора, обрабатываются и выводятся интерфейсом командной строки GHCi (для получения дополнительной информации об исключениях см. модуль Control.Exception в документации по библиотекам).

Каждая новая привязка затеняет любые существующие привязки с тем же именем, включая сущности, которые находятся в области видимости в текущем контексте модуля.

Предупреждение

Временные привязки, введенные в командной строке, действуют только до следующей команды :load, :reload, :add или :unadd, в этот момент они будут просто потеряны. Однако они сохраняются при изменении контекста с помощью :module: временные привязки просто перемещаются в новое место.

Подсказка

Чтобы получить список привязок, которые в данный момент находятся в области видимости, используйте команду :show bindings:

ghci> :show bindings
x :: Int
ghci>

Подсказка

Если вы включите опцию +t, GHCi будет показывать тип каждой переменной, привязанной оператором. Например:

ghci> :set +t
ghci> let (x:xs) = [1..]
x :: Integer
xs :: [Integer]

3.4.3. Многострочный ввод

Помимо синтаксиса :{ ... :} для многострочного ввода, упомянутого выше, GHCi также имеет режим многострочного ввода, активируемый :set +m, :set +m, в котором GHCi автоматически определяет, когда текущий оператор не завершен, и позволяет добавлять дополнительные строки. Многострочный ввод завершается пустой строкой. Например:

ghci> :set +m
ghci> let x = 42
ghci|

Дополнительные привязки могут быть добавлены к этому let оператору, поэтому GHCi указывает, что следующая строка продолжает предыдущую, изменяя приглашение. Обратите внимание, что выравнивание используется, поэтому, чтобы добавить больше привязок к этому let оператору, мы должны выровнять их:

ghci> :set +m
ghci> let x = 42
ghci|     y = 3
ghci|
ghci>

Явные фигурные скобки и точки с запятой могут использоваться вместо выравнивания:

ghci> do {
ghci| putStrLn "hello"
ghci| ;putStrLn "world"
ghci| }
hello
world
ghci>

Обратите внимание, что после закрывающей фигурной скобки GHCi знает, что текущий оператор завершен, поэтому пустая строка не требуется.

Режим многострочного ввода полезен при вводе монадических do операторов:

ghci> flip evalStateT 0 $ do
ghci| i <- get
ghci| lift $ do
ghci|   putStrLn "Hello World!"
ghci|   print i
ghci|
"Hello World!"
0
ghci>

Во время многострочного взаимодействия пользователь может прервать и вернуться к приглашению верхнего уровня.

ghci> do
ghci| putStrLn "Hello, World!"
ghci| ^C
ghci>

3.4.4. Объявления типов, классов и других элементов

В командной строке GHCi вы также можете ввести любое верхнеуровневое объявление Haskell, включая data, type, newtype, class, instance, deriving, и foreign объявления. Например:

ghci> data T = A | B | C deriving (Eq, Ord, Show, Enum)
ghci> [A ..]
[A,B,C]
ghci> :i T
data T = A | B | C      -- Defined at <interactive>:2:6
instance Enum T -- Defined at <interactive>:2:45
instance Eq T -- Defined at <interactive>:2:30
instance Ord T -- Defined at <interactive>:2:34
instance Show T -- Defined at <interactive>:2:39

Как и при обычных привязках переменных, более поздние определения затеняют более ранние, поэтому вы можете повторно вводить объявление, чтобы исправить проблему или расширить его. Но есть подвох: когда новое объявление типа затеняет более старое, могут быть и другие объявления, которые ссылаются на старый тип. Следует помнить, что старый тип по-прежнему существует, и эти другие объявления все еще ссылаются на старый тип. Однако, хотя старый и новый тип имеют одинаковое имя, GHCi будет рассматривать их как разные. Например:

ghci> data T = A | B
ghci> let f A = True; f B = False
ghci> data T = A | B | C
ghci> f A

<interactive>:2:3:
    Couldn't match expected type `main::Interactive.T'
                with actual type `T'
    In the first argument of `f', namely `A'
    In the expression: f A
    In an equation for `it': it = f A
ghci>

Старая, затененная, версия T отображается как main::Interactive.T GHCi, чтобы отличить ее от нового T, который отображается просто как T.

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

ghci> type family T a b
ghci> type instance T a b = a
ghci> let uc :: a -> T a b; uc = id

ghci> type instance T a b = b

<interactive>:3:15: error:
    Conflicting family instance declarations:
      T a b = a -- Defined at <interactive>:3:15
      T a b = b -- Defined at <interactive>:5:15

-- Darn! We have to re-declare T.

ghci> type family T a b
-- This is a brand-new T, unrelated to the old one
ghci> type instance T a b = b
ghci> uc 'a' :: Int

<interactive>:8:1: error:
    • Couldn't match type ‘Char’ with ‘Int’
      Expected type: Int
        Actual type: Ghci1.T Char b0
    • In the expression: uc 'a' :: Int
      In an equation for ‘it’: it = uc 'a' :: Int

3.4.5. Что фактически находится в области видимости на приглашении?

Когда вы вводите выражение в приглашении, какие идентификаторы и типы находятся в области видимости? GHCi предоставляет гибкий способ управления тем, как именно создается контекст для выражения:

  • Команды :load, :reload, :add и :unadd (Влияние :load на область видимости).
  • Декларация import (Управление областью видимости с помощью import).
  • Команда :module (Управление областью видимости с помощью команды :module).

Команда :show imports покажет сводку модулей, которые влияют на глобальную область видимости.

Подсказка

GHCi будет выполнять автодополнение имен, которые находятся в области видимости; например, если вы запустите GHCi и наберете J<tab>, GHCi расширит его до Just.

3.4.5.1. Влияние :load на область видимости

Команды :load, :reload, :add и :unadd (Загрузка исходных файлов и Загрузка скомпилированного кода) влияют на глобальную область видимости. Давайте начнем с простых случаев; когда вы запускаете GHCi, приглашение выглядит так:

ghci>

По умолчанию это означает, что все из модуля Prelude в настоящее время находится в области видимости. Если бы приглашение было установлено как %s> в файле конфигурации .ghci, мы бы увидели Prelude> . Однако это не основной механизм из-за большого пространства, которое может занимать приглашение, если выполнено больше импортов.

Синтаксис в приглашении *module указывает, что в область видимости для выражений, набираемых в приглашении, входит вся глобальная область видимости ⟨модуль⟩. Без * видна только экспортная часть модуля.

Примечание

По техническим причинам GHCi может поддерживать только форму * для модулей, которые интерпретируются. Скомпилированные модули и модули пакетов могут только добавлять свои экспортные части в текущую область видимости. Чтобы гарантировать, что GHCi загружает интерпретированную версию модуля, добавьте * при загрузке модуля, например :load *M.

В общем случае после команды :load в область видимости автоматически добавляется импорт для последнего загруженного модуля «цели» в форме * , если это возможно. Например, если вы скажете :load foo.hs bar.hs и bar.hs содержит модуль Bar, тогда область видимости будет установлена в *Bar , если Bar интерпретируется, или если Bar скомпилирован, она будет установлена в Prelude и Bar (GHCi автоматически добавляет Prelude , если оно отсутствует и нет модулей в форме * ). Эти автоматически добавленные импорты можно увидеть с помощью :show imports:

ghci> :load hello.hs
[1 of 1] Compiling Main             ( hello.hs, interpreted )
Ok, modules loaded: Main.
*ghci> :show imports
:module +*Main -- added automatically
*ghci>

и автоматически добавленный импорт будет заменен в следующий раз, когда вы используете :load, :reload, :add или :unadd. Его также можно удалить с помощью :module, как и обычные импорты.

3.4.5.2. Управление областью видимости с помощью import

Мы не ограничены одним модулем: GHCi может объединять области видимости из нескольких модулей в любой комбинации * и не-* форм. GHCi объединяет области видимости всех этих модулей, чтобы сформировать область видимости, которая активна в приглашении.

Чтобы добавить модули в область видимости, используйте обычный синтаксис Haskell import:

ghci> import System.IO
ghci> hPutStrLn stdout "hello\n"
hello

Поддерживается полный синтаксис Haskell import, включая hiding и as пункты. В приглашении показываются модули, которые в настоящее время импортированы, но детали hiding, as и т. д. опускаются. Чтобы увидеть полную картину, используйте :show imports:

ghci> import System.IO
ghci> import Data.Map as Map
ghci Map> :show imports
import Prelude -- implicit
import System.IO
import Data.Map as Map

Обратите внимание, что импорт Prelude помечен как неявный. Его можно переопределить с помощью явного импорта Prelude , как и в Haskell-модуле.

При наличии нескольких модулей в области видимости, особенно нескольких модулей в форме * , вероятно, возникнут конфликты имен. Haskell указывает, что конфликты имен сообщаются только тогда, когда используется неоднозначный идентификатор, и GHCi ведет себя аналогичным образом для выражений, набираемых в приглашении.

3.4.5.3. Управление областью видимости с помощью команды :module

Другой способ управления областью видимости — использование команды :module со следующим синтаксисом:

:module +|- *mod1 ... *modn

Использование формы + команд module добавляет модули в текущую область видимости, а - удаляет их. Без + или - текущая область видимости заменяется набором указанных модулей. Обратите внимание, что если вы используете эту форму и опустите Prelude, будет добавлен неявный импорт Prelude.

Команда :module позволяет сделать две вещи, которые нельзя сделать с обычными import объявлениями:

  • :module поддерживает модификатор * для модулей, который открывает полную глобальную область видимости модуля, а не только его экспортную часть.
  • Импорты можно удалить из контекста с помощью синтаксиса :module -M. Синтаксис import кумулятивный (как в Haskell-модуле), поэтому это единственный способ уменьшить область видимости.

3.4.5.4. Квалифицированные имена

-fimplicit-import-qualified
По умолчанию:

включено

Для большей простоты, GHCi-приглашение также ведет себя так, как будто существует неявное import qualified объявление для каждого модуля в каждом пакете и каждого модуля, загруженного в GHCi. Это поведение можно отключить с помощью флага -fno-implicit-import-qualified.

3.4.5.5. :module и :load

Может показаться, что :module/import и :load/:add/:reload выполняют похожие действия: вы можете использовать их, чтобы добавить модуль в область видимости. Однако есть очень важное различие. GHCi оперирует с двумя наборами модулей:

  • Набор модулей, которые в настоящее время загружены. Этот набор изменяется с помощью :load, :reload, :add и :unadd и может быть показан с помощью :show modules.
  • Набор модулей, которые в настоящее время находятся в области видимости в приглашении. Этот набор изменяется с помощью import и :module, а также автоматически изменяется после :load, :reload, :add и :unadd, как описано выше. Набор модулей в области видимости можно отобразить с помощью :show imports.

Вы можете добавить модуль в область видимости (через :module или import) только если (а) он загружен, или (б) это модуль из пакета, о котором GHCi знает. Попытка добавить в область видимости не загруженный модуль с помощью :module или import может привести к сообщению module M is not loaded.

3.4.5.6. Затенение и имя модуля Ghci1

Связывания в командной строке могут затенять предыдущие связывания:

ghci> let foo = True
ghci> let foo = False
ghci> :show bindings
foo :: Bool = False

Но затенённая вещь всё ещё существует и может появиться позже, например, в сигнатуре типа:

ghci> data T = A | B deriving Eq
ghci> let a = A
ghci> data T = ANewType
ghci> :t a
a :: Ghci1.T

Теперь тип a выводится с использованием полных имён T, используя имя модуля Ghci1 (и Ghci2 для следующего набора связей и так далее). Вы также можете использовать эти полные имена:

ghci> a == Ghci1.A
True
ghci> let a = False -- shadowing a
ghci> Ghci2.a == Ghci1.A
True

Команда :show bindings показывает только связывания, которые не затенены. Связывания, определяющие несколько имён, такие как конструктор типа и его конструкторы данных, отображаются, если любое определённое имя всё ещё доступно без квалификации.

3.4.6. Переменная it

Всякий раз, когда в командной строке вводится выражение (или оператор без связывания), GHCi неявно связывает его значение с переменной it. Например:

ghci> 1+2
3
ghci> it * 2
6

На самом деле GHCi выполняет проверку типа выражения, и если у него нет типа IO, то он преобразует его следующим образом: выражение e преобразуется в

let it = e;
print it

что затем выполняется как действие IO.

Следовательно, исходное выражение должно иметь тип, который является экземпляром класса Show, иначе GHCi выдаст ошибку:

ghci> id

<interactive>:1:0:
    No instance for (Show (a -> a))
      arising from use of `print' at <interactive>:1:0-1
    Possible fix: add an instance declaration for (Show (a -> a))
    In the expression: print it
    In a 'do' expression: print it

Сообщение об ошибке содержит некоторые подсказки относительно внутреннего преобразования.

Если выражение имеет тип IO a для некоторого a, то it будет связано с результатом вычисления IO, который имеет тип a. Например:

ghci> Data.Time.getZonedTime
2017-04-10 12:34:56.93213581 UTC
ghci> print it
2017-04-10 12:34:56.93213581 UTC

Соответствующее преобразование для типа IO e —

it <- e

Обратите внимание, что it затеняется новым значением каждый раз, когда вы оцениваете новое выражение, и старое значение it теряется.

Чтобы прекратить связывание значения it с каждой командой, можно установить флаг -fno-it. Переменная it может быть источником утечки памяти из-за того, как GHCi обрабатывает затенённые объявления (см. Объявления типов, классов и других).

-fno-it

При установке этого флага переменная it больше не будет устанавливаться в результат ранее вычисленного выражения.

3.4.7. Приведение типов по умолчанию в GHCi

ExtendedDefaultRules
С тех пор:

6.8.1

Разрешить приведение типов по умолчанию не только для числовых классов.

Рассмотрим сеанс GHCi:

ghci> reverse []

Что должен сделать GHCi? Строго говоря, программа неоднозначна. show (reverse []) (что вычисляет GHCi здесь) имеет тип Show a => String, и как он отображается, зависит от типа a. Например:

ghci> reverse ([] :: String)
""
ghci> reverse ([] :: [Int])
[]

Однако для пользователя неудобно указывать тип, поэтому GHCi расширяет правила приведения типов по умолчанию языка Haskell (раздел 4.3.4 отчета по Haskell 2010) следующим образом. Стандартные правила берут каждый набор ограничений (C1 a, C2 a, ..., Cn a) для каждой переменной типа a, и присваивают переменной типа значение по умолчанию, если

  1. Переменная типа a не появляется ни в каких других ограничениях
  2. Все классы Ci являются стандартными.
  3. По крайней мере один из классов Ci является числовым.

В командной строке GHCi или с GHC, если указан флаг ExtendedDefaultRules, типы вместо этого разрешаются следующим способом:

Найдите все нерешённые ограничения. Затем:

  • Найдите те, которые имеют вид (C a) , где a — это переменная типа, и разделите эти ограничения на группы, в которых есть общая переменная типа a.
  • Оставьте только те группы, в которых по крайней мере один из классов является интерактивным классом (определено ниже).
  • Теперь для каждой оставшейся группы G последовательно попробуйте каждый тип ty из списка типов по умолчанию; если установка a = ty позволит полностью разрешить ограничения в G, присвойте переменной типа a значение ty.
  • Тип единицы () и тип списка [] добавляются в начало стандартного списка типов, которые используются при приведении типов по умолчанию.

Обратите внимание, что любые многопараметрические ограничения (D a b) или (D [a] Int) не участвуют в этом процессе (ни для помощи, ни для препятствования); но, конечно, они должны быть разрешимы после завершения процесса приведения по умолчанию.

Последний момент означает, что, например, эта программа:

main :: IO ()
main = print def

instance Num ()

def :: (Num a, Enum a) => a
def = toEnum 0

выводит () вместо 0, так как тип приводится по умолчанию к () , а не к Integer.

Цель изменения заключается в том, что действия IO a по умолчанию сводятся к IO (), что, в свою очередь, означает, что ghci не будет пытаться распечатать результат при их выполнении. Это особенно важно для printf, который имеет экземпляр, возвращающий IO a. Однако он может вернуть только undefined (причина, по которой у экземпляра есть этот тип, заключается в том, что printf не требует расширения системы классов), поэтому, если тип приводится по умолчанию к Integer, ghci выдаёт ошибку при выполнении printf.

См. также Действия ввода/вывода в командной строке, чтобы узнать, как монада вычислительного выражения по умолчанию устанавливается в IO , если это возможно.

3.4.7.1. Интерактивные классы

Интерактивные классы (только применимы, когда ExtendedDefaultRules включен): все числовые классы, Show, Eq, Ord, Foldable или Traversable.

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

3.4.7.2. Расширенные правила для объявлений по умолчанию

Поскольку правила приведения по умолчанию смягчены в условиях ExtendedDefaultRules, правила для объявлений по умолчанию также смягчаются. Согласно разделу 4.3.4 отчета по Haskell 2010, объявление по умолчанию выглядит как default (t1, ..., tn), где для каждой ti должно выполняться условие Num ti. Это смягчается до утверждения, что для каждой ti должен существовать интерактивный класс C, такой что выполняется условие C ti. Это означает, что в этих списках могут быть разрешены конструкторы типов. Например, следующее работает, если вы хотите, чтобы ваши ограничения Foldable приводились по умолчанию к Maybe, но ваши ограничения Num по-прежнему приводились по умолчанию к Integer или Double:

default (Maybe, Integer, Double)

3.4.8. Использование пользовательской функции интерактивного вывода

С GHC 7.6.1 GHCi печатает результат выражений, введённых в командной строке, используя функцию System.IO.print. Её сигнатура — Show a => a -> IO (), и она работает путём преобразования значения в String с помощью show.

Это не идеально в некоторых случаях, например, когда вывод длинный или содержит строки с символами, не входящими в ASCII.

Флаг -interactive-print ⟨name⟩ позволяет указать любую функцию типа C a => a -> IO (), для некоторых ограничений C, в качестве функции вывода вычисленных выражений. Функция может находиться в любом загруженном модуле или любом зарегистрированном пакете, но только когда она находится в зарегистрированном пакете, она переживёт :cd, :add, :unadd, :load, :reload или :set.

-interactive-print ⟨name⟩

Установить функцию, используемую GHCi для вывода результатов вычислений. Указанное имя должно иметь тип C a => a -> IO ().

В качестве примера, предположим, что у нас есть специальный модуль вывода:

module SpecPrinter where
import System.IO

sprint a = putStrLn $ show a ++ "!"

Функция sprint добавляет знак восклицания в конец любого выводимого значения. Запуск GHCi с командой:

ghci -interactive-print=SpecPrinter.sprint SpecPrinter

запустит интерактивную сессию, где значения будут выводиться с использованием sprint:

*SpecPrinter> [1,2,3]
[1,2,3]!
*SpecPrinter> 42
42!

Пользовательская функция красивого вывода может использоваться, например, для форматирования древовидных и вложенных структур в более удобочитаемом виде.

Флаг -interactive-print ⟨name⟩ также можно использовать при запуске GHC в -e mode:

% ghc -e "[1,2,3]" -interactive-print=SpecPrinter.sprint SpecPrinter
[1,2,3]!

3.4.9. Стек-трейсы в GHCi

[ Эта экспериментальная функция включена новым флагом -fexternal-interpreter , введённым в GHC 8.0.1. В настоящее время она не поддерживается в Windows.]

GHCi может использовать систему профилирования для сбора информации о стеке вызовов при выполнении интерпретируемого кода. Для доступа к стекам вызовов запустите GHCi так:

ghci -fexternal-interpreter -prof

Это запускает интерпретируемый код в отдельном процессе (см. Запуск интерпретатора в отдельном процессе) и выполняет его в режиме профилирования для сбора информации о стеке вызовов. Обратите внимание, что поскольку мы выполняем интерпретируемый код в режиме профилирования, все используемые пакеты должны быть скомпилированы для профилирования. Флаг -prof для GHCi работает только в сочетании с флагом -fexternal-interpreter.

Существует три способа получить доступ к текущему стеку вызовов.

  • error и undefined автоматически добавляют текущий стек в сообщение об ошибке. Это часто дополняет стек HasCallStack (см. HasCallStack), поэтому отображаются оба стека вызовов.
  • Debug.Trace.traceStack — это версия Debug.Trace.trace, которая также печатает текущий стек вызовов.
  • Функции в модуле GHC.Stack могут использоваться для получения текущего стека и его отображения.

Вам не нужно использовать -fprof-auto для интерпретируемых модулей, аннотации автоматически добавляются с достаточной точностью, чтобы различать отдельные точки входа в вызов. Однако вы не увидите никакой информации о стеке вызовов для скомпилированного кода, если он не был скомпилирован с -fprof-auto или не имеет явных SCC аннотаций (см. Вставка центров стоимости вручную).

3.5. Отладчик GHCi

GHCi содержит простой отладчик императивного стиля, в котором вы можете остановить вычисление для проверки значений переменных. Отладчик интегрирован в GHCi и включён по умолчанию: для включения отладочных функций не требуется никаких флагов. Существует одно важное ограничение: точки останова и пошаговое выполнение доступны только в интерпретируемых модулях; скомпилированный код невидим для отладчика [5].

Отладчик предоставляет следующее:

  • Возможность установить точку останова на определении функции или выражении в программе. Когда функция вызывается или выражение вычисляется, GHCi приостанавливает выполнение и возвращается к приглашению, где вы можете проверить значения локальных переменных перед продолжением выполнения.
  • Выполнение может быть пошагово: вычислитель приостановит выполнение примерно после каждого сокращения, позволяя проверить локальные переменные. Это эквивалентно установке точки останова в каждой точке программы.
  • Выполнение может происходить в режиме трассировки, в котором вычислитель запоминает каждый шаг вычисления по мере его выполнения, но не приостанавливает выполнение до достижения фактической точки останова. В этом случае можно проверить историю шагов вычисления.
  • Исключения (например, неудача сопоставления с образцом и error) могут обрабатываться как точки останова, чтобы помочь найти источник исключения в программе.

В настоящее время нет поддержки получения «стека вызовов», но функции трассировки и истории предоставляют полезную альтернативу, которая часто будет достаточно для установления контекста ошибки. Например, можно автоматически прервать выполнение при возникновении исключения, даже если оно возникло внутри скомпилированного кода (см. Отладка исключений).

-fbreak-points
Значение по умолчанию:

включено для GHCi

Цель этого флага — разрешить отключение вставки точек останова с помощью обратной формы.

3.5.1. Точки останова и просмотр переменных

Рассмотрим быструю сортировку в качестве примера. Вот код:

qsort [] = []
qsort (a:as) = qsort left ++ [a] ++ qsort right
  where (left,right) = (filter (<=a) as, filter (>a) as)

main = print (qsort [8, 4, 0, 3, 1, 23, 11, 18])

Сначала загрузим модуль в GHCi:

ghci> :l qsort.hs
[1 of 1] Compiling Main             ( qsort.hs, interpreted )
Ok, modules loaded: Main.
*ghci>

Теперь установим точку останова справа от второго уравнения qsort:

*ghci> :break 2
Breakpoint 0 activated at qsort.hs:2:15-46
*ghci>

Команда :break 2 устанавливает точку останова на строке 2 в недавно загруженном модуле, в данном случае qsort.hs. Конкретно, она выбирает левую полную подвыражение на этой строке, для установки точки останова, в данном случае выражение (qsort left ++ [a] ++ qsort right).

Теперь запустим программу:

*ghci> main
Stopped at qsort.hs:2:15-46
_result :: [a]
a :: a
left :: [a]
right :: [a]
[qsort.hs:2:15-46] *ghci>

Выполнение остановилось на точке останова. Подсказка изменилась, чтобы указать, что мы в настоящее время остановились на точке останова, и местоположение: [qsort.hs:2:15-46]. Для большей ясности местоположения, можно использовать команду :list:

[qsort.hs:2:15-46] *ghci> :list
1  qsort [] = []
2  qsort (a:as) = qsort left ++ [a] ++ qsort right
3    where (left,right) = (filter (<=a) as, filter (>a) as)

Команда :list выводит исходный код вокруг текущей точки останова. Если устройство вывода поддерживает это, то GHCi выделит активное подвыражение жирным шрифтом.

GHCi предоставил привязки для свободных переменных [6] выражения, на котором была размещена точка останова (a, left, right ) и дополнительную привязку к результату выражения (_result). Эти переменные такие же, как и другие переменные, которые вы можете определить в GHCi; вы можете использовать их в выражениях, которые вы набираете в подсказке, вы можете запросить их типы с помощью :type и так далее. Есть одно важное отличие: эти переменные могут иметь только частичные типы. Например, если мы попытаемся отобразить значение left:

[qsort.hs:2:15-46] *ghci> left

<interactive>:1:0:
    Ambiguous type variable `a' in the constraint:
      `Show a' arising from a use of `print' at <interactive>:1:0-3
    Cannot resolve unknown runtime types: a
    Use :print or :force to determine these types

Это потому, что qsort — это полиморфная функция, и так как GHCi не сохраняет информацию о типах во время выполнения, он не может определить типы переменных во время выполнения, которые включают переменные типов. Следовательно, когда вы запрашиваете отображение left в подсказке, GHCi не может понять, какой экземпляр Show использовать, поэтому он выводит ошибку типа, указанную выше.

К счастью, отладчик включает общую команду печати :print, которая может проверить фактическое значение переменной во время выполнения и попытаться восстановить ее тип. Если мы попробуем это на left:

[qsort.hs:2:15-46] *ghci> :set -fprint-evld-with-show
[qsort.hs:2:15-46] *ghci> :print left
left = (_t1::[a])

Это не особенно информативно. Что произошло, так это то, что left привязано к невычисленному вычислению (подвешенное состояние или thunk), и :print не форсирует никакого вычисления. Идея в том, что :print может использоваться для проверки значений в точке останова без каких-либо нежелательных побочных эффектов. Она не будет принудительно вычислять никакие значения, что могло бы привести к получению программой другого ответа, чем обычно, и, следовательно, не вызовет никаких исключений, бесконечных циклов или срабатывания дополнительных точек останова (см. Вложенные точки останова). Вместо принудительного вычисления thunk, :print привязывает каждый thunk к новой переменной, начинающейся с подчеркивания, в этом случае _t1.

-fprint-evld-with-show

Флаг -fprint-evld-with-show инструктирует :print использовать доступные Show экземпляры по возможности. Это происходит только тогда, когда содержимое проверяемой переменной полностью вычислено.

Если нас не беспокоит сохранение вычисленного состояния переменной, мы можем использовать :force вместо :print. Команда :force ведет себя точно так же, как :print, за исключением того, что она форсирует вычисление всех встреченных thunk:

[qsort.hs:2:15-46] *ghci> :force left
left = [4,0,3,1]

Теперь, поскольку :force проверил значение во время выполнения left, он восстановил его тип. Мы можем увидеть результаты этого восстановления типов:

[qsort.hs:2:15-46] *ghci> :show bindings
_result :: [Integer]
a :: Integer
left :: [Integer]
right :: [Integer]
_t1 :: [Integer]

Теперь мы не только знаем тип left, но и все остальные частичные типы также были решены. Поэтому мы можем запросить значение a, например:

[qsort.hs:2:15-46] *ghci> a
8

Вам может быть полезно использовать функцию Haskell seq для вычисления отдельных thunk вместо вычисления всего выражения с помощью :force. Например:

[qsort.hs:2:15-46] *ghci> :print right
right = (_t1::[Integer])
[qsort.hs:2:15-46] *ghci> seq _t1 ()
()
[qsort.hs:2:15-46] *ghci> :print right
right = 23 : (_t2::[Integer])

Мы вычислили только thunk _t1, показав начало списка, а хвост — это другой thunk, сейчас связанный с _t2. Функция seq немного неудобна в использовании здесь, поэтому вы можете использовать :def, чтобы сделать более удобный интерфейс (оставляется в качестве упражнения для читателя!).

Наконец, мы можем продолжить текущее выполнение:

[qsort.hs:2:15-46] *ghci> :continue
Stopped at qsort.hs:2:15-46
_result :: [a]
a :: a
left :: [a]
right :: [a]
[qsort.hs:2:15-46] *ghci>

Выполнение продолжилось с точки, где оно ранее остановилось, и теперь остановилось на точке останова во второй раз.

3.5.1.1. Установка точек останова

Точки останова можно установить различными способами. Возможно, самый простой способ установить точку останова — назвать функцию верхнего уровня:

:break identifier

Где ⟨идентификатор⟩ обозначает любую функцию верхнего уровня в интерпретируемом модуле, в настоящее время загруженном в GHCi (могут использоваться квалифицированные имена). Точка останова будет установлена в теле функции, когда она будет полностью применена. Если функция имеет несколько шаблонов, то точка останова будет установлена на каждом из них.

Используя квалифицированные имена, можно установить точки останова на все функции (верхнего уровня и вложенные) во всех загруженных и интерпретируемых модулях:

:break [ModQual.]topLevelIdent[.nestedIdent]...[.nestedIdent]

⟨ModQual⟩ — необязательно и представляет собой либо фактическое имя модуля, либо локальный псевдоним квалифицированного оператора импорта.

⟨topLevelIdent⟩ — это имя функции верхнего уровня в модуле, на который ссылается ⟨ModQual⟩.

⟨nestedIdent⟩ — необязательно и представляет собой имя функции, вложенной в оператор let или where внутри ранее упомянутой функции ⟨nestedIdent⟩ или ⟨topLevelIdent⟩.

Если ⟨ModQual⟩ — это имя модуля, то ⟨topLevelIdent⟩ может быть любым идентификатором верхнего уровня в этом модуле. Если ⟨ModQual⟩ отсутствует или является локальным псевдонимом квалифицированного импорта, то ⟨topLevelIdent⟩ должен быть в области видимости.

Точки останова можно устанавливать на произвольно вложенные функции, но цепочка вложенных имен функций должна быть указана полностью.

Рассмотрим функцию foo в модуле Main:

foo s = 'a' : add s
    where add = (++"z")

Точку останова на функции add можно установить с помощью одной из следующих команд:

:break Main.foo.add
:break foo.add

Точки останова также можно устанавливать по номеру строки (и, необязательно, по номеру столбца):

:break line
:break line column
:break module line
:break module line column

Когда точка останова устанавливается на конкретной строке, GHCi устанавливает точку останова на левом наименьшем подвыражении, которое начинается и заканчивается на этой строке. Если две полные подвыражения начинаются в одном столбце, выбирается наибольшее. Если на строке нет полного подвыражения, то выбирается самое левое выражение, начинающееся на строке, а в противном случае — самое правое выражение, частично или полностью покрывающее строку.

Когда точка останова устанавливается на конкретной строке и столбце, GHCi выбирает наименьшее подвыражение, которое охватывает это местоположение, для установки точки останова. Примечание: GHC рассматривает символ TAB как имеющий ширину 1, где бы он ни встречался; другими словами, он считает символы, а не столбцы. Это соответствует действиям некоторых редакторов, но не других. Лучший совет — вообще избегать символов TAB в исходном коде (см. -Wtabs в Проверки и проверки на корректность).

Если модуль опущен, используется последний загруженный модуль.

Не все подвыражения являются потенциальными местами установки точек останова. Одиночные переменные обычно не считаются местами установки точек останова (если переменная не является правой частью определения функции, лямбды или альтернативы case). Правило состоит в том, что все редекси являются местами установки точек останова вместе с телами функций, лямбд, альтернатив case и операторов присваивания. Обычно нет точки останова на выражении let, но всегда будет точка останова на его теле, поскольку мы обычно заинтересованы в проверке значений, связанных с оператором let.

3.5.1.2. Управление точками останова

Список точек останова, которые в данный момент определены, можно отобразить с помощью :show breaks:

*ghci> :show breaks
[0] Main qsort.hs:1:11-12 enabled
[1] Main qsort.hs:2:15-46 enabled

Чтобы отключить одну или несколько определенных точек останова, используйте команду :disable с одним или несколькими разделенными пробелами номерами, приведенными в выводе из :show breaks:. Чтобы отключить все точки останова сразу, используйте :disable *.

*ghci> :disable 0
*ghci> :show breaks
[0] Main qsort.hs:1:11-12 disabled
[1] Main qsort.hs:2:15-46 enabled

Отключенные точки останова можно (снова) включить с помощью команды :enable. Параметры команд :disable и :enable идентичны.

Чтобы удалить точку останова, используйте команду :delete с номером, приведенным в выводе из :show breaks:

*ghci> :delete 0
*ghci> :show breaks
[1] Main qsort.hs:2:15-46 disabled

Чтобы удалить все точки останова сразу, используйте :delete *.

3.5.2. Пошаговая отладка

Пошаговая отладка — отличный способ визуализировать выполнение вашей программы и полезный инструмент для выявления источника ошибки. GHCi предлагает два варианта пошаговой отладки. Используйте :step, чтобы активировать все точки останова в программе и выполнить ее до достижения следующей точки останова. Используйте :steplocal, чтобы ограничить набор активных точек останова только теми, которые находятся в текущей функции верхнего уровня. Аналогично, используйте :stepmodule, чтобы выполнять пошаговую отладку только на точках останова в текущем модуле. Например:

*ghci> :step main
Stopped at qsort.hs:5:7-47
_result :: IO ()

Команда :step expr начинает вычисление ⟨expr⟩ в режиме пошаговой отладки. Если ⟨expr⟩ опущено, выполняется пошаговая отладка от текущей точки останова. Команды :steplocal и :stepmodule работают аналогично.

Команда :list особенно полезна при пошаговой отладке, чтобы увидеть, где вы находитесь в данный момент:

[qsort.hs:5:7-47] *ghci> :list
4
5  main = print (qsort [8, 4, 0, 3, 1, 23, 11, 18])
6
[qsort.hs:5:7-47] *ghci>

На самом деле, GHCi предоставляет возможность выполнения команды при достижении точки останова, поэтому мы можем автоматически выполнить :list:

[qsort.hs:5:7-47] *ghci> :set stop :list
[qsort.hs:5:7-47] *ghci> :step
Stopped at qsort.hs:5:14-46
_result :: [Integer]
4
5  main = print (qsort [8, 4, 0, 3, 1, 23, 11, 18])
6
[qsort.hs:5:14-46] *ghci>

3.5.3. Вложенные точки останова

Когда GHCi останавливается на точке останова, и введённая в приглашении выражение запускает вторую точку останова, новая точка останова становится «текущей», а старая сохраняется в стеке. Таким образом, можно создать произвольное количество контекстов точек останова. Например:

[qsort.hs:2:15-46] *ghci> :st qsort [1,3]
Stopped at qsort.hs:(1,0)-(3,55)
_result :: [a]
... [qsort.hs:(1,0)-(3,55)] *ghci>

Остановившись на точке останова на строке 2, которую мы установили ранее, мы запустили новое вычисление с помощью :step qsort [1,3]. Это новое вычисление остановилось после одного шага (на определении qsort). Приглашение изменилось, теперь с префиксом ..., чтобы указать, что существуют сохранённые точки останова за пределами текущей. Чтобы увидеть стек контекстов, используйте :show context:

... [qsort.hs:(1,0)-(3,55)] *ghci> :show context
--> main
  Stopped at qsort.hs:2:15-46
--> qsort [1,3]
  Stopped at qsort.hs:(1,0)-(3,55)
... [qsort.hs:(1,0)-(3,55)] *ghci>

Чтобы отказаться от текущего вычисления, используйте :abandon:

... [qsort.hs:(1,0)-(3,55)] *ghci> :abandon
[qsort.hs:2:15-46] *ghci> :abandon
*ghci>

3.5.4. Переменная _result

При остановке на точке останова или пошаговой отладке GHCi связывает переменную _result со значением текущего активного выражения. Значение _result предположительно ещё недоступно, потому что мы остановили его вычисление, но его можно принудительно вычислить: если тип известен и отображается, то просто ввод _result в приглашении отобразит его. Однако есть одно предостережение: вычисление _result вероятно вызовет дальнейшие точки останова, начиная с точки останова, на которой мы остановились (если мы остановились на реальной точке останова, а не из-за :step). Поэтому, вероятно, необходимо сразу выполнить :continue при вычислении _result. В качестве альтернативы можно использовать :force, которое игнорирует точки останова.

3.5.5. Отслеживание и история

Вопрос, который мы часто задаём при отладке программы, — «как я сюда попал?». Традиционные отладчики императивных языков обычно предоставляют функцию отслеживания стека, позволяющую увидеть стек активных вызовов функций (иногда называемый «лексический стек вызовов»), описывая путь по коду до текущей позиции. К сожалению, это трудно обеспечить в Haskell, потому что выполнение происходит по запросу, а не по принципу «глубина вглубь», как в строгих языках. «Стек» в исполняющей системе GHC слабо связан с лексическим стеком вызовов. В идеале GHCi должен поддерживать отдельный лексический стек вызовов дополнительно к динамическому стеку вызовов, и, на самом деле, это делает наша система профилирования (Профилирование), и некоторые другие отладчики Haskell. Однако на данный момент GHCi не поддерживает лексический стек вызовов (есть некоторые технические проблемы, которые нужно решить). Вместо этого мы предоставляем способ отслеживания назад от точки останова до предыдущих этапов вычислений: по сути, это как пошаговая отладка назад, и во многих случаях она должна обеспечить достаточную информацию для ответа на вопрос «как я сюда попал?».

Для использования отслеживания вычислите выражение с помощью команды :trace. Например, если мы установили точку останова на базовом случае qsort;

*ghci> :list qsort
1  qsort [] = []
2  qsort (a:as) = qsort left ++ [a] ++ qsort right
3    where (left,right) = (filter (<=a) as, filter (>a) as)
4
*ghci> :b 1
Breakpoint 1 activated at qsort.hs:1:11-12
*ghci>

а затем запустили небольшое qsort с отслеживанием:

*ghci> :trace qsort [3,2,1]
Stopped at qsort.hs:1:11-12
_result :: [a]
[qsort.hs:1:11-12] *ghci>

Теперь мы можем проверить историю шагов вычисления:

[qsort.hs:1:11-12] *ghci> :hist
-1  : qsort.hs:3:24-38
-2  : qsort.hs:3:23-55
-3  : qsort.hs:(1,0)-(3,55)
-4  : qsort.hs:2:15-24
-5  : qsort.hs:2:15-46
-6  : qsort.hs:3:24-38
-7  : qsort.hs:3:23-55
-8  : qsort.hs:(1,0)-(3,55)
-9  : qsort.hs:2:15-24
-10 : qsort.hs:2:15-46
-11 : qsort.hs:3:24-38
-12 : qsort.hs:3:23-55
-13 : qsort.hs:(1,0)-(3,55)
-14 : qsort.hs:2:15-24
-15 : qsort.hs:2:15-46
-16 : qsort.hs:(1,0)-(3,55)
<end of history>

Чтобы проверить один из шагов в истории, используйте :back:

[qsort.hs:1:11-12] *ghci> :back
Logged breakpoint at qsort.hs:3:24-38
_result :: [a]
as :: [a]
a :: a
[-1: qsort.hs:3:24-38] *ghci>

Обратите внимание, что локальные переменные на каждом шаге в истории сохранились и могут быть проверены как обычно. Также обратите внимание, что приглашение изменилось, чтобы указать, что мы сейчас проверяем первый шаг в истории: -1. Команда :forward может использоваться для перемещения вперёд в истории.

Команда :trace может использоваться с выражением или без него. Если используется без выражения, отслеживание начинается с текущей точки останова, как и :step.

История доступна только при использовании :trace; причина в том, что мы обнаружили, что регистрация каждой точки останова в истории снижает производительность в 2 раза или более.

-fghci-hist-size=⟨n⟩
Значение по умолчанию:

50

Изменить глубину истории вычислений, отслеживаемую GHCi.

3.5.6. Отладка исключений

Ещё один распространённый вопрос при отладке — «откуда взялось это исключение?». Исключения, такие как те, которые генерируются error или head [], не содержат связанной с ними информации о контексте. Выявление конкретного вызова head в вашей программе, который привёл к ошибке, может быть кропотливым процессом, обычно включающим Debug.Trace.trace, или компиляцию с профилированием и использование Debug.Trace.traceStack или +RTS -xc (см. -xc).

Отладчик GHCi предлагает способ быстро и без изменения или перекомпиляции исходного кода прояснить эти ошибки. Один из способов — установить точку останова в месте в исходном коде, которое вызывает исключение, а затем использовать :trace и :history для определения контекста. Однако head находится в библиотеке, и мы не можем установить на него точку останова напрямую. По этой причине GHCi предоставляет флаги -fbreak-on-exception, которые заставляют оценщик останавливаться при возникновении исключения, и -fbreak-on-error, которые работают аналогичным образом, но останавливаются только на неперехваченных исключениях. При остановке на исключении GHCi будет действовать так же, как и при достижении точки останова, за исключением того, что он не покажет вам расположение в исходном коде. По этой причине эти команды действительно полезны только в сочетании с :trace для регистрации шагов, предшествующих исключению. Например:

*ghci> :set -fbreak-on-exception
*ghci> :trace qsort ("abc" ++ undefined)
“Stopped at <exception thrown>
_exception :: e
[<exception thrown>] *ghci> :hist
-1  : qsort.hs:3:24-38
-2  : qsort.hs:3:23-55
-3  : qsort.hs:(1,0)-(3,55)
-4  : qsort.hs:2:15-24
-5  : qsort.hs:2:15-46
-6  : qsort.hs:(1,0)-(3,55)
<end of history>
[<exception thrown>] *ghci> :back
Logged breakpoint at qsort.hs:3:24-38
_result :: [a]
as :: [a]
a :: a
[-1: qsort.hs:3:24-38] *ghci> :force as
*** Exception: Prelude.undefined
[-1: qsort.hs:3:24-38] *ghci> :print as
as = 'b' : 'c' : (_t1::[Char])

Само исключение привязано к новой переменной _exception.

Остановка на исключениях особенно полезна для выявления того, что ваша программа делала, когда она находилась в бесконечном цикле. Просто нажмите Control-C и изучите историю, чтобы узнать, что происходило.

-fbreak-on-exception

Заставляет GHCi приостановить вычисление и вернуться к интерактивному приглашению в случае возникновения исключения. -fbreak-on-exception останавливает работу на всех исключениях.

-fbreak-on-error

Заставляет GHCi приостановить вычисление и вернуться к интерактивному приглашению в случае возникновения исключения. -fbreak-on-error останавливается только на тех исключениях, которые в противном случае не перехватывались.

3.5.7. Пример: инспектирование функций

Можно использовать отладчик для проверки значений функций. Когда мы находимся на точке останова и функция находится в области видимости, отладчик не может показать вам исходный код для неё; однако, получить некоторую информацию можно, применив её к некоторым аргументам и наблюдая результат.

Процесс немного усложняется, когда привязка полиморфна. Мы покажем процесс на примере. Для упрощения мы будем использовать хорошо известную функцию map:

import Prelude hiding (map)

map :: (a->b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs

Мы устанавливаем точку останова на map, и вызываем её.

*ghci> :break 5
Breakpoint 0 activated at  map.hs:5:15-28
*ghci> map Just [1..5]
Stopped at map.hs:(4,0)-(5,12)
_result :: [b]
x :: a
f :: a -> b
xs :: [a]

GHCi сообщает нам, что, среди других привязок, f находится в области видимости. Однако её тип ещё не полностью известен, и поэтому её нельзя применить к каким-либо аргументам. Тем не менее, обратите внимание, что тип её первого аргумента такой же, как тип x, а тип результата совпадает с типом _result.

Как мы демонстрировали ранее (Точки останова и инспектирование переменных), отладчик имеет встроенную возможность обновлять тип f всякий раз, когда обнаруживаются типы x или _result. Поэтому в данном случае мы немного «принуждаем» x, чтобы восстановить как её тип, так и часть аргумента f.

*ghci> seq x ()
*ghci> :print x
x = 1

Теперь мы можем проверить, что, как ожидалось, тип x был восстановлен, а с ним и тип f:

*ghci> :t x
x :: Integer
*ghci> :t f
f :: Integer -> b

Отсюда мы можем применить f к любому аргументу типа Integer и наблюдать результаты.

*ghci> let b = f 10
*ghci> :t b
b :: b
*ghci> b
<interactive>:1:0:
    Ambiguous type variable `b' in the constraint:
      `Show b' arising from a use of `print' at <interactive>:1:0
*ghci> :p b
b = (_t2::a)
*ghci> seq b ()
()
*ghci> :t b
b :: a
*ghci> :p b
b = Just 10
*ghci> :t b
b :: Maybe Integer
*ghci> :t f
f :: Integer -> Maybe Integer
*ghci> f 20
Just 20
*ghci> map f [1..5]
[Just 1, Just 2, Just 3, Just 4, Just 5]

При первом применении f, нам пришлось выполнить больше реконструкции типов, чтобы восстановить тип результата f. Но после этого мы можем свободно использовать f в обычном режиме.

3.5.8. Ограничения

  • Когда вы останавливаетесь на точке останова, если вы пытаетесь оценить переменную, которая уже находится в процессе оценки, вторая оценка зависнет. Причина в том, что GHC знает, что переменная находится в процессе оценки, поэтому новая оценка просто ждёт результата, прежде чем продолжить, но, конечно же, этого не произойдёт, потому что первая оценка остановлена на точке останова. Команда Ctrl+C может прервать зависшую оценку и вернуться к приглашению.

    Наиболее распространённый способ, которым это может произойти, – это когда вы оцениваете CAF (например, main), останавливаетесь на точке останова и снова запрашиваете значение CAF в приглашении.

  • Неявные параметры (см. Неявные параметры) доступны только в области видимости точки останова, если есть явное описание типа.

3.6. Вызов GHCi

GHCi вызывается командой ghci или ghc --interactive. Также можно указать один или несколько модулей или файлов в командной строке; это инструктирует GHCi загрузить указанные модули или файлы (и все модули, от которых они зависят), так же, как если бы вы сказали :load modules в приглашении GHCi (см. Команды GHCi). Например, чтобы запустить GHCi и загрузить программу, главный модуль которой находится в файле Main.hs, можно сказать:

$ ghci Main.hs

Большинство командных строк, принимаемых GHC (см. Использование GHC), также имеют смысл в интерактивном режиме. Те, которые не имеют смысла, в основном очевидны.

-flocal-ghci-history

По умолчанию GHCi сохраняет глобальную историю в $XDG_DATA_HOME/ghc/ghci_history или %APPDATA%/<app>/ghci_history, но вы можете использовать текущий каталог, например:

$ ghci -flocal-ghci-history

Он создаст .ghci-history в текущей папке, где запускается GHCi.

-fghci-leak-check

(Только для отладки) При загрузке новых модулей с помощью :load, проверьте, что все ранее загруженные модули были правильно удалены сборщиком мусора. Выводит сообщения, если утечка обнаружена.

3.6.1. Пакеты

Большинство пакетов (см. Использование пакетов) доступны без необходимости указания дополнительных флагов: они будут автоматически загружены в первый раз, когда понадобятся.

Однако для скрытых пакетов вам нужно запросить загрузку пакета, используя флаг -package ⟨pkg⟩:

$ ghci -package readline
GHCi, version 8.y.z: https://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
Loading package readline-1.0 ... linking ... done.
ghci>

Следующая команда работает для загрузки новых пакетов в работающий GHCi:

ghci> :set -package name

Но обратите внимание, что это приведёт к разгрузке всех загруженных в настоящее время модулей и вас вернут к приглашению Prelude.

3.6.2. Дополнительные библиотеки

Дополнительные библиотеки могут быть указаны в командной строке с помощью обычного параметра -llib. (Здесь термин библиотека относится к библиотекам внешнего кода; для использования библиотек кода Haskell см. Модули против файлов.) Например, для загрузки библиотеки «m»:

$ ghci -lm

В системах с библиотеками совместно используемого кода .so-стиля фактически загруженной библиотекой будет liblib.so. GHCi ищет библиотеки в следующих местах в этом порядке:

  • Пути, указанные с помощью командной строки -L ⟨dir⟩,
  • Стандартный путь поиска библиотек для вашего системного загрузчика, который в некоторых системах может быть изменён установкой переменной окружения LD_LIBRARY_PATH,
  • Стандартный путь поиска библиотек линковщика также может быть переопределён в некоторых системах, используя переменную окружения LIBRARY_PATH. Из-за некоторых особенностей реализации в Windows, установка LIBRARY_PATH также расширит путь поиска системного загрузчика для любой найденной им библиотеки. Таким образом, часто достаточно установить LIBRARY_PATH.

В системах с библиотеками совместно используемого кода .dll-стиля фактически загруженной библиотекой будет lib.dll, liblib.dll. GHCi также полностью поддерживает импорт библиотек, либо в стиле Microsoft .lib, либо в стиле GNU GCC .a и .dll.a библиотек. Если у вас есть библиотека импорта, рекомендуется всегда указывать библиотеку импорта вместо .dll. Например, используйте -lgcc` instead of ``-llibgcc_s_seh-1. Снова, GHCi выдаст ошибку, если не найдёт библиотеку.

Порядок параметров -l имеет значение: библиотека должна быть упомянута перед библиотеками, от которых она зависит (см. Параметры, влияющие на линковку).

3.7. Команды GHCi

Все команды GHCi начинаются с «:» и состоят из имени команды, за которым могут следовать один или несколько параметров. Имя команды может быть сокращено, при этом неоднозначности разрешаются в пользу наиболее часто используемых команд.

:abandon

Отменяет текущее вычисление (доступно только при остановке на точке останова).

:add [*]⟨module⟩

Добавляет ⟨модуль⟩(и) в текущий целевой набор и выполняет перезагрузку. Обычно предварительно скомпилированный код для модуля будет загружен, если он доступен, в противном случае модуль будет скомпилирован в байт-код. Использование префикса * принудительно загружает модуль в виде байт-кода.

⟨модуль⟩ может быть путем к файлу. Символ «~» в начале ⟨модуля⟩ будет заменён содержимым переменной окружения HOME.

:all-types

Отображает все типы, собранные для выражений и (локальных) связей, загруженных в настоящее время (в то время как :set +c был активен), с соответствующим фрагментом исходного кода, например:

GhciTypes> :all-types
GhciTypes.hs:(38,13)-(38,24): Maybe Id
GhciTypes.hs:(45,10)-(45,29): Outputable SpanInfo
GhciTypes.hs:(45,10)-(45,29): (Rational -> SpanInfo -> SDoc) -> Outputable SpanInfo
:back ⟨n⟩

Возвращается на ⟨n⟩ шагов назад в истории. ⟨n⟩ равно 1, если не указано. См. Отслеживание и история для получения дополнительной информации об инструментах отладки GHCi. См. также: :trace, :history, :forward.

:break [⟨identifier⟩ | [⟨module⟩] ⟨line⟩ [⟨column⟩]]

Устанавливает точку останова для указанной функции или строки и столбца. См. Установка точек останова.

:browse[!] [[*] ⟨module⟩]

Отображает идентификаторы, экспортируемые модулем ⟨модуль⟩, который должен быть загружен в GHCi или являться членом пакета. Если ⟨модуль⟩ опущен, используется последний загруженный модуль.

Как и все остальные команды GHCi, вывод всегда отображается в текущем контексте GHCi (Что реально находится в области видимости на приглашении?).

Существуют два варианта команды browse:

  • Если перед именем модуля помещён символ *, то отображаются все идентификаторы в области видимости в ⟨модуле⟩ (а не только экспортированные).

    Вариант с * доступен только для интерпретируемых модулей; для скомпилированных модулей (включая модули из пакетов) доступен только вариант без *, :browse.

  • Конструкторы данных и методы классов обычно отображаются в контексте их объявления типа данных или класса. Однако, если к команде добавлен символ !, таким образом :browse!, они перечисляются индивидуально. Вариант с ! также снабжает список комментариями, указывающими на возможные импорты для каждой группы записей. Вот пример:

    ghci> :browse! Data.Maybe
    -- not currently imported
    Data.Maybe.catMaybes :: [Maybe a] -> [a]
    Data.Maybe.fromJust :: Maybe a -> a
    Data.Maybe.fromMaybe :: a -> Maybe a -> a
    Data.Maybe.isJust :: Maybe a -> Bool
    Data.Maybe.isNothing :: Maybe a -> Bool
    Data.Maybe.listToMaybe :: [a] -> Maybe a
    Data.Maybe.mapMaybe :: (a -> Maybe b) -> [a] -> [b]
    Data.Maybe.maybeToList :: Maybe a -> [a]
    -- imported via Prelude
    Just :: a -> Maybe a
    data Maybe a = Nothing | Just a
    Nothing :: Maybe a
    maybe :: b -> (a -> b) -> Maybe a -> b
    

    Этот вывод показывает, что в контексте текущего сеанса (т. е. в области видимости Prelude) первая группа элементов из Data.Maybe не находятся в области видимости (хотя они доступны в полностью квалифицированной форме в сеансе GHCi - см. Что реально находится в области видимости на приглашении?), тогда как вторая группа элементов находятся в области видимости (через Prelude) и поэтому доступны либо без квалификации, либо с квалификатором Prelude..

:cd ⟨dir⟩

Изменяет текущий рабочий каталог на ⟨каталог⟩. Символ «~» в начале ⟨каталога⟩ будет заменен содержимым переменной окружения HOME. См. также команду :show paths для отображения текущего рабочего каталога.

Примечание: изменение каталогов приводит к разгрузке всех загруженных в настоящее время модулей. Это связано с тем, что путь поиска обычно выражается с использованием относительных каталогов, и изменение пути поиска в середине сеанса не поддерживается.

:cmd ⟨expr⟩

Выполняет ⟨выражение⟩ как вычисление типа IO String, а затем выполняет полученную строку как список команд GHCi. Несколько команд разделяются символами новой строки. Команда :cmd полезна с командами :def и :set stop.

:complete ⟨type⟩ [⟨n⟩-][⟨m⟩] ⟨string-literal⟩

Эта команда позволяет запрашивать завершения команд в GHCi даже при взаимодействии через канал вместо обычного терминала и предназначена для интеграции завершения GHCi с текстовыми редакторами и IDE.

При вызове :complete печатает ⟨n⟩-й по ⟨m⟩-й кандидаты завершения для частичного ввода ⟨строковая-литераль⟩ для области завершения, обозначенной ⟨тип⟩. В настоящее время поддерживается только область repl, которая обозначает вид завершения, который был бы предоставлен GHCi интерактивно на приглашении для ввода.

Если опущены, ⟨n⟩ и ⟨m⟩ по умолчанию принимают значения первого или последнего кандидата завершения, соответственно. Если кандидатов меньше, чем запрошено в аргументе диапазона, ⟨n⟩ и ⟨m⟩ неявно ограничиваются количеством доступных кандидатов завершения.

Вывод команды :complete начинается со строки заголовка, содержащей три поля, разделенные пробелами:

  • Целое число, обозначающее количество l напечатанных завершений,
  • целое число, обозначающее общее количество доступных завершений, и, наконец
  • строковая литерал, обозначающая общий префикс, который должен быть добавлен к возвращаемым кандидатам завершения.

За строкой заголовка следуют ⟨l⟩ строк, каждая из которых содержит одного кандидата завершения, закодированного как (цитируемую) строковую литерал. Вот несколько примеров вызовов, демонстрирующие различные случаи:

ghci> :complete repl 0 ""
0 470 ""
ghci> :complete repl 5 "import For"
5 21 "import "
"Foreign"
"Foreign.C"
"Foreign.C.Error"
"Foreign.C.String"
"Foreign.C.Types"
ghci> :complete repl 5-10 "import For"
6 21 "import "
"Foreign.C.Types"
"Foreign.Concurrent"
"Foreign.ForeignPtr"
"Foreign.ForeignPtr.Safe"
"Foreign.ForeignPtr.Unsafe"
"Foreign.Marshal"
ghci> :complete repl 20- "import For"
2 21 "import "
"Foreign.StablePtr"
"Foreign.Storable"
ghci> :complete repl "map"
3 3 ""
"map"
"mapM"
"mapM_"
ghci> :complete repl 5-10 "map"
0 3 ""
:continue [⟨ignoreCount⟩]

Продолжает текущее вычисление, когда оно остановлено на точке останова.

Если указан ⟨ignoreCount⟩, программа проигнорирует текущую точку останова на следующие ⟨ignoreCount⟩ итерации. См. команду :ignore.

:def[!] ⟨name⟩ ⟨expr⟩

:def используется для определения новых команд или макросов в GHCi. Команда :def ⟨name⟩ ⟨expr⟩ определяет новую команду GHCi :name, реализованную выражением Haskell ⟨выражение⟩, который должен иметь тип String -> IO String. Когда вводится :name args, GHCi выполнит выражение (name args), получит результат String, и передаст его обратно в GHCi как новую последовательность команд. Разные команды в результате должны быть разделены «\n».

Это немного запутанно, поэтому вот несколько примеров. Для начала, вот новая команда GHCi, которая не принимает никаких аргументов и не производит никаких результатов, а только выводит текущую дату и время:

ghci> let date _ = Data.Time.getZonedTime >>= print >> return ""
ghci> :def date date
ghci> :date
2017-04-10 12:34:56.93213581 UTC

Вот пример команды, принимающей аргумент. Это повторная реализация :cd:

ghci> let mycd d = System.Directory.setCurrentDirectory d >> return ""
ghci> :def mycd mycd
ghci> :mycd ..

Или я могу определить простой способ вызова «ghc --make Main» в текущем каталоге:

ghci> :def make (\_ -> return ":! ghc --make Main")

Мы можем определить команду, которая читает вход GHCi из файла. Это может быть полезно для создания набора связей, которые мы хотим многократно загружать в сеанс GHCi:

ghci> :def . readFile
ghci> :. cmds.ghci

Обратите внимание, что мы назвали команду :., аналогично команде Unix-оболочки «.», выполняющей ту же функцию.

Написание :def само по себе отображает текущие определенные макросы. Попытка переопределить имя существующей команды приводит к ошибке, если не используется форма :def!, в этом случае старая команда с этим именем будет безмолвно перезаписана. Однако для встроенных команд старая команда может быть использована, если имя команды предваряется двумя двоеточиями (например, ::load). Невозможно переопределить команды :{, :} и :!.

:delete * | ⟨num⟩ ...

Удаляет одну или несколько точек останова по номеру (используйте :show breaks для просмотра номера каждой точки останова). Форма * удаляет все точки останова.

:disable * | ⟨num⟩ ...

Отключает одну или несколько точек останова по номеру (используйте :show breaks для просмотра номера и состояния каждой точки останова). Форма * отключает все точки останова.

:doc ⟨name⟩

(Экспериментально: эта команда, вероятно, претерпит существенные изменения в GHC 8.8.)

Отображает документацию для данного имени. В настоящее время команда ограничена отображением документации непосредственно в объявлении, игнорируя документацию для аргументов, конструкторов и т. д.

:edit ⟨file⟩

Открывает редактор для изменения файла ⟨file⟩ или самого последнего загруженного модуля, если ⟨file⟩ опущено. Если при последней загрузке произошли ошибки, курсор будет помещён на строку первой ошибки. Вызываемый редактор определяется из переменных окружения VISUAL или EDITOR, или по умолчанию на вашей системе, если ни один из них не задан. Вы можете изменить редактор с помощью :set editor.

VISUAL
Скрытый:
:enable * | ⟨num⟩ ...

Включает один или несколько отключенных точек останова по номеру (используйте :show breaks, чтобы увидеть номер и состояние каждой точки останова). Формат * включает все отключенные точки останова. Включение точки останова сбросит её ignore count до 0. (См. :ignore)

:force ⟨identifier⟩ ...

Выводит значение ⟨identifier⟩ так же, как и :print. В отличие от :print, :force вычисляет каждый тхунк, который встречается при обходе значения. Это может привести к исключениям или бесконечным циклам, или дополнительным точкам останова (которые игнорируются, но отображаются).

:forward ⟨n⟩

Переместиться вперёд на ⟨n⟩ шагов в истории. ⟨n⟩ равен 1, если опущено. См. Отслеживание и история для получения дополнительной информации о средствах отладки GHCi. См. также: :trace, :history, :back.

:help
:?

Отображает список доступных команд.

:

Повторить предыдущую команду.

:history [num]

Отобразить историю шагов вычисления. С числом, отобразить заданное количество шагов (по умолчанию: 20). Для использования с :trace; см. Отслеживание и история. Для изменения количества записей в истории, хранимых GHCi, используйте флаг -fghci-hist-size=⟨n⟩.

:info[!] ⟨name⟩

Отображает информацию о данном имени(ях). Например, если ⟨name⟩ – это класс, то будут напечатаны методы класса и их типы; если ⟨name⟩ – это конструктор типа, то будет напечатано его определение; если ⟨name⟩ – это функция, то будет напечатан её тип. Если ⟨name⟩ загружено из файла исходного кода, GHCi также отобразит расположение его определения в исходном коде.

Для типов и классов GHCi также подытоживает экземпляры, которые их упоминают. Чтобы избежать отображения неважной информации, экземпляр показывается только в том случае, если (a) его заголовок упоминает ⟨name⟩, и (b) все остальные упомянутые в экземпляре вещи находятся в области видимости (либо квалифицированные, либо иным образом) в результате команд :load или :module.

Команда :info! работает аналогичным образом, но она снимает ограничение (b), показывая все экземпляры, которые находятся в области видимости и упоминают ⟨name⟩ в своём заголовке.

:instances ⟨type⟩

Отображает все экземпляры классов, доступные для аргумента ⟨type⟩. Команда сопоставит ⟨type⟩ с первым параметром каждого экземпляра, а затем проверит, что все ограничения удовлетворяются.

В сочетании с PartialTypeSignatures, пользователь может вставлять подстановки в запрос и узнать ограничения, необходимые для каждой подстановки для соответствия ⟨type⟩ с экземпляром.

Вывод представляет собой список всех соответствующих экземпляров, упрощенных и проинициализированных по мере возможности.

Например:

> :instances Maybe (Maybe Int)
instance Eq (Maybe (Maybe Int)) -- Defined in ‘GHC.Maybe’
instance Ord (Maybe (Maybe Int)) -- Defined in ‘GHC.Maybe’
instance Show (Maybe (Maybe Int)) -- Defined in ‘GHC.Show’
instance Read (Maybe (Maybe Int)) -- Defined in ‘GHC.Read’

> :set -XPartialTypeSignatures -fno-warn-partial-type-signatures

> :instances Maybe _
instance Eq _ => Eq (Maybe _) -- Defined in ‘GHC.Maybe’
instance Semigroup _ => Monoid (Maybe _) -- Defined in ‘GHC.Base’
instance Ord _ => Ord (Maybe _) -- Defined in ‘GHC.Maybe’
instance Semigroup _ => Semigroup (Maybe _) -- Defined in ‘GHC.Base’
instance Show _ => Show (Maybe _) -- Defined in ‘GHC.Show’
instance Read _ => Read (Maybe _) -- Defined in ‘GHC.Read’

В результатах будут отображаться только те экземпляры, которые потенциально могут быть использованы. Экземпляры, требующие неудовлетворительных ограничений, такие как TypeError не будут включены. В следующем примере экземпляр A не показан, потому что он не может быть использован.

ghci>:set -XDataKinds -XUndecidableInstances
ghci>import GHC.TypeLits
ghci>class A a
ghci>instance (TypeError (Text "Not possible")) => A Bool
ghci>:instances Bool
instance Eq Bool -- Defined in ‘GHC.Classes’
instance Ord Bool -- Defined in ‘GHC.Classes’
instance Enum Bool -- Defined in ‘GHC.Enum’
instance Show Bool -- Defined in ‘GHC.Show’
instance Read Bool -- Defined in ‘GHC.Read’
instance Bounded Bool -- Defined in ‘GHC.Enum’
:issafe [⟨module⟩]

Отображает информацию Safe Haskell о заданном модуле (или текущем модуле, если опущено). Это включает тип доверия модуля и содержащего его пакета.

:ignore ⟨break⟩ ⟨ignoreCount⟩

Устанавливает счётчик игнорирования точки останова с номером ⟨break⟩ на ⟨ignoreCount⟩.

Следующие ⟨ignoreCount⟩ раз программа достигает точки останова ⟨break⟩, эта точка останова игнорируется, и программа не останавливается. Каждый раз, когда точка останова игнорируется, счётчик ignore count уменьшается на 1. Когда счётчик ignore count равен нулю, программа снова останавливается на точке останова.

Вы также можете указать ⟨ignoreCount⟩ в команде :continue, когда возобновляете выполнение вашей программы.

:kind[!] ⟨type⟩

Определяет и отображает вид ⟨type⟩. Последний может быть произвольным выражением типа, включая частичное применение конструктора типа, такого как Either Int. На самом деле, :kind даже позволяет вам написать частичное применение синонима типа (обычно запрещённого), так что это работает:

ghci> type T a b = (a,b,a)
ghci> :k T Int Bool
T Int Bool :: *
ghci> :k T
T :: * -> * -> *
ghci> :k T Int
T Int :: * -> *

Свободные переменные типа также неявно квантуются, так же, как если бы вы написали :k forall a. [a] так что это тоже работает:

ghci> :k [a]
[a] :: *

Если вы укажете необязательное «!», GHC дополнительно нормализует тип, раскрывая синонимы типа и вычисляя применения типа-функций, и отобразит нормализованный результат.

:list ⟨identifier⟩

Отображает исходный код вокруг определения ⟨identifier⟩ или текущей точки останова, если она не задана. Это требует, чтобы идентификатор был определён в интерпретируемом модуле. Если устройство вывода поддерживает это, GHCi выделит активное подвыражение жирным шрифтом.

:list [⟨module⟩] ⟨line⟩

Отображает исходный код вокруг указанной строки номера ⟨module⟩. Это требует интерпретации модуля. Если устройство вывода поддерживает это, GHCi выделит активное подвыражение жирным шрифтом.

:load[!] [*]⟨module⟩

Рекурсивно загружает указанные модули ⟨module⟩ и все зависящие от них модули. Здесь каждый ⟨module⟩ должен быть именем модуля или именем файла, но не может быть именем модуля в пакете.

Все ранее загруженные модули, кроме модулей пакетов, забываются. Новый набор модулей известен как целевой набор. Обратите внимание, что :load может быть использован без каких-либо аргументов, чтобы разгрузить все текущие загруженные модули и привязки.

Обычно предварительно скомпилированный код для модуля загружается, если он доступен, в противном случае модуль компилируется в байт-код. Использование префикса * заставляет модуль загружаться как байт-код.

Добавление необязательного «!» преобразует ошибки типа в предупреждения при загрузке. Это позволяет использовать части модуля, которые верны, даже если в некоторых определениях есть ошибки типа. Эффективно, флаг -fdefer-type-errors устанавливается перед загрузкой и сбрасывается после загрузки, если флаг не был установлен ранее. См. Откладывание ошибок типа до выполнения для дополнительной мотивации и подробностей.

После команды :load текущий контекст устанавливается на:

  • ⟨module⟩, если он был загружен успешно, или
  • последний успешно загруженный модуль, если в результате текущей команды :load были загружены другие модули, или
  • Prelude в противном случае.
:loc-at ⟨module⟩ ⟨line⟩ ⟨col⟩ ⟨end-line⟩ ⟨end-col⟩ [⟨name⟩]

Попытка найти место определения имени в заданном фрагменте исходного кода, например:

X> :loc-at X.hs 6 14 6 16 mu
X.hs:(8,7)-(8,9)

Эта команда полезна при интеграции GHCi с текстовыми редакторами и IDE для предоставления функции перехода к определению.

Команда :loc-at требует, чтобы была установлена :set +c.

:main ⟨arg1⟩ ... ⟨argn⟩

При компиляции и выполнении программы она может использовать действие ввода-вывода getArgs для доступа к аргументам командной строки. Однако мы не можем просто передать аргументы в main во время тестирования в ghci, так как main не принимает свои аргументы напрямую.

Вместо этого мы можем использовать команду :main. Она выполняет то, что находится в области видимости main, а все аргументы обрабатываются так же, как и аргументы командной строки, например:

ghci> main = System.Environment.getArgs >>= print
ghci> :main foo bar
["foo","bar"]

Мы также можем заключать аргументы, содержащие символы, такие как пробелы, в кавычки, и они будут обрабатываться как строки Haskell, или мы можем просто использовать синтаксис списков Haskell:

ghci> :main foo "bar baz"
["foo","bar baz"]
ghci> :main ["foo", "bar baz"]
["foo","bar baz"]

Наконец, можно вызвать другие действия ввода-вывода, используя флаг -main-is или команду :run:

ghci> foo = putStrLn "foo" >> System.Environment.getArgs >>= print
ghci> bar = putStrLn "bar" >> System.Environment.getArgs >>= print
ghci> :set -main-is foo
ghci> :main foo "bar baz"
foo
["foo","bar baz"]
ghci> :run bar ["foo", "bar baz"]
bar
["foo","bar baz"]
:module +|- [*]⟨mod1⟩ ...
import ⟨mod⟩

Устанавливает или изменяет текущий контекст для инструкций, набранных в подсказке. Форма import mod эквивалентна :module +mod. Подробнее см. в Что фактически находится в области видимости в подсказке?.

:print ⟨names⟩

Выводит значение без принудительной оценки. :print может использоваться для значений, типы которых неизвестны или частично известны, что может быть в случае локальных переменных с полиморфными типами на точке останова. При проверке значения во время выполнения :print пытается восстановить тип значения и, если возможно, расширит тип в среде GHCi. Если обнаружены невычисленные компоненты (ленивые вычисления), :print связывает каждую ленивую вычисление с новой переменной, имя которой начинается с _t. Дополнительную информацию см. в разделе Точки останова и проверка переменных. См. также команду :sprint, которая работает как :print, но не связывает новые переменные.

:quit

Выходит из GHCi. Вы также можете выйти, набрав Control-D в подсказке.

:reload[!]

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

Добавление необязательного “!” превращает ошибки типов в предупреждения во время загрузки. Это позволяет использовать части модуля, которые верны, даже если в некоторых определениях есть ошибки типов. По существу, флаг -fdefer-type-errors устанавливается перед загрузкой и сбрасывается после загрузки, если флаг еще не был установлен ранее. Дополнительную информацию и мотивацию см. в разделе Откладывание ошибок типов до времени выполнения.

:run

См. :main.

:script [⟨n⟩] ⟨filename⟩

Выполняет строки файла в виде серии команд GHCi. Синтаксис аргументов имени файла учитывает правила цитирования оболочки, т.е. имена файлов, содержащие пробелы, могут быть заключены в двойные кавычки или пробелы могут быть экранированы обратной косой чертой. Эта команда совместима с многострочными операторами, заданными :set +m

:set [⟨option⟩ ...]

Устанавливает различные параметры. Список доступных параметров см. в Команды :set и :seti, а список специфичных для GHCi флагов см. в Параметры режима интерактивного режима. Команда :set сама по себе показывает, какие параметры в данный момент установлены. Она также отображает текущие динамические настройки флагов, с отдельным отображением флагов, специфичных для GHCi.

:set args ⟨arg⟩

Устанавливает список аргументов, которые возвращаются, когда программа вызывает System.Environment.getArgs.

:set editor ⟨cmd⟩

Устанавливает команду, используемую командой :edit, на ⟨cmd⟩.

:set local-config ⟨source|ignore⟩

Если ignore, файлы ./.ghci будут игнорироваться (источник небезопасных локальных скриптов представляет собой угрозу безопасности). По умолчанию source. Установите эту директиву в вашем пользовательском скрипте .ghci, т.е. перед выполнением локального скрипта.

Даже если установлено ignore, локальный скрипт по-прежнему будет обработан, если он указан с помощью -ghci-script в командной строке или импортирован через :script.

:set prog ⟨prog⟩

Устанавливает строку, которая возвращается, когда программа вызывает System.Environment.getProgName.

:set prompt ⟨prompt⟩

Устанавливает строку, используемую в качестве подсказки в GHCi. Внутри ⟨prompt⟩ следующие последовательности заменяются:

  • %s именами модулей, которые в данный момент находятся в области видимости.
  • %l номером строки (как указано в сообщениях компилятора) текущей подсказки.
  • %d датой в формате «День Недели Месяц Число» (например, «Вторник Май 26»).
  • %t текущим временем в формате 24-часовых часов HH:MM:СС.
  • %T текущим временем в формате 12-часовых часов HH:MM:СС.
  • %@ текущим временем в формате 12-часовых часов HH:MM с обозначением AM/PM.
  • %A текущим временем в формате 24-часовых часов HH:MM.
  • %u именем пользователя текущего пользователя.
  • %w текущей рабочей директорией.
  • %o операционной системой.
  • %a архитектурой компьютера.
  • %N именем компилятора.
  • %V версией компилятора.
  • %call(cmd [args]) результатом вызова cmd args.
  • %% значением %.

Если ⟨prompt⟩ начинается с ", она интерпретируется как строка Haskell; в противном случае она обрабатывается как буквальная строка.

:set prompt-cont ⟨prompt⟩

Устанавливает строку, используемую в качестве подсказки продолжения (используется при использовании команды :{) в GHCi.

:set prompt-function ⟨prompt-function⟩

Устанавливает функцию, используемую для отображения подсказки в GHCi. Функция должна иметь тип [String] -> Int -> IO String. Эта функция вызывается каждый раз при создании подсказки. Первый аргумент представляет имена модулей, находящихся в области видимости (имя «самого верхнего» модуля начнется с *; см. Что фактически находится в области видимости в подсказке? для получения дополнительной информации). Второй аргумент – номер строки (как указано в сообщениях компилятора) текущей подсказки.

:set prompt-cont-function ⟨prompt-function⟩

Устанавливает функцию, используемую для отображения подсказки продолжения (используется при использовании команды :{) в GHCi.

:set stop ⟨num⟩ ⟨cmd⟩

Устанавливает команду, которая будет выполняться при достижении точки останова или при выборе нового элемента в истории. Наиболее распространённое использование :set stop — отображение исходного кода в текущей позиции, например :set stop :list.

Если перед командой указано число, то команды выполняются только при достижении указанной точки останова (только). Это может быть очень полезно: например, :set stop 1 :continue фактически отключает точку останова 1, выполняя :continue при её достижении. В этом случае GHCi всё равно выведет сообщение о достижении точки останова. Если вам не нужно такое сообщение, вы можете использовать команду :disable. Более того, с помощью хитрого использования :def и :cmd вы можете использовать :set stop для реализации условных точек останова:

*ghci> :def cond \expr -> return (":cmd if (" ++ expr ++ ") then return \"\" else return \":continue\"")
*ghci> :set stop 0 :cond (x < 3)

Чтобы пропустить точки останова для определённого числа итераций, используйте команду :ignore или параметр ⟨ignoreCount⟩ команды :continue.

:seti [⟨option⟩ ...]

Подобно :set, но настройки, заданные с помощью :seti, влияют только на выражения и команды, введённые в командной строке, а не на модули, загруженные с помощью :load (в отличие от настроек, заданных с помощью :set, которые применяются везде). См. Установка настроек только для интерактивной оценки.

Без аргументов отображает текущий набор настроек, применяемых к выражениям и командам, введённым в командной строке.

:show bindings

Отобразить привязки, созданные в командной строке, и их типы.

:show breaks

Список активных точек останова.

:show context

Список активных вычислений, приостановленных в точках останова.

:show imports

Отобразить импорты, которые в настоящее время действуют, созданные командами import и :module.

:show modules

Отобразить список загруженных в настоящее время модулей.

:show packages

Отобразить текущие активные флаги пакетов, а также список загруженных в настоящее время пакетов.

:show paths

Отобразить текущую рабочую директорию (установленную с помощью команды :cd), а также список директорий, в которых ищутся исходные файлы (установленные параметром -i).

:show language

Отобразить текущие активные языковые флаги для исходных файлов.

:showi language

Отобразить текущие активные языковые флаги для выражений, набранных в командной строке (см. также :seti).

:show targets

Отобразить список загруженных в настоящее время модулей. Этот набор загруженных модулей можно изменить с помощью команд :load, :reload, :add и :unadd.

:show [args|prog|prompt|editor|stop]

Отображает указанную настройку (см. :set).

:sprint ⟨expr⟩

Печатает значение без принудительной оценки. :sprint похож на :print, с той разницей, что невычисленные подвыражения не привязываются к новым переменным, а просто обозначаются как _.

:step [⟨expr⟩]

Включает все точки останова и начинает оценивать выражение в режиме пошаговой отладки. В этом режиме вычисление будет останавливаться после каждой редукции, что позволит просматривать локальные переменные. Если ⟨expr⟩ не указано, вычисление возобновится в последней точке останова. См. Пошаговая отладка.

:steplocal

Включает только точки останова в текущей привязке верхнего уровня и возобновляет вычисление в последней точке останова. Продолжение с помощью :steplocal невозможно, если последняя точка останова была достигнута ошибкой (-fbreak-on-error) или исключением (-fbreak-on-exception).

:stepmodule

Включает только точки останова в текущем модуле и возобновляет вычисление в последней точке останова.

:trace ⟨expr⟩

Вычисляет заданное выражение (или с последней точки останова, если выражение не задано), а также регистрирует шаги вычисления для последующего просмотра с помощью :history. См. Отслеживание и история.

:type ⟨expression⟩

Вычисляет и выводит тип ⟨выражения⟩, решая ограничения и сводя типы семейств настолько, насколько это возможно. Для полиморфных типов он не подставляет никакие квантифицированные переменные forall.

*X> :type length
length :: Foldable t => t a -> Int

Сведение типа семейства пропускается, если функция не полностью инстанцирована, так как это было отмечено как дающее более интуитивные результаты. Вы можете использовать :info, если вы не применяете никаких аргументов, так как это вернёт исходный тип функции.

:type +d ⟨expression⟩

Вычисляет и выводит тип ⟨выражения⟩, подставляя все квантифицированные переменные forall, решая ограничения, используя значения по умолчанию и обобщая. В этом режиме, если вычисленный тип ограничен каким-либо интерактивным классом (Num, Show, Eq, Ord, Foldable, или Traversable), ограниченная переменная типа(ы) принимает значения по умолчанию в соответствии с правилами, описанными в ExtendedDefaultRules. Этот режим очень полезен, когда вычисленный тип достаточно общий (например, для foldr) и может быть полезно увидеть более конкретную инстанциацию.

*X> :type +d length
length :: [a] -> Int
:type-at ⟨path⟩ ⟨line⟩ ⟨col⟩ ⟨end-line⟩ ⟨end-col⟩ [⟨name⟩]

Сообщает вычисленный тип в заданном интервале/позиции в модуле, например:

*X> :type-at X.hs 6 6 6 7 f
Int -> Int

Эта команда полезна при интеграции GHCi с текстовыми редакторами и IDE для предоставления функции отображения типа в точке.

Первый параметр (путь) должен быть путём к файлу, а не именем модуля. Тип этого пути зависит от того, как модуль был загружен в GHCi: если модуль был загружен по имени, то должен использоваться путь, рассчитанный GHCi, как описано в Модули против имён файлов. Если модуль был загружен с абсолютным или относительным путём, то должен быть указан тот же путь.

Последний строковый параметр полезен, когда интервал устарел, т.е. файл изменился, и код переместился. В этом случае :type-at обращается к общей команде :type для поиска.

Для команды :type-at необходимо установить :set +c.

:unadd ⟨module⟩

Удаляет ⟨модуль⟩(и) из текущего набора целей и выполняет перезагрузку (см. :add выше).

:undef ⟨name⟩

Удаляет пользовательскую команду ⟨имя⟩ (см. :def выше).

:unset ⟨option⟩

Снимает определённые настройки. Список доступных настроек см. в Команды :set и :seti.

:uses ⟨module⟩ ⟨line⟩ ⟨col⟩ ⟨end-line⟩ ⟨end-col⟩ [⟨name⟩]

Отчет обо всех локальных использованиях вещи в заданной позиции в модуле, например:

:uses GhciFind.hs 53 66 53 70 name
GhciFind.hs:(46,25)-(46,29)
GhciFind.hs:(47,37)-(47,41)
GhciFind.hs:(53,66)-(53,70)
GhciFind.hs:(57,62)-(57,66)

Эта команда полезна для выделения и навигации по всем использованиям идентификатора в редакторах и IDE.

Команда :uses требует, чтобы была установлена опция :set +c.

:: ⟨builtin-command⟩

Выполняет встроенную команду GHCi (например, ::type 3). То есть, ищет в списке встроенных команд, исключая определенные макросы. См. также: :def.

:! ⟨command⟩

Выполняет командную строку оболочки ⟨command⟩.

3.8. Команды :set и :seti

Команда :set устанавливает два типа опций: опции GHCi, которые начинаются с «+», и опции командной строки, которые начинаются с «-».

Примечание

В настоящее время команда :set не поддерживает какой-либо вид кавычек в своих аргументах: кавычки не будут удалены и не могут использоваться для группирования слов вместе. Например, :set -DFOO='BAR BAZ' не сделает того, что вы ожидаете.

3.8.1. Опции GHCi

Опции GHCi можно установить с помощью :set и сбросить с помощью :unset.

Доступные опции GHCi:

:set +c

Сбор информации о типе и местоположении после загрузки модулей. Команды :all-types, :loc-at, :type-at и :uses требуют, чтобы была активна опция +c.

:set +m

Включить разбор многострочных команд. Многострочная команда запрашивается, когда текущая строка ввода содержит открытые контексты выравнивания (см. Многострочный ввод).

:set +r

Обычно любая оценка выражений верхнего уровня (иначе называемых CAFs или Constant Applicative Forms) в загруженных модулях сохраняется между оценками. Включение +r приводит к тому, что все оценки выражений верхнего уровня отбрасываются после каждой оценки (они все еще сохраняются *во время* одной оценки).

Эта опция может помочь, если оцениваемые выражения верхнего уровня потребляют большое количество места или если вам нужны повторяемые измерения производительности.

:set +s

Отображение некоторых статистических данных после оценки каждого выражения, включая затраченное время и количество выделенных байт. ПРИМЕЧАНИЕ: значение выделения точно лишь до размера области выделения менеджера памяти, потому что оно вычисляется на каждом GC. Поэтому вы можете увидеть нулевые значения, если GC не происходил.

:set +t

Отображение типа каждой связанной переменной после ввода оператора в подсказку. Если оператор представляет собой единственное выражение, тогда единственной связью переменной будет переменная it.

3.8.2. Установка опций командной строки GHC в GHCi

Обычные опции командной строки GHC также можно установить с помощью :set. Например, чтобы включить -Wmissing-signatures, нужно ввести:

ghci> :set -Wmissing-signatures

GHCi также примет любые прагмы заголовков файлов, которые он найдет, такие как {-# OPTIONS_GHC ... #-} и {-# LANGUAGE ... #-} (см. Прагмы). Например, вместо использования :set для включения -Wmissing-signatures, можно написать:

ghci> {-# OPTIONS_GHC -Wmissing-signatures #-}

Любая опция командной строки GHC, обозначенная как динамическая (см. таблицу в Справочник по флагам), может быть установлена с помощью :set. Чтобы сбросить опцию, можно установить обратную опцию:

ghci> :set -Wno-incomplete-patterns -XNoMultiParamTypeClasses

Справочник по флагам перечисляет обратную опцию для каждой опции, где это применимо.

Некоторые статические опции (-package ⟨pkg⟩, -I⟨dir⟩, -i⟨dir⟩[:⟨dir⟩]* и -l ⟨lib⟩ в частности) также будут работать, но некоторые могут не вступить в силу до следующей перезагрузки.

3.8.3. Установка опций только для интерактивной оценки

GHCi фактически поддерживает два набора опций:

  • Опции загрузки применяются при загрузке модулей
  • Опции интерактивного режима применяются при оценке выражений и команд, введенных в подсказке GHCi.

Команда :set изменяет оба, но также есть команда :seti (для «установки интерактивных»), которая влияет только на установленные интерактивные опции.

Часто бывает полезно изменять интерактивные опции без применения этой опции к загруженным модулям. Например

:seti -XMonoLocalBinds

Было бы нежелательно, если бы MonoLocalBinds применялась к загруженным модулям: это может привести к ошибке компиляции, но чаще всего — к дополнительной перекомпиляции, поскольку GHC будет думать, что ему нужно перекомпилировать модуль, потому что изменились флаги.

Если вы устанавливаете опции языка в файле .ghci , рекомендуется использовать :seti, а не :set, если вы действительно не хотите, чтобы они применялись ко всем модулям, которые вы загружаете в GHCi.

Два набора опций можно проверить с помощью команд :set и :seti соответственно без аргументов. Например, в чистой сессии GHCi мы можем увидеть что-то вроде этого:

ghci> :seti
base language is: GHC2021
with the following modifiers:
  -XExtendedDefaultRules
  -XNoMonomorphismRestriction
GHCi-specific dynamic flag settings:
other dynamic, non-language, flag settings:
  -fexternal-dynamic-refs
  -fignore-optim-changes
  -fignore-hpc-changes
  -fimplicit-import-qualified
warning settings:

Два набора опций инициализируются следующим образом. Сначала оба набора опций инициализируются, как описано в Файлы .ghci и .haskeline. Затем интерактивные опции изменяются следующим образом:

  • Опция -XExtendedDefaultRules включена, чтобы применить специальные правила по умолчанию к выражениям, набранным в подсказке (см. Правила по умолчанию для типов в GHCi).
  • Ограничение мономорфизма отключено (см. Отключение ограничения мономорфизма).

3.9. Файлы .ghci и .haskeline

3.9.1. Файлы .ghci

При запуске, если не задан флаг -ignore-dot-ghci, GHCi считывает и выполняет команды из следующих файлов в указанном порядке, если они существуют:

  1. ghcappdata/ghci.conf, где ⟨ghcappdata⟩ зависит от вашей системы, но обычно это что-то вроде $HOME/.ghc или $XDG_CONFIG_HOME/ghc в Unix или C:\Users{username}\AppData\Roaming\ghc в Windows.
  2. ./.ghci

Файл ghci.conf наиболее полезен для включения предпочитаемых опций (например, :set +s) и определения полезных макросов.

Примечание

При настройке языковых опций в этом файле обычно желательно использовать :seti, а не :set (см. Настройка опций для интерактивного вычисления только).

Размещение файла .ghci в каталоге с проектом Haskell — полезный способ настроить определенные параметры всего проекта, чтобы не вводить их каждый раз при запуске GHCi: например, если ваш проект использует многопараметрические типы классов, переменные типов в области видимости и CPP, и содержит исходные файлы в трех подкаталогах A, B и C, вы можете поместить следующие строки в .ghci:

:set -XMultiParamTypeClasses -XScopedTypeVariables -cpp
:set -iA:B:C

(Обратите внимание, что строго говоря, флаг -i — статический, но фактически он работает для его установки с помощью :set таким образом. Изменения не вступят в силу до следующего :load.)

Предупреждение

Использование недоверенных файлов ./.ghci представляет собой угрозу безопасности. Они могут содержать произвольные команды, которые будут выполнены от имени пользователя. Используйте :set local-config для предотвращения обработки файлов ./.ghci.

После создания библиотеки макросов GHCi, вы можете захотеть загружать их из отдельных файлов или загрузить свой файл .ghci в сессию GHCi во время отладки.

:def source readFile

С этим определенным макросом в вашем файле .ghci вы можете использовать :source file для чтения команд GHCi из file. Вы можете найти (и внести свой вклад!) другие предложения для файлов .ghci на этой странице вики Haskell: GHC/GHCi

Кроме того, все файлы, указанные с флагами -ghci-script, будут считаны после стандартных файлов, что позволит использовать пользовательские файлы .ghci.

Два параметра командной строки управляют чтением файлов начальной загрузки:

-ignore-dot-ghci

Не читать ни файл ./.ghci, ни другие файлы начальной загрузки при запуске.

-ghci-script

Прочитать определенный файл после обычных файлов начальной загрузки. Может быть указано многократно для нескольких входных данных. -ignore-dot-ghci не относится к этим файлам.

При определении макросов GHCi следует учитывать важное поведение, когда имена могут конфликтовать с встроенными командами, особенно в отношении автодополнения.

Например, предположим, что у вас есть макрос с именем :time и вы ввели в оболочке :t 3 — что должно произойти? Текущий алгоритм автодополнения команд:

  1. Сначала ищем точное совпадение имени среди определенных макросов.
  2. Ищем точное совпадение имени в списке встроенных команд.
  3. Проводим поиск по префиксу в списке встроенных команд — если совпадает встроенная команда, но макрос определен с тем же именем, что и встроенная команда, выбираем макрос.
  4. Проводим поиск по префиксу в списке встроенных команд.
  5. Проводим поиск по префиксу в списке определенных макросов.

Вот несколько примеров:

  1. У вас есть макрос :time и вы вводите :t 3

    Вы получите :type 3

  2. У вас есть макрос :type и вы вводите :t 3

    Вы получите :type 3 с вашим определенным макросом, а не встроенным.

  3. У вас есть макрос :time и макрос :type, и вы вводите :t 3

    Вы получите :type 3 с вашим определенным макросом.

Для приоритета встроенных команд можно использовать :: ⟨builtin-command⟩, например, ::type 3.

3.9.2. Файл .haskeline

GHCi использует библиотеку Haskeline внутри. Вы можете настроить ее, в том числе, для удаления дубликатов из истории GHCi. См.: Предпочтения пользователей Haskeline.

3.10. Компиляция в объектный код внутри GHCi

По умолчанию GHCi компилирует код Haskell в байт-код, который интерпретируется системой выполнения. GHCi также может компилировать Haskell-код в объектный код: для включения этой функции используйте флаг -fobject-code в командной строке или с помощью :set (опция -fbyte-code восстанавливает компиляцию в байт-код). Компиляция в объектный код занимает больше времени, но обычно код будет выполняться в 10–20 раз быстрее, чем байт-код.

Компиляция в объектный код внутри GHCi особенно полезна при разработке приложений на основе компиляции, потому что команда :reload обычно работает намного быстрее, чем перезапуск GHC с --make из командной строки, поскольку все интерфейсные файлы уже кэшированы в памяти.

Существуют и недостатки компиляции в объектный код: например, вы не можете устанавливать точки останова в модулях объектного кода. Только экспорт модуля объектного кода будет виден в GHCi, а не все глобальные определения, как в интерпретируемых модулях.

3.11. Запуск интерпретатора в отдельном процессе

Обычно GHCi выполняет интерпретируемый код в том же процессе, что и сам GHC, на том же RTS и с общей кучей. Однако, если задан флаг -fexternal-interpreter, GHC запустит отдельный процесс для выполнения интерпретируемого кода и будет взаимодействовать с ним с помощью сообщений через канал.

-fexternal-interpreter
Since:

8.0.1

Выполнять интерпретируемый код (для GHCi, Template Haskell, Quasi-quoting или Annotations) в отдельном процессе. Интерпретатор будет работать в режиме профилирования, если -prof активен, и в режиме динамической компоновки, если активен -dynamic.

Есть несколько нюансов, которые, надеюсь, будут устранены в будущем: этот вариант в настоящее время не реализован в Windows (это бесполезно), и внешний интерпретатор не поддерживает отладчик GHCi, поэтому точки останова и пошаговый режим не работают с -fexternal-interpreter.

См. также флаги -pgmi ⟨cmd⟩ (Замена программы для одной или нескольких фаз) и -opti ⟨option⟩ (Принудительное применение параметров к определённой фазе).

Почему это может быть полезно? Основная причина заключается в том, что RTS, выполняющий интерпретируемый код, может быть другого типа (профилирование или динамическая компоновка) по сравнению с самим GHC. Например:

  • Мы можем использовать профайлер для сбора стековых следов при использовании GHCi (см. Стековые следы в GHCi).
  • При компиляции кода Template Haskell с -prof нам не нужно сначала компилировать модули без -prof (см. Использование Template Haskell с профилированием), так как мы можем выполнить профилируемый объектный код в интерпретаторе.

Эта функция экспериментальна в GHC 8.0.x, но в будущих выпусках она может стать стандартной.

3.11.1. Создание внешнего интерпретатора

Исходный код программы внешнего интерпретатора находится в utils/iserv. Он очень прост, так как большая часть сложной логики взята из библиотеки ghci.

Иногда желательно настроить программу внешнего интерпретатора. Например, можно добавить символы в компоновщик RTS, используемый внешним интерпретатором. Это делается просто во время компоновки путём компоновки дополнительной .o, которая определяет функцию rtsExtraSyms возвращающую дополнительные символы. Такой подход позволяет избежать необходимости перекомпиляции RTS с добавлением символов в его встроенный список. Типичный файл C будет выглядеть так:

#include <RtsSymbols.h>

#define CODE_SYM(vvv) { MAYBE_LEADING_UNDERSCORE_STR(#vvv), \
                      (void*)(&(vvv)), STRENGTH_NORMAL, SYM_TYPE_CODE },
#define DATA_SYM(vvv) { MAYBE_LEADING_UNDERSCORE_STR(#vvv), \
                      (void*)(&(vvv)), STRENGTH_NORMAL, SYM_TYPE_DATA },

RtsSymbolVal my_iserv_syms[] = {
    CODE_SYM(malloc)
    CODE_SYM(getauxval)
    CODE_SYM(posix_spawn_file_actions_init)
    ...
    { 0, 0, STRENGTH_NORMAL, SYM_TYPE_CODE } /* sentinel */
};

RtsSymbolVal* rtsExtraSyms() {
    return my_iserv_syms;
}

Для получения дополнительной информации, прочитайте примечание [Extra RTS symbols] в RTS.

3.11.2. Запуск интерпретатора на другом хосте

При использовании флага -fexternal-interpreter GHC запустит и будет взаимодействовать с отдельным процессом, используя каналы. В некоторых сценариях (например, при кросс-компиляции) предпочтительнее, чтобы коммуникация происходила через сеть. GHC предоставляет две утилиты для этого, которые можно найти в каталоге utils.

  • remote-iserv необходимо скомпилировать с помощью кросс-компилятора, чтобы он выполнялся на удалённом хосте. Или, если использовать его на том же хосте, этап компиляции stage2 также подойдёт.
  • iserv-proxy необходимо скомпилировать на машине сборки с помощью компилятора сборки.

После запуска remote-iserv ⟨tmp_dir⟩ ⟨port⟩ на целевом хосте и предоставления ему временной папки (куда он скопирует необходимые библиотеки для загрузки) и порта, он будет ожидать подключения прокси.

Указание -pgmi ⟨/path/to/iserv-proxy⟩ и -opti ⟨slave-ip⟩ -opti ⟨slave-port⟩ [-opti -v] в дополнение к -fexternal-interpreter позволит ghc использовать прокси.

Существуют некоторые ограничения при использовании этого метода. Ввод-вывод файлов и процессов будет выполняться на целевом хосте. В связи с этим, пакеты вроде git-embed, file-embed и другие могут вести себя непредсказуемо, если целевой и хост-компьютеры не используют одну и ту же файловую систему.

3.12. Компоновка библиотек GHCi

При вызове статическим способом GHCi будет использовать статический компоновщик времени выполнения GHC RTS для загрузки объектных файлов импортированных модулей, когда они доступны. Однако, когда эти модули скомпилированы с флагом -split-sections, такая компоновка может быть довольно дорогой. Для уменьшения этой стоимости, менеджеры пакетов и системы сборки могут создать предварительно скомпонованный объектный файл GHCi, используя режим --merge-objs. Это объединяет объектные файлы каждого модуля в один объект, сворачивая секции функций в одну текстовую секцию, что позволяет эффективно загрузить её компоновщику времени выполнения.

3.13. ЧАВО и моменты, на которые стоит обратить внимание

Интерпретатор не может загрузить модули с объявлениями внешних экспортов!

К сожалению, нет. Мы ещё не реализовали это. Пожалуйста, вручную скомпилируйте все проблемные модули перед их загрузкой в GHCi.

-O неэффективна в GHCi!

До GHC 9.8 оптимизации считались слишком нестабильными для использования с интерпретатором байткода. Это ограничение было снято, но всё ещё рассматривается как экспериментальное и защищено флагом -funoptimized-core-for-interpreter, который включён по умолчанию. Для использования оптимизаций выполните:

ghci -fno-unoptimized-core-for-interpreter -O
Потоки не продолжают работу, когда GHCi ждёт ввода.

Это должно работать, если ваш GHCi был скомпилирован с флагом -threaded, который является стандартным. Обратитесь к поставщику вашей установки GHCi.

After using getContents, I can’t use stdin, until I do :load or :reload

Это определённое поведение getContents: он устанавливает обработчик stdin в состояние, известное как полузакрытое, в котором любые дальнейшие операции ввода-вывода на нём запрещены. Поскольку состояние ввода-вывода сохраняется между вычислениями, состояние полузакрытия сохраняется до следующей команды :load или :reload.

Вы можете заставить stdin сбросить себя после каждого вычисления, дав GHCi команду :set +r. Это работает, потому что stdin - это просто выражение верхнего уровня, которое можно вернуть в его неоценённое состояние так же, как и любое другое выражение верхнего уровня (CAF).

Я не могу прервать вычисления в GHCi на Windows с помощью Control-C.

См. Запуск GHCi в Windows.

Режим буферизации по умолчанию отличается в GHCi от GHC.

В GHC обработчик stdout по умолчанию является построчно-буферизованным. Однако в GHCi мы отключаем буферизацию на stdout, поскольку это обычно желаемое поведение в интерпретаторе: вывод появляется по мере его генерации.

Если вы хотите построчно-буферизованное поведение, как в GHC, вы можете запустить свою программу следующим образом:

main = do { hSetBuffering stdout LineBuffering; ... }
[5]

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

[6]

Изначально мы предоставляли связи для всех переменных в области видимости, а не только для свободных переменных выражения, но обнаружили, что это значительно сказывается на производительности, поэтому сейчас ограничение только на свободные переменные.

© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/9.12.1/docs/users_guide/ghci.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API