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>

Загрузка стандартного модуля Prelude и стандартных библиотек может занять некоторое время, после чего появится приглашение. Как сказано в приветственном сообщении, можно ввести :?, чтобы увидеть список доступных команд и краткое описание каждой из них. Мы рассмотрим большинство этих команд по ходу изложения; полная документация по всем командам приведена в разделе Команды 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

Обычно после команды :load GHCi показывает только количество загруженных модулей. При использовании этого флага 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 обычно ищет соответствующие скомпилированные объектные файлы и, если это возможно, предпочитает их интерпретации исходного кода. Например, предположим, что у нас есть программа из четырёх модулей: A, B, C и D. Модули B и C импортируют только D, а A импортирует и B, и C:

  A
 / \
B   C
 \ /
  D

Сначала скомпилируем D, а затем загрузим всю программу:

ghci> :! ghc -c -this-unit-id interactive-session -dynamic D.hs
ghci> :load A B C D
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 его необходимо скомпилировать для динамической компоновки.

Также обратите внимание на параметр -this-unit-id ⟨unit-id⟩ interactive-session, переданный GHC: GHCi может использовать объектный код модуля, загруженного с помощью :load, только если объектный код был скомпилирован для interactive-session.

В любой момент можно использовать команду :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 -this-unit-id interactive-session -dynamic C.hs
*ghci> :load A B C D
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 -this-unit-id interactive-session -dynamic D.hs
*ghci> :reload
Ok, modules loaded: A, B, C, D.

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

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

Автоматическая загрузка объектных файлов иногда может вызывать путаницу: определения верхнего уровня, не экспортируемые модулем, доступны для использования в выражениях, вводимых в приглашении, только когда модуль интерпретируется (см. Что на самом деле доступно в приглашении?). Поэтому иногда может понадобиться принудительно загрузить модуль в GHCi с помощью интерпретатора. Для этого при использовании :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 выполнит его как вычисление IO.

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.

Разумеется, вы также можете связывать обычные выражения, не относящиеся к IO, с помощью оператора 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 GHCi отображает как main::Interactive.T, чтобы отличить её от нового 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. Если бы в файле конфигурации .ghci приглашение было задано как %s>, отображалось бы Prelude>. Однако это не используется по умолчанию, поскольку при наличии дополнительных импортов приглашение может занимать много места.

Синтаксис *module в приглашении указывает, что область видимости выражений, введённых в командной строке, включает всю область верхнего уровня модуля ⟨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 объединяет области видимости всех этих модулей, формируя область, действующую в командной строке.

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

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

Поддерживается полный синтаксис импорта Haskell, включая конструкции 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 ведёт себя так, словно для каждого модуля в каждом пакете и каждого модуля, загруженного в GHCi, объявлен неявный импорт import qualified. Это поведение можно отключить флагом -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) можно, только если он либо (a) загружен, либо (b) является модулем пакета, известного 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 (а также в GHC, если указан флаг ExtendedDefaultRules) обычные правила вывода типа по умолчанию смягчены (подробное описание используемого алгоритма см. в разделе Расширения механизма вывода типов классов по умолчанию):

  • Вместо одних только числовых классов (например, Num, RealFloat и т. д.) допускается любой интерактивный класс (определённый ниже).
  • При выборе типа по умолчанию рассматриваются только ограничения вида C v для интерактивного класса C. В частности, снимается требование, согласно которому переменная типа не должна встречаться в нестандартных или неунарных ограничениях; такие ограничения просто не учитываются при выборе типа по умолчанию.
  • Тип единицы () и тип списка [] добавляются в начало стандартного списка типов, проверяемых при выводе типа по умолчанию.

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

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, при выполнении printf в ghci возникнет ошибка.

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

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

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

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

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

Поскольку при включённом ExtendedDefaultRules правила вывода типа по умолчанию смягчены, смягчены также правила для объявлений default. Согласно разделу 4.3.4 отчёта Haskell 2010, объявление default имеет вид 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. Это происходит только в том случае, если содержимое проверяемой переменной вычислено полностью.

Если сохранение степени вычисленности переменной не важно, вместо :print можно использовать :force. Команда :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

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

[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

Здесь ⟨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 считает ширину символа табуляции равной 1 независимо от его положения; иными словами, учитываются символы, а не столбцы. Так поступают некоторые редакторы, другие — нет. Лучше всего вообще не использовать символы табуляции в исходном коде (см. -Wtabs в разделе Предупреждения и проверка корректности).

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

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

Остановившись в точке останова, можно показать текущий стек вычислений с помощью команды :where.

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). Поэтому при вычислении _result, вероятно, потребуется сразу же выполнить команду :continue. Также можно воспользоваться командой :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; причина в том, что запись каждой точки останова в историю снижает производительность в два раза или больше.

-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 знает, что переменная уже вычисляется, поэтому новое вычисление ожидает результата, прежде чем продолжить. Но этого, разумеется, не произойдёт, поскольку первое вычисление остановлено в точке останова. Нажатие Control-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, будет создан .ghci-history.

-fghci-leak-check

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

-fload-initial-targets
По умолчанию:

включено

Начиная с версии:

9.14.1

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

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 не сможет найти библиотеку, он сообщит об ошибке.

GHCi также может загружать из командной строки обычные объектные файлы (.o или .obj — в зависимости от платформы) и статические архивы (.a). Просто добавьте имя объектного файла или библиотеки в командную строку. В Windows GHCi также поддерживает формат big-obj.

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

3.7. Команды GHCi

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

:abandon

Прерывает текущую оценку (доступно только при остановке в точке останова).

:add [*]⟨module⟩

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

⟨module⟩ может быть путём к файлу. Символ «~» в начале ⟨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⟩ не указано, оно равно единице. Подробнее о средствах отладки GHCi см. в разделе Трассировка и история. См. также: :trace, :history, :forward.

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

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

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

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

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

У команды browse есть два варианта:

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

    Форма с * доступна только для интерпретируемых модулей; для скомпилированных модулей (в том числе модулей из пакетов) доступна только форма :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⟩

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

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

:cmd ⟨expr⟩

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

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

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

При вызове :complete выводит варианты автодополнения с ⟨n⟩-го по ⟨m⟩-й для частичного ввода ⟨string-literal⟩ в области автодополнения, заданной параметром ⟨type⟩. В настоящее время поддерживается только область 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 ⟨expr⟩, тип которого должен быть 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⟩

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

:edit ⟨file⟩

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

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

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

:force ⟨identifier⟩ ...

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

:forward ⟨n⟩

Перемещается вперёд на ⟨n⟩ шагов по истории. Если ⟨n⟩ не указано, оно равно единице. Подробнее о средствах отладки GHCi см. в разделе Трассировка и история. См. также: :trace, :history, :back.

:help
:?

Показывает список доступных команд.

:

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

:history [num]

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

:info[!] ⟨name⟩

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

GHCi выводит объявления типов (синонимы типов, newtype и типы данных, классы, семейства типов и данных) со значимыми с точки зрения семантики невидимыми связующими @.

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

  • Он явно встречается в теле объявления
  • За ним следует значимый связующий, поэтому он влияет на позицию

Для объявлений типов, не порождающих новые типы (синонимов типов и семейств типов), действует ещё один критерий:

  • За ним не следует видимый связующий, поэтому он влияет на арность объявления типа

Для типов и классов 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. Однако при тестировании в ghci нельзя просто передать аргументы в main, поскольку 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. Если встречаются невычисленные компоненты (thunk-структуры), :print связывает с каждой такой структурой новую переменную, имя которой начинается с _t. Дополнительные сведения см. в разделе Точки останова и проверка переменных. См. также команду :sprint, которая работает как :print, но не связывает новые переменные.

:quit

Завершает работу GHCi. Также можно выйти, введя в приглашении Control-D.

:reload[!] [none | ⟨mod1⟩ ...]

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

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

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

:run

См. :main.

:script [⟨n⟩] ⟨filename⟩

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

:set [⟨option⟩ ...]

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

:set args ⟨arg⟩

Задаёт список аргументов, возвращаемых при вызове программой System.Environment.getArgs.

:set editor ⟨cmd⟩

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

: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-часовом формате ЧЧ:ММ:СС.
  • %T текущим временем в 12-часовом формате ЧЧ:ММ:СС.
  • %@ текущим временем в 12-часовом формате с указанием AM/PM.
  • %A текущим временем в 24-часовом формате ЧЧ:ММ.
  • %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).

:stepout
Начиная с:

9.14.1

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

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

f = do

a b <— (1) установите точку останова, затем выполните переход сюда c

b = do

… d <— (2) выполните переход внутрь этого хвостового вызова

d = do

… something <— (3) выполните выход отсюда …

При пошаговом выходе выполнение остановится на вызове c в f, а не на b.

:stepmodule

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

:trace ⟨expr⟩

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

:type ⟨expression⟩

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

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

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

:type +d ⟨expression⟩

Выводит тип ⟨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⟩

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

:undef ⟨name⟩

Удаляет пользовательскую команду ⟨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.

:where

Показывает текущий стек вычисления, когда выполнение остановлено на точке останова.

:: ⟨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

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

:set +r

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

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

:set +s

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

: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

При запуске GHCi считывает и выполняет команды из следующих файлов в указанном порядке, если они существуют, кроме случаев, когда задан флаг -ignore-dot-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
Начиная с версии:

8.0.1

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

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

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

Зачем это нужно? Главная причина в том, что RTS, выполняющая интерпретируемый код, может отличаться от 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⟩ на целевой машине и передачи ей временного каталога (куда она скопирует необходимые для загрузки библиотеки) и порта она будет ожидать подключения прокси.

Если вместе с -fexternal-interpreter указать -pgmi ⟨/path/to/iserv-proxy⟩ и -opti ⟨slave-ip⟩ -opti ⟨slave-port⟩ [-opti -v], GHC будет работать через прокси.

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

3.12. Сборка библиотек GHCi

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

3.13. Часто задаваемые вопросы и важные замечания

Интерпретатор не может загружать модули с объявлениями foreign export!

К сожалению, это так. Эта возможность ещё не реализована. Перед загрузкой в 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).

В Windows я не могу использовать Control-C, чтобы прервать вычисления в GHCi.

См. раздел Запуск 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.14.1/docs/users_guide/ghci.html

Spec-Zone.ru

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