5. Использование GHCi
GHCi 1 — это интерактивная среда GHC, в которой можно интерактивно вычислять выражения Haskell и интерпретировать программы. Если вы знакомы с Hugs, то вы быстро освоитесь с GHCi. Однако GHCi также поддерживает интерактивную загрузку скомпилированного кода, а также все 2 расширения языка, предоставляемые GHC. GHCi также включает интерактивный отладчик (см. Отладчик GHCi).
-
1 -
Буква «i» обозначает «Интерактивный»
-
2 -
кроме
foreign export, на данный момент
5.1. Введение в GHCi
Давайте начнём с примера сессии GHCi. Вы можете запустить GHCi с помощью команды ghci:
$ ghci GHCi, version 8.y.z: https://www.haskell.org/ghc/ :? for help Prelude>
Может быть небольшая пауза, пока GHCi загружает прелюдию и стандартные библиотеки, после чего отобразится приглашение. Как сказано в заголовке, вы можете ввести :?, чтобы увидеть список доступных команд и краткое описание каждой из них. Мы объясним большинство этих команд по ходу дела, а полная документация по всем командам находится в разделе Команды GHCi.
Выражения Haskell можно вводить в приглашении:
Prelude> 1+2 3 Prelude> let x = 42 in x / 9 4.666666666666667 Prelude>
GHCi интерпретирует всю строку как выражение для вычисления. Выражение не может занимать несколько строк — как только вы нажмёте Enter, GHCi попытается его вычислить.
В Haskell за выражением let следует in. Однако в GHCi, поскольку выражение также может быть интерпретировано в монаде IO, привязку let без сопровождающего in оператора можно сигнализировать пустой строкой, как в приведённом выше примере.
Начиная с GHC 8.0.1, вы можете привязывать значения и функции к именам без let оператора:
Prelude> x = 42 Prelude> x 42 Prelude>
5.2. Загрузка исходных файлов
Предположим, у нас есть следующий исходный код Haskell, который мы разместим в файле Main.hs:
main = print (fac 20) fac 0 = 1 fac n = n * fac (n-1)
Вы можете сохранить Main.hs в любом месте, но если вы сохраните его не в текущей директории 3, нам нужно будет перейти в нужную директорию в GHCi:
Prelude> :cd dir
где ⟨dir⟩ — это директория (или папка), в которой вы сохранили Main.hs.
Для загрузки файла исходного кода Haskell в GHCi используйте команду :load:
Prelude> :load Main Compiling Main ( Main.hs, interpreted ) Ok, modules loaded: Main. *Main>
GHCi загрузил модуль Main, и приглашение изменилось на *Main>, чтобы указать, что текущий контекст для выражений, вводимых в приглашении, — это модуль Main, который мы только что загрузили (мы объясним, что означает * позже в разделе Что на самом деле находится в области видимости в приглашении?). Теперь мы можем вводить выражения, включающие функции из модуля Main.hs:
*Main> fac 17 355687428096000
Загрузка многомодульной программы выполняется аналогично; просто укажите имя «главного» модуля команде :load (подсказка: :load можно сократить до :l). Главный модуль, как правило, Main, но он может быть и другим. GHCi обнаружит, какие модули прямо или косвенно требуются главному модулю, и загрузит их все в порядке зависимости.
-
3 -
Если вы запустили GHCi из командной строки, то текущей директорией GHCi является та же, что и текущая директория оболочки, из которой он был запущен. Если вы запустили GHCi из меню «Пуск» в Windows, то текущей директорией, скорее всего, будет что-то вроде
C:\Documents and Settings\user name.
-
-fshow-loaded-modules -
- По умолчанию
-
выкл.
- С версии
-
8.2.2
Обычно GHCi отображает только количество модулей, загруженных после команды
:load. С этим флагом GHC также будет отображать имена загруженных модулей. Это было поведением по умолчанию до GHC 8.2.1 и может быть полезным для некоторых пользователей инструментов.
5.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используется для указания пути поиска файлов интерфейса, см. Путь поиска.
5.2.2. Внесение изменений и перекомпиляция
Если вы внесли изменения в исходный код и хотите, чтобы GHCi перекомпилировал программу, используйте команду :reload. Программа будет перекомпилирована при необходимости, GHCi постарается избежать фактической перекомпиляции модулей, если их внешние зависимости не изменились. Это тот же механизм, который мы используем для избежания перекомпиляции модулей в режиме пакетной компиляции (см. Проверяющий перекомпиляцию).
5.3. Загрузка скомпилированного кода
При загрузке Haskell-модуля в GHCi он обычно преобразуется в байт-код и выполняется с помощью интерпретатора. Однако интерпретированный код также может выполняться вместе со скомпилированным кодом в GHCi; на самом деле, обычно при запуске GHCi он загружает скомпилированную копию пакета base, который содержит Prelude.
Почему мы хотим использовать скомпилированный код? Ну, скомпилированный код примерно в 10 раз быстрее, чем интерпретированный, но его создание занимает примерно в 2 раза больше времени (возможно, больше, если включена оптимизация). Поэтому имеет смысл компилировать те части программы, которые редко изменяются, и использовать интерпретатор для активно разрабатываемого кода.
При загрузке исходных модулей с помощью :load, GHCi обычно ищет соответствующие скомпилированные файлы объектного кода и использует один из них вместо интерпретации исходного кода, если это возможно. Например, предположим, что у нас есть программа из 4 модулей, состоящая из модулей A, B, C, и D. Модули B и C оба импортируют только D, а A импортирует как B, так и C:
A / \ B C \ / D
Мы можем скомпилировать D, затем загрузить всю программу следующим образом:
Prelude> :! ghc -c -dynamic D.hs Prelude> :load A Compiling B ( B.hs, interpreted ) Compiling C ( C.hs, interpreted ) Compiling A ( A.hs, interpreted ) Ok, modules loaded: A, B, C, D (D.o). *Main>
В сообщениях компилятора мы видим, что нет строки для D. Это потому, что компиляция D не нужна, так как исходный код и все его зависимости не изменились с момента последней компиляции.
Обратите внимание на флаг -dynamic для GHC: GHCi использует динамически подключаемый объектный код (если на вашей платформе это поддерживается), и поэтому для использования скомпилированного кода с GHCi он должен быть скомпилирован для динамической загрузки.
В любой момент вы можете использовать команду :show modules, чтобы получить список модулей, загруженных в GHCi:
*Main> :show modules D ( D.hs, D.o ) C ( C.hs, interpreted ) B ( B.hs, interpreted ) A ( A.hs, interpreted ) *Main>
Если мы теперь изменим исходный код D (или предположим это: удобным для этого является использование команды Unix touch для исходного файла), компилятор больше не сможет использовать объектный файл, потому что он может быть устаревшим:
*Main> :! touch D.hs *Main> :reload Compiling D ( D.hs, interpreted ) Ok, modules loaded: A, B, C, D. *Main>
Обратите внимание, что модуль D был скомпилирован, но в этом случае, поскольку его исходный код фактически не изменился, его интерфейс остался таким же, и проверяющий перекомпиляцию определил, что A, B и C перекомпилировать не нужно.
Итак, давайте попробуем скомпилировать один из других модулей:
*Main> :! ghc -c C.hs *Main> :load A Compiling D ( D.hs, interpreted ) Compiling B ( B.hs, interpreted ) Compiling C ( C.hs, interpreted ) Compiling A ( A.hs, interpreted ) Ok, modules loaded: A, B, C, D.
Мы не получили скомпилированную версию C! Что случилось? Ну, в GHCi скомпилированный модуль может зависеть только от других скомпилированных модулей, и в данном случае C зависит от D, у которого нет файла объектного кода, поэтому GHCi также отклонил файл объектного кода C. Хорошо, давайте также скомпилируем D:
*Main> :! ghc -c D.hs *Main> :reload Ok, modules loaded: A, B, C, D.
Ничего не произошло! Вот еще один урок: вновь скомпилированные модули не подхватываются командой :reload, только командой :load:
*Main> :load A Compiling B ( B.hs, interpreted ) Compiling A ( A.hs, interpreted ) Ok, modules loaded: A, B, C (C.o), D (D.o).
Автоматическая загрузка файлов объектного кода может иногда привести к путанице, потому что неэкспортированные определения на верхнем уровне модуля доступны для использования в выражениях в командной строке только при интерпретации модуля (см. Что действительно находится в области видимости в командной строке?). По этой причине иногда вам может потребоваться принудительно загрузить модуль с помощью интерпретатора. Это можно сделать, добавив префикс * к имени модуля или имени файла при использовании :load, например
Prelude> :load *A Compiling A ( A.hs, interpreted ) *A>
Когда используется *, GHCi игнорирует любой предварительно скомпилированный объектный код и интерпретирует модуль. Если вы уже загрузили несколько модулей как объектный код и решили, что хотите интерпретировать один из них, вместо повторной загрузки всего набора вы можете использовать :add *M для указания того, что вы хотите, чтобы M был интерпретирован (обратите внимание, что это может привести к интерпретации и других модулей, так как скомпилированные модули не могут зависеть от интерпретируемых).
Чтобы всегда компилировать все в объектный код и никогда не использовать интерпретатор, используйте опцию -fobject-code (см. Компиляция в объектный код внутри GHCi).
Подсказка
Поскольку GHCi будет использовать только файл объектного кода, если может быть уверен, что скомпилированная версия актуальна, хорошей техникой при работе с большой программой является периодическое выполнение ghc --make для компиляции всего проекта (скажем, перед обедом :-), а затем продолжение работы в интерпретаторе. При изменении кода измененные модули будут интерпретироваться, а остальная часть проекта останется скомпилированной.
5.4. Интерактивная оценка в командной строке
Когда вы вводите выражение в командной строке, GHCi немедленно оценивает и выводит результат:
Prelude> reverse "hello" "olleh" Prelude> 5+5 10
5.4.1. Действия ввода/вывода в командной строке
GHCi делает больше, чем просто вычисление выражений в командной строке. Если вы введете выражение типа IO a для некоторого a, то GHCi выполняет его как вычисление ввода/вывода.
Prelude> "hello" "hello" Prelude> putStrLn "hello" hello
Это работает даже если тип выражения более общий, при условии, что он может быть инстанцирован в IO a. Например
Prelude> return True True
Кроме того, GHCi выведет результат действия ввода/вывода, если (и только если):
- Тип результата является экземпляром
Show. - Тип результата не является
().
Например, помня, что putStrLn :: String -> IO ():
Prelude> putStrLn "hello"
hello
Prelude> do { putStrLn "hello"; return "yes" }
hello
"yes"
5.4.2. Использование обозначения do в командной строке
GHCi фактически принимает операторы, а не только выражения в командной строке. Это означает, что вы можете привязать значения и функции к именам и использовать их в будущих выражениях или операторах.
Синтаксис оператора, принятого в командной строке GHCi, точно такой же, как и синтаксис оператора в Haskell do выражении. Однако перегрузки монады здесь нет: операторы, набранные в командной строке, должны быть в IO монаде.
Prelude> x <- return 42 Prelude> print x 42 Prelude>
Оператор x <- return 42 означает «выполнить return 42 в IO монаде и привязать результат к x». Затем мы можем использовать x в будущих операторах, например, для вывода, как мы делали выше.
-
-fprint-bind-result -
Если установлен
-fprint-bind-result, GHCi будет выводить результат оператора, если и только если:- Оператор не является привязкой или это монадическая привязка (
p <- e) которая связывает ровно одну переменную. - Тип переменной не является полиморфным, не является
(), и является экземпляромShow.
- Оператор не является привязкой или это монадическая привязка (
Конечно, вы также можете привязать обычные выражения, не связанные с вводом/выводом, используя оператор let:
Prelude> let x = 42 Prelude> x 42 Prelude>
Другое важное различие между двумя типами привязок заключается в том, что монадическая привязка (p <- e) является строгой (она вычисляет e), тогда как при использовании формы let, выражение не вычисляется немедленно:
Prelude> let x = error "help!" Prelude> print x *** Exception: help! Prelude>
Обратите внимание, что привязки let не автоматически выводят привязанное значение, в отличие от монадических привязок.
Вы также можете определить функции в командной строке:
Prelude> add a b = a + b Prelude> add 1 2 3 Prelude>
Однако это быстро становится утомительным при определении функций с несколькими условиями или группами взаимно рекурсивных функций, потому что полное определение должно быть дано на одной строке, используя явные точки с запятой вместо форматирования:
Prelude> f op n [] = n ; f op n (h:t) = h `op` f op n t Prelude> f (+) 0 [1..3] 6 Prelude>
-
:{ -
:} -
Начать или закончить блок многострочных команд GHCi.
Чтобы решить эту проблему, команды GHCi можно разбить на несколько строк, заключив их в :{ и :} (каждая на отдельной строке):
Prelude> :{
Prelude| g op n [] = n
Prelude| g op n (h:t) = h `op` g op n t
Prelude| :}
Prelude> g (*) 1 [1..3]
6
Такие многострочные команды можно использовать с любой командой GHCi, и обратите внимание, что правило форматирования действует. Основное назначение многострочных команд — не заменить загрузку модулей, а сделать определения в файлах .ghci (см. Файлы .ghci и .haskeline) более читабельными и поддерживаемыми.
Любые исключения, возникшие во время оценки или выполнения оператора,捕获并由GHCi командной строки (для получения дополнительной информации об исключениях см. модуль Control.Exception в документации по библиотекам).
Каждая новая привязка затеняет любые существующие привязки с тем же именем, включая сущности, находящиеся в области видимости в текущем контексте модуля.
Предупреждение
Временные привязки, введенные в командной строке, действуют только до следующей команды :load или :reload, после чего они просто теряются. Однако они сохраняются при смене контекста с помощью :module: временные привязки просто перемещаются в новое место.
Подсказка
Чтобы получить список привязок, находящихся в настоящее время в области видимости, используйте команду :show bindings:
Prelude> :show bindings x :: Int Prelude>
Подсказка
Если вы включите опцию +t, GHCi будет показывать тип каждой переменной, связанной оператором. Например:
Prelude> :set +t Prelude> let (x:xs) = [1..] x :: Integer xs :: [Integer]
5.4.3. Многострочный ввод
Помимо синтаксиса :{ ... :} для многострочного ввода, упомянутого выше, GHCi также имеет режим многострочного ввода, включенный командами :set +m, :set +m, в котором GHCi автоматически определяет, когда текущий оператор не завершен, и позволяет добавлять дополнительные строки. Многострочный ввод завершается пустой строкой. Например:
Prelude> :set +m Prelude> let x = 42 Prelude|
Дополнительные привязки могут быть добавлены к этому let оператору, поэтому GHCi указывает, что следующая строка продолжает предыдущую, изменяя приглашение. Обратите внимание, что форматирование действует, поэтому, чтобы добавить больше привязок к этому let оператору, нам нужно выровнять их:
Prelude> :set +m Prelude> let x = 42 Prelude| y = 3 Prelude| Prelude>
Явные фигурные скобки и точки с запятой можно использовать вместо форматирования:
Prelude> do {
Prelude| putStrLn "hello"
Prelude| ;putStrLn "world"
Prelude| }
hello
world
Prelude>
Обратите внимание, что после закрывающей фигурной скобки GHCi знает, что текущий оператор завершен, поэтому пустая строка не требуется.
Режим многострочного ввода полезен при вводе монадических do операторов:
Control.Monad.State> flip evalStateT 0 $ do Control.Monad.State| i <- get Control.Monad.State| lift $ do Control.Monad.State| putStrLn "Hello World!" Control.Monad.State| print i Control.Monad.State| "Hello World!" 0 Control.Monad.State>
Во время многострочного взаимодействия пользователь может прервать его и вернуться к приглашению верхнего уровня.
Prelude> do Prelude| putStrLn "Hello, World!" Prelude| ^C Prelude>
5.4.4. Объявления типов, классов и других элементов
В командной строке GHCi вы также можете ввести любые декларации Haskell на верхнем уровне, включая data, type, newtype, class, instance, deriving, и foreign декларации. Например:
Prelude> data T = A | B | C deriving (Eq, Ord, Show, Enum) Prelude> [A ..] [A,B,C] Prelude> :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 будет рассматривать их как разные. Например:
Prelude> data T = A | B
Prelude> let f A = True; f B = False
Prelude> data T = A | B | C
Prelude> 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
Prelude>
Старая, затененная, версия T отображается GHCi как main::Interactive.T для того, чтобы отличить ее от нового T, который отображается просто как T.
Объявления экземпляров класса и типа-семейства просто добавляются в список доступных экземпляров, за исключением одного случая. Поскольку вы можете захотеть переопределить один, экземпляр класса заменяет любой предыдущий экземпляр с идентичным заголовком. Вам запрещено переопределять экземпляр типа семейства, так как это может быть небезопасно с точки зрения типа. Вместо этого переопределите все семейство типов. (См. Типы семейств.) Например:
Prelude> type family T a b
Prelude> type instance T a b = a
Prelude> let uc :: a -> T a b; uc = id
Prelude> 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.
Prelude> type family T a b
-- This is a brand-new T, unrelated to the old one
Prelude> type instance T a b = b
Prelude> 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
5.4.5. Что действительно находится в области видимости в командной строке?
Когда вы вводите выражение в приглашении, какие идентификаторы и типы находятся в области видимости? GHCi предоставляет гибкий способ управления тем, как создаётся контекст для выражения:
- Команды
:load,:addи:reload(Влияние :load на область видимости). - Декларация
import(Управление областью видимости с помощью import). - Команда
:module(Управление областью видимости с помощью команды :module).
Команда :show imports покажет сводку модулей, которые влияют на область видимости верхнего уровня.
Подсказка
GHCi будет автоматически дополнять имена, которые находятся в области видимости; например, если вы запустите GHCi и наберете J<tab>, то GHCi расширит его до Just.
5.4.5.1. Влияние :load на область видимости
Команды :load, :add и :reload (Загрузка исходных файлов и Загрузка скомпилированного кода) влияют на область видимости верхнего уровня. Начнём с простых случаев; когда вы запускаете GHCi, приглашение выглядит так:
Prelude>
что указывает на то, что всё из модуля Prelude в настоящее время находится в области видимости; видимые идентификаторы — это те, которые были бы видны в файле исходного кода Haskell без деклараций import.
Если теперь загрузить файл в GHCi, приглашение изменится:
Prelude> :load Main.hs Compiling Main ( Main.hs, interpreted ) *Main>
Новое приглашение — *Main, что указывает на то, что мы вводим выражения в контексте верхнего уровня модуля Main. Всё, что находится в области видимости на верхнем уровне в модуле Main , которое мы только что загрузили, также находится в области видимости в приглашении (вероятно, включая Prelude, если Main явно его не скрывает).
Синтаксис в приглашении *module указывает, что вся область видимости верхнего уровня ⟨модуль⟩ вносит вклад в область видимости для выражений, вводимых в приглашении. Без * видимы только экспорты модуля.
Примечание
По техническим причинам GHCi может поддерживать только форму * для интерпретируемых модулей. Скомпилированные модули и модули пакетов могут вносить в текущую область видимости только свои экспорты. Чтобы убедиться, что GHCi загружает интерпретируемую версию модуля, добавьте * при загрузке модуля, например, :load *M.
В общем случае после команды :load в область видимости автоматически добавляется импорт для недавно загруженного целевого модуля в форме * , если это возможно. Например, если вы скажете :load foo.hs bar.hs , а bar.hs содержит модуль Bar, то область видимости будет установлена в *Bar , если Bar интерпретируется, или в Prelude Bar , если Bar скомпилирован (GHCi автоматически добавляет Prelude , если он отсутствует и нет модулей в форме *). Эти автоматически добавленные импорты можно увидеть с помощью :show imports:
Prelude> :load hello.hs [1 of 1] Compiling Main ( hello.hs, interpreted ) Ok, modules loaded: Main. *Main> :show imports :module +*Main -- added automatically *Main>
и автоматически добавленный импорт заменяется при следующем использовании :load, :add или :reload. Его также можно удалить с помощью :module, как и обычные импорты.
5.4.5.2. Управление областью видимости с помощью import
Мы не ограничены одним модулем: GHCi может объединять области видимости из нескольких модулей в любом сочетании форм * и не-*. GHCi объединяет области видимости всех этих модулей для формирования области видимости, которая действует в приглашении.
Для добавления модулей в область видимости используйте обычный синтаксис Haskell import:
Prelude> import System.IO Prelude System.IO> hPutStrLn stdout "hello\n" hello Prelude System.IO>
Поддерживается полный синтаксис импорта Haskell, включая hiding и as предложения. Приглашение показывает модули, которые в настоящее время импортированы, но не показывает подробности о hiding, as и т. д. Чтобы увидеть полную картину, используйте :show imports:
Prelude> import System.IO Prelude System.IO> import Data.Map as Map Prelude System.IO Map> :show imports import Prelude -- implicit import System.IO import Data.Map as Map Prelude System.IO Map>
Обратите внимание, что импорт Prelude отмечен как неявный. Его можно переопределить явным импортом Prelude , как и в модуле Haskell.
При наличии нескольких модулей в области видимости, особенно нескольких модулей в форме *, возможно возникновение коллизий имён. Haskell определяет, что о конфликтах имён сообщается только в случае использования неоднозначного идентификатора, и GHCi ведёт себя аналогично для выражений, вводимых в приглашении.
5.4.5.3. Управление областью видимости с помощью команды :module
Другой способ управления областью видимости — использование команды :module со следующим синтаксисом:
:module +|- *mod1 ... *modn
Использование формы + команд module добавляет модули в текущую область видимости, а - удаляет их. Без + или - текущая область видимости заменяется наборами указанных модулей. Обратите внимание, что если вы используете эту форму и опускаете Prelude, автоматически добавляется неявный импорт Prelude.
Команда :module позволяет выполнить две задачи, которые нельзя выполнить с обычными декларациями import:
-
:moduleподдерживает модификатор*для модулей, открывая полную область видимости верхнего уровня модуля, а не только его экспорты. - Импорты могут быть удалены из контекста, используя синтаксис
:module -M. Синтаксисimportявляется кумулятивным (как в модуле Haskell), поэтому это единственный способ вычесть из области видимости.
5.4.5.4. Квалифицированные имена
Для упрощения GHCi-приглашение также ведет себя так, как будто есть неявная декларация import qualified для каждого модуля в каждом пакете и каждого модуля, загруженного в GHCi. Это поведение можно отключить с помощью флага -fno-implicit-import-qualified.
5.4.5.5. :module и :load
Может показаться, что :module/import и :load/:add/:reload выполняют схожие действия: оба позволяют включить модуль в область видимости. Однако существует очень важное различие. GHCi оперирует двумя наборами модулей:
- Набор модулей, которые в настоящее время загружены. Этот набор изменяется командами
:load,:addи:reloadи может быть показан с помощью:show modules. - Набор модулей, которые в настоящее время находятся в области видимости в приглашении. Этот набор изменяется командами
importи:module, а также автоматически изменяется после:load,:addи:reload, как описано выше. Набор модулей в области видимости можно показать с помощью:show imports.
Вы можете добавить модуль в область видимости (через :module или import) только если (а) он загружен или (б) он является модулем из пакета, о котором GHCi знает. Попытка добавить в область видимости не загруженный модуль с помощью :module или import может привести к сообщению module M is not loaded.
5.4.6. Команды :main и :run
При компиляции и выполнении программы она может использовать функцию getArgs для доступа к аргументам командной строки. Однако мы не можем просто передать аргументы функции main во время тестирования в GHCi, так как функция main не принимает их непосредственно.
Вместо этого мы можем использовать команду :main. Она запускает любую функцию main , которая находится в области видимости, при этом любые аргументы обрабатываются так же, как аргументы командной строки, например:
Prelude> main = System.Environment.getArgs >>= print Prelude> :main foo bar ["foo","bar"]
Мы также можем указывать аргументы, содержащие символы, такие как пробелы, и они будут обрабатываться как строки Haskell, или мы можем просто использовать синтаксис списков Haskell:
Prelude> :main foo "bar baz" ["foo","bar baz"] Prelude> :main ["foo", "bar baz"] ["foo","bar baz"]
Наконец, можно вызвать другие функции, либо с флагом -main-is , либо с командой :run:
Prelude> foo = putStrLn "foo" >> System.Environment.getArgs >>= print Prelude> bar = putStrLn "bar" >> System.Environment.getArgs >>= print Prelude> :set -main-is foo Prelude> :main foo "bar baz" foo ["foo","bar baz"] Prelude> :run bar ["foo", "bar baz"] bar ["foo","bar baz"]
5.4.7. Переменная it
Всякий раз, когда выражение (или не связывающее утверждение, точнее) вводится в приглашение, GHCi неявно связывает его значение с переменной it. Например:
Prelude> 1+2 3 Prelude> it * 2 6
Фактически происходит следующее: GHCi проверяет тип выражения, и если у него нет типа IO, то он преобразует его следующим образом: выражение e преобразуется в
let it = e; print it
которое затем выполняется как действие IO.
Следовательно, исходное выражение должно иметь тип, являющийся экземпляром класса Show, иначе GHCi выдаст ошибку:
Prelude> 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. Например:
Prelude> Data.Time.getZonedTime 2017-04-10 12:34:56.93213581 UTC Prelude> print it 2017-04-10 12:34:56.93213581 UTC
Соответствующее преобразование для типа IO e имеет вид
it <- e
Обратите внимание, что it затеняется новым значением каждый раз, когда вы вычисляете новое выражение, и старое значение it теряется.
Чтобы предотвратить привязку значения it к каждой команде, можно установить флаг -fno-it. Переменная it может быть источником утечек памяти из-за того, как GHCi обрабатывает затенение объявлений (см. Объявления типов, классов и других элементов).
-
-fno-it -
При установке этого флага переменная
itбольше не будет установлена в результат ранее выполненного выражения.
5.4.8. Настройка типов по умолчанию в GHCi
-
ExtendedDefaultRules -
- С
-
6.8.1
Разрешить настройку по умолчанию для более чем просто числовых классов.
Рассмотрим сеанс GHCi:
ghci> reverse []
Что должен сделать GHCi? Строго говоря, программа неоднозначна. show (reverse []) (что вычисляет GHCi в данном случае) имеет тип Show a => String, и как это отображается, зависит от типа a. Например:
ghci> reverse ([] :: String) "" ghci> reverse ([] :: [Int]) []
Однако, для пользователя утомительно каждый раз указывать тип, поэтому GHCi расширяет правила настройки типов Haskell (Раздел 4.3.4 Отчета Haskell 2010) следующим образом. Стандартные правила берут каждую группу ограничений (C1 a, C2 a, ..., Cn a) для каждой переменной типа a, и устанавливают значение по умолчанию для переменной типа, если
- Переменная типа
aне появляется в других ограничениях - Все классы
Ciявляются стандартными. - По крайней мере, один из классов
Ciявляется числовым.
В приглашении GHCi или с GHC, если задан флаг ExtendedDefaultRules, типы вместо этого разрешаются следующим способом:
Найдите все неразрешенные ограничения. Затем:
- Найдите те, которые имеют вид
(C a), гдеa— это переменная типа, и разделите эти ограничения на группы, которые имеют общую переменную типаa. - Сохраните только те группы, в которых по крайней мере один из классов является интерактивным классом (определено ниже).
- Теперь для каждой оставшейся группы G, попробуйте каждый тип
tyиз списка типов по умолчанию поочередно; если установкаa = tyпозволит полностью разрешить ограничения в G. В таком случае установите значение по умолчанию дляaвty. - Тип единицы
()и тип списка[]добавляются в начало стандартного списка типов, которые используются при настройке типов по умолчанию.
Обратите внимание, что любые многопараметрические ограничения (D a b) или (D [a] Int) не участвуют в процессе (ни для помощи, ни для препятствия); но, конечно, они должны быть разрешимы после завершения процесса настройки по умолчанию.
Последний момент означает, что, например, эта программа:
main :: IO () main = print def instance Num () def :: (Num a, Enum a) => a def = toEnum 0
выводит (), а не 0, поскольку тип настроен по умолчанию на (), а не на Integer.
Цель изменения заключается в том, что действия IO a по умолчанию устанавливаются в IO (), что, в свою очередь, означает, что ghci не будет пытаться вывести результат при их выполнении. Это особенно важно для printf, у которого есть экземпляр, возвращающий IO a. Однако, он может вернуть только undefined (причина, по которой экземпляр имеет этот тип, состоит в том, что printf не требует расширений системы классов), поэтому, если тип настроен по умолчанию на Integer, ghci выдаст ошибку при выполнении printf.
См. также Действия ввода-вывода в приглашении для того, как монада вычислительного выражения по умолчанию устанавливается в IO при возможности.
5.4.8.1. Интерактивные классы
Интерактивные классы (актуальны только при включении ExtendedDefaultRules) — это любые числовые классы, Show, Eq, Ord, Foldable или Traversable.
До тех пор, пока переменная типа ограничена одним из этих классов, настройка по умолчанию будет происходить, как описано выше.
5.4.8.2. Расширенные правила для объявлений по умолчанию
Поскольку правила настройки по умолчанию ослаблены при включении ExtendedDefaultRules, правила для объявлений по умолчанию также ослаблены. Согласно разделу 4.3.4 отчета Haskell 2010, объявление по умолчанию выглядит так default (t1, ..., tn), где для каждой ti, должно выполняться условие Num ti. Это ослабляется до утверждения, что для каждой ti, должен существовать интерактивный класс C, такой что выполняется условие C ti. Это означает, что конструкторы типов могут быть разрешены в этих списках. Например, следующее работает, если вы хотите, чтобы ваши ограничения Foldable настраивались по умолчанию на Maybe, но ваши ограничения Num по-прежнему настраивались по умолчанию на Integer или Double:
default (Maybe, Integer, Double)
5.4.9. Использование пользовательской функции интерактивного вывода
С 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, :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]!
5.4.10. Стек вызовов в 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 (см. Вставка центров затрат вручную).
5.5. Отладчик GHCi
GHCi содержит простой отладчик в стиле императива, в котором вы можете остановить выполнение вычислений для проверки значений переменных. Отладчик интегрирован в GHCi и включён по умолчанию: для включения функций отладки не требуются флаги. Существует одно важное ограничение: точки останова и пошаговое выполнение доступны только в интерпретируемых модулях; скомпилированный код недоступен для отладчика 5.
Отладчик предоставляет следующее:
- Возможность установки точки останова на определении функции или выражении в программе. Когда функция вызывается или выражение оценивается, GHCi приостанавливает выполнение и возвращается к приглашению, где вы можете проверить значения локальных переменных перед продолжением выполнения.
- Выполнение может быть пошаговым: вычислитель приостанавливает выполнение приблизительно после каждого сокращения, что позволяет проверять локальные переменные. Это эквивалентно установке точки останова в каждой точке программы.
- Выполнение может происходить в режиме трассировки, в котором вычислитель запоминает каждый шаг вычисления по мере его выполнения, но не приостанавливает выполнение до достижения фактической точки останова. В этом случае можно просмотреть историю шагов вычисления.
- Исключения (например, неудача сопоставления с образцом и
error) могут рассматриваться как точки останова, чтобы помочь найти источник исключения в программе.
В настоящее время нет поддержки получения «стека вызовов», но функции трассировки и истории предоставляют полезную альтернативу, которой часто бывает достаточно для установления контекста ошибки. Например, можно автоматически приостановить выполнение при возникновении исключения, даже если оно возникло внутри скомпилированного кода (см. Отладка исключений).
5.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:
Prelude> :l qsort.hs [1 of 1] Compiling Main ( qsort.hs, interpreted ) Ok, modules loaded: Main. *Main>
Теперь установим точку останова в правой части второго уравнения qsort:
*Main> :break 2 Breakpoint 0 activated at qsort.hs:2:15-46 *Main>
Команда :break 2 устанавливает точку останова на строке 2 в последнем загруженном модуле, в данном случае qsort.hs. В частности, она выбирает самое левое полное подвыражение на этой строке, на котором нужно установить точку останова, что в данном случае является выражением (qsort left ++ [a] ++ qsort right).
Теперь запустим программу:
*Main> main Stopped at qsort.hs:2:15-46 _result :: [a] a :: a left :: [a] right :: [a] [qsort.hs:2:15-46] *Main>
Выполнение остановилось на точке останова. Приглашение изменилось, чтобы указать, что мы в настоящее время остановились на точке останова, и местоположение: [qsort.hs:2:15-46]. Для более точного определения местоположения мы можем использовать команду :list:
[qsort.hs:2:15-46] *Main> :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] *Main> 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] *Main> :set -fprint-evld-with-show [qsort.hs:2:15-46] *Main> :print left left = (_t1::[a])
Это не особенно информативно. То, что произошло, — это то, что left связано с неоценённым вычислением (подвешенным или thunk), и :print не принуждает к какой-либо оценке. Идея заключается в том, что :print может использоваться для проверки значений в точке останова без каких-либо нежелательных побочных эффектов. Он не будет принуждать к оценке, что может привести к получению программы другого ответа, чем обычно, и, следовательно, не вызовет никаких исключений, бесконечных циклов или дальнейших точек останова (см. Вложенные точки останова). Вместо того, чтобы принуждать к оценке thunks, :print связывает каждый thunk со свежей переменной, начинающейся с нижнего подчёркивания, в данном случае _t1.
-
-fprint-evld-with-show -
Флаг
-fprint-evld-with-showуказывает:printна повторное использование доступных экземпляровShow, где это возможно. Это происходит только тогда, когда содержимое проверяемой переменной полностью оценивается.
Если нас не волнует сохранение оцененности переменной, мы можем использовать :force вместо :print. Команда :force ведет себя точно так же, как :print, за исключением того, что она принуждает к оценке всех встреченных thunk:
[qsort.hs:2:15-46] *Main> :force left left = [4,0,3,1]
Теперь, поскольку :force проверил значение во время выполнения left, он восстановил его тип. Мы можем увидеть результаты этого восстановления типа:
[qsort.hs:2:15-46] *Main> :show bindings _result :: [Integer] a :: Integer left :: [Integer] right :: [Integer] _t1 :: [Integer]
Теперь мы не только знаем тип left, но и все другие частичные типы были разрешены. Таким образом, мы можем запросить значение a, например:
[qsort.hs:2:15-46] *Main> a 8
Возможно, вам пригодится функция Haskell seq для оценки отдельных thunk вместо оценки всего выражения с помощью :force. Например:
[qsort.hs:2:15-46] *Main> :print right right = (_t1::[Integer]) [qsort.hs:2:15-46] *Main> seq _t1 () () [qsort.hs:2:15-46] *Main> :print right right = 23 : (_t2::[Integer])
Мы оценили только _t1 thunk, обнаружив начало списка, а хвост — это другой thunk, теперь связанный с _t2. Функция seq немного неудобна в использовании здесь, поэтому вы можете использовать :def для создания более удобного интерфейса (оставлено в качестве упражнения для читателя!).
Наконец, мы можем продолжить текущее выполнение:
[qsort.hs:2:15-46] *Main> :continue Stopped at qsort.hs:2:15-46 _result :: [a] a :: a left :: [a] right :: [a] [qsort.hs:2:15-46] *Main>
Выполнение продолжилось в той точке, где оно ранее остановилось, и теперь снова остановилось на точке останова.
5.5.1.1. Установка точек останова
Точки останова можно устанавливать различными способами. Возможно, самый простой способ установить точку останова — назвать функцию верхнего уровня:
:break identifier
Где ⟨identifier⟩ обозначает любую функцию верхнего уровня в интерпретируемом модуле, который в настоящее время загружен в GHCi (можно использовать квалифицированные имена). Точка останова будет установлена в теле функции, когда она будет полностью применена, но до выполнения сопоставления с образцом.
Точки останова также можно устанавливать по номеру строки (и, необязательно, столбцу):
:break line :break line column :break module line :break module line column
Когда точка останова устанавливается на конкретной строке, GHCi устанавливает точку останова на левом крайнем подвыражении, которое начинается и заканчивается на этой строке. Если две полные подвыражения начинаются в одном столбце, выбирается самое длинное. Если на строке нет полного подвыражения, то выбирается левое подвыражение, начинающееся на этой строке, а если и этого нет, то правое подвыражение, которое частично или полностью охватывает строку.
Когда точка останова устанавливается на определенной строке и столбце, GHCi выбирает самое маленькое подвыражение, которое включает эту позицию, на котором нужно установить точку останова. Примечание: GHC рассматривает символ ТАБ как имеющий ширину 1, где бы он ни встречался; другими словами, он подсчитывает символы, а не столбцы. Это соответствует тому, что делают некоторые редакторы, и не соответствует другим. Лучший совет — вообще избегать символов табуляции в вашем исходном коде (см. -Wtabs в Предупреждения и проверка на корректность).
Если модуль опущен, используется последний загруженный модуль.
Не все подвыражения являются потенциальными точками останова. Одиночные переменные обычно не считаются точками останова (за исключением случая, когда переменная является правой частью определения функции, лямбда-выражения или альтернативы case). Правило гласит, что все редексы являются точками останова, вместе с телами функций, лямбда-выражений, альтернатив case и операторов связывания. Обычно нет точки останова на выражении let, но всегда будет точка останова на его теле, поскольку мы обычно заинтересованы в проверке значений переменных, связанных с let.
5.5.1.2. Управление точками останова
Список текущих точек останова можно отобразить, используя :show breaks:
*Main> :show breaks [0] Main qsort.hs:1:11-12 enabled [1] Main qsort.hs:2:15-46 enabled
Чтобы отключить одну или несколько определенных точек останова, используйте команду :disable с одним или несколькими разделенными пробелами номерами, указанными в выводе из :show breaks:. Чтобы отключить все точки останова сразу, используйте :disable *.
*Main> :disable 0 *Main> :show breaks [0] Main qsort.hs:1:11-12 disabled [1] Main qsort.hs:2:15-46 enabled
Отключенные точки останова можно (снова) включить с помощью команды :enable. Параметры команд :disable и :enable идентичны.
Чтобы удалить точку останова, используйте команду :delete с номером, указанным в выводе из :show breaks:
*Main> :delete 0 *Main> :show breaks [1] Main qsort.hs:2:15-46 disabled
Чтобы удалить все точки останова сразу, используйте :delete *.
5.5.2. Пошаговое выполнение
Пошаговое выполнение — отличный способ визуализировать выполнение программы и полезный инструмент для выявления источника ошибки. GHCi предлагает два варианта пошагового выполнения. Используйте :step, чтобы включить все точки останова в программе и выполнить ее до достижения следующей точки останова. Используйте :steplocal, чтобы ограничить набор включенных точек останова теми, которые находятся в текущей функции верхнего уровня. Аналогично, используйте :stepmodule, чтобы выполнять по шагам только точки останова, содержащиеся в текущем модуле. Например:
*Main> :step main Stopped at qsort.hs:5:7-47 _result :: IO ()
Команда :step expr начинает оценку ⟨expr⟩ в режиме пошагового выполнения. Если ⟨expr⟩ опущено, то выполнение продолжается с текущей точки останова. Команды :steplocal и :stepmodule работают аналогично.
Команда :list особенно полезна при пошаговом выполнении, чтобы увидеть текущее положение:
[qsort.hs:5:7-47] *Main> :list 4 5 main = print (qsort [8, 4, 0, 3, 1, 23, 11, 18]) 6 [qsort.hs:5:7-47] *Main>
Фактически, GHCi предоставляет способ выполнения команды при достижении точки останова, поэтому мы можем автоматически выполнить :list:
[qsort.hs:5:7-47] *Main> :set stop :list [qsort.hs:5:7-47] *Main> :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] *Main>
5.5.3. Вложенные точки останова
Когда GHCi останавливается на точке останова, и введенное в командной строке выражение активирует вторую точку останова, новая точка останова становится «текущей», а старая сохраняется в стеке. Таким образом, можно создать произвольное количество контекстов точек останова. Например:
[qsort.hs:2:15-46] *Main> :st qsort [1,3] Stopped at qsort.hs:(1,0)-(3,55) _result :: [a] ... [qsort.hs:(1,0)-(3,55)] *Main>
Когда мы остановились на точке останова на строке 2, которую установили ранее, мы запустили новую оценку с помощью :step qsort [1,3]. Эта новая оценка остановилась после одного шага (на определении qsort). Командная строка изменилась, теперь с префиксом ..., чтобы указать, что существуют сохраненные точки останова за пределами текущей. Чтобы увидеть стек контекстов, используйте :show context:
... [qsort.hs:(1,0)-(3,55)] *Main> :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)] *Main>
Чтобы прервать текущую оценку, используйте :abandon:
... [qsort.hs:(1,0)-(3,55)] *Main> :abandon [qsort.hs:2:15-46] *Main> :abandon *Main>
5.5.4. Переменная _result
При остановке на точке останова или при пошаговом выполнении GHCi связывает переменную _result со значением текущего активного выражения. Значение _result предположительно еще недоступно, так как мы остановили его оценку, но его можно принудительно получить: если тип известен и отображается, то ввод _result в командную строку отобразит его. Однако есть одно предостережение: оценка _result, скорее всего, приведет к дополнительным точкам останова, начиная с точки останова, на которой мы остановились (если мы остановились на реальной точке останова, а не из-за :step). Поэтому, вероятно, потребуется немедленно выполнить :continue при оценке _result. В качестве альтернативы, вы можете использовать :force, которое игнорирует точки останова.
5.5.5. Отслеживание и история
Вопрос, который мы часто задаем при отладке программы, — «как я сюда попал?». Традиционные императивные отладчики обычно предоставляют функцию отслеживания стека, которая позволяет просмотреть стек активных вызовов функций (иногда называемый «лексическим стеком вызовов»), описывая путь по коду до текущего местоположения. К сожалению, это сложно реализовать в Haskell, так как выполнение происходит на основе запроса, а не на основе глубины, как в строгих языках. «Стек» в исполняющей системе GHC мало похож на лексический стек вызовов. В идеале GHCi должен поддерживать отдельный лексический стек вызовов в дополнение к динамическому стеку вызовов, и, на самом деле, именно это делает наша система профилирования (Профилирование), и некоторые другие отладчики Haskell. Однако на данный момент GHCi не поддерживает лексический стек вызовов (существуют некоторые технические проблемы). Вместо этого мы предоставляем способ отслеживания назад от точки останова до предыдущих шагов оценки: по сути, это похоже на пошаговое выполнение назад и во многих случаях должно предоставлять достаточную информацию для ответа на вопрос «как я сюда попал?».
Для использования отслеживания оцените выражение с помощью команды :trace. Например, если мы установим точку останова на базовом случае qsort:
*Main> :list qsort 1 qsort [] = [] 2 qsort (a:as) = qsort left ++ [a] ++ qsort right 3 where (left,right) = (filter (<=a) as, filter (>a) as) 4 *Main> :b 1 Breakpoint 1 activated at qsort.hs:1:11-12 *Main>
а затем запустим небольшое qsort с отслеживанием:
*Main> :trace qsort [3,2,1] Stopped at qsort.hs:1:11-12 _result :: [a] [qsort.hs:1:11-12] *Main>
Теперь мы можем просмотреть историю шагов оценки:
[qsort.hs:1:11-12] *Main> :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] *Main> :back Logged breakpoint at qsort.hs:3:24-38 _result :: [a] as :: [a] a :: a [-1: qsort.hs:3:24-38] *Main>
Обратите внимание, что локальные переменные на каждом шаге в истории были сохранены и могут быть изучены обычным способом. Также обратите внимание, что командная строка изменилась, чтобы указать, что мы в данный момент рассматриваем первый шаг в истории: -1. Команда :forward может быть использована для перемещения вперед в истории.
Команда :trace может быть использована с или без выражения. Когда она используется без выражения, отслеживание начинается с текущей точки останова, как и :step.
История доступна только при использовании :trace; причина в том, что мы обнаружили, что ведение журнала каждой точки останова в истории снижает производительность в 2 раза или более.
-
-fghci-hist-size=⟨n⟩ -
- Значение по умолчанию
-
50
Изменить глубину истории оценки, отслеживаемой GHCi.
5.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, для регистрации шагов, предшествующих исключению. Например:
*Main> :set -fbreak-on-exception
*Main> :trace qsort ("abc" ++ undefined)
“Stopped at <exception thrown>
_exception :: e
[<exception thrown>] *Main> :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>] *Main> :back
Logged breakpoint at qsort.hs:3:24-38
_result :: [a]
as :: [a]
a :: a
[-1: qsort.hs:3:24-38] *Main> :force as
*** Exception: Prelude.undefined
[-1: qsort.hs:3:24-38] *Main> :print as
as = 'b' : 'c' : (_t1::[Char])
Само исключение привязано к новой переменной _exception.
Останов на исключениях особенно полезен для выявления того, что делала ваша программа, когда она была в бесконечном цикле. Просто нажмите Control-C и просмотрите историю, чтобы выяснить, что происходило.
-
-fbreak-on-exception -
Приводит к остановке вычислений GHCi и возвращению к интерактивному приглашению в случае возникновения исключения.
-fbreak-on-exceptionостанавливается на всех исключениях.
-
-fbreak-on-error -
Приводит к остановке вычислений GHCi и возвращению к интерактивному приглашению в случае возникновения исключения.
-fbreak-on-errorостанавливается только на тех исключениях, которые в противном случае остались бы необработанными.
5.5.7. Пример: инспектирование функций
Возможно использовать отладчик для проверки значений функций. Когда мы находимся на точке останова и функция находится в области видимости, отладчик не может показать вам исходный код для неё; однако, возможно получить некоторую информацию, применив её к некоторым аргументам и наблюдая результат.
Процесс немного усложняется, когда связывание является полиморфным. Мы покажем процесс на примере. Для простоты мы будем использовать хорошо известную map функцию:
import Prelude hiding (map) map :: (a->b) -> [a] -> [b] map f [] = [] map f (x:xs) = f x : map f xs
Мы устанавливаем точку останова на map, и вызываем её.
*Main> :break 5 Breakpoint 0 activated at map.hs:5:15-28 *Main> 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.
*Main> seq x () *Main> :print x x = 1
Теперь мы можем проверить, что, как ожидалось, тип x был реконструирован, а с ним и тип f:
*Main> :t x x :: Integer *Main> :t f f :: Integer -> b
Отсюда мы можем применить f к любому аргументу типа Integer и наблюдать результаты.
*Main> let b = f 10
*Main> :t b
b :: b
*Main> b
<interactive>:1:0:
Ambiguous type variable `b' in the constraint:
`Show b' arising from a use of `print' at <interactive>:1:0
*Main> :p b
b = (_t2::a)
*Main> seq b ()
()
*Main> :t b
b :: a
*Main> :p b
b = Just 10
*Main> :t b
b :: Maybe Integer
*Main> :t f
f :: Integer -> Maybe Integer
*Main> f 20
Just 20
*Main> map f [1..5]
[Just 1, Just 2, Just 3, Just 4, Just 5]
В первом применении f нам пришлось сделать больше реконструкции типов, чтобы восстановить тип результата f. Но после этого мы можем использовать f как обычно.
5.5.8. Ограничения
-
При остановке на точке останова, если вы попытаетесь оценить переменную, которая уже находится в процессе оценки, вторая оценка зависнет. Причина в том, что GHC знает, что переменная оценивается, поэтому новая оценка просто ожидает результата перед продолжением, но, конечно, этого не произойдёт, потому что первая оценка остановлена на точке останова. Команда Ctrl+C может прервать зависшую оценку и вернуться к приглашению.
Наиболее распространённый способ, которым это может произойти, это когда вы оцениваете CAF (например, main), останавливаетесь на точке останова и снова запрашиваете значение CAF в приглашении.
- Неявные параметры (см. Неявные параметры) доступны только в области видимости точки останова, если есть явное описание типа.
5.6. Вызов GHCi
GHCi вызывается командой ghci или ghc --interactive. Также можно указать один или несколько модулей или имён файлов в командной строке; это указывает GHCi загрузить указанные модули или имена файлов (и все модули, от которых они зависят), точно так же, как если бы вы ввели :load modules в приглашении GHCi (см. Команды GHCi). Например, чтобы запустить GHCi и загрузить программу, главный модуль которой находится в файле Main.hs, мы можем сказать:
$ ghci Main.hs
Большинство командных строк, принятых GHC (см. Использование GHC), также имеют смысл в интерактивном режиме. Те, которые не имеют смысла, в основном очевидны.
-
-flocal-ghci-history -
По умолчанию GHCi сохраняет глобальную историю в
~/.ghc/ghci_historyили%APPDATA%/<app>/ghci_history, но вы можете использовать текущую директорию, например:$ ghci -flocal-ghci-history
Это создаст
.ghci-historyв текущей папке, где запущен GHCi.
-
-fghci-leak-check -
(Только для отладки) При загрузке новых модулей с помощью
:load, проверьте, что все ранее загруженные модули были правильно собраны сборщиком мусора. Выводит сообщения, если утечка обнаружена.
5.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. Prelude>
Следующая команда работает для загрузки новых пакетов в работающий GHCi:
Prelude> :set -package name
Однако обратите внимание, что это приведёт к разгрузке всех загруженных в настоящее время модулей, и вас вернёт обратно в Prelude.
5.6.2. Дополнительные библиотеки
Дополнительные библиотеки могут быть указаны в командной строке с помощью обычного -llib параметра. (Термин библиотека здесь относится к библиотекам кода внешних объектов; для использования библиотек кода Haskell см. Модули и имена файлов.) Например, чтобы загрузить библиотеку “m”:
$ ghci -lm
В системах с библиотеками общего пользования в стиле .so, фактически загружаемая библиотека будет liblib.so. GHCi ищет библиотеки в следующих местах, в указанном порядке:
- Пути, указанные с помощью параметра командной строки
-L ⟨dir⟩, - Стандартный путь поиска библиотек для вашего системного загрузчика, который в некоторых системах может быть переопределён настройкой переменной окружения
LD_LIBRARY_PATH. - Стандартный путь поиска библиотек компоновщика также может быть переопределён в некоторых системах с помощью переменной окружения
LIBRARY_PATH. Из-за некоторых особенностей реализации в Windows, настройкаLIBRARY_PATHтакже расширит путь системного загрузчика для любых найденных библиотек. Поэтому часто установкаLIBRARY_PATHдостаточно.
В системах с библиотеками общего пользования в стиле .dll, фактически загружаемая библиотека будет lib.dll, liblib.dll. GHCi также полностью поддерживает импортируемые библиотеки, в стиле Microsoft .lib, или GNU GCC .a и .dll.a библиотеки. Если у вас есть импортируемая библиотека, рекомендуется всегда указывать импортируемую библиотеку вместо .dll. Например, используйте -lgcc` instead of
``-llibgcc_s_seh-1. Опять же, GHCi выдаст ошибку, если не найдёт библиотеку.
Порядок параметров -l имеет значение: библиотека должна упоминаться до библиотек, от которых она зависит (см. Параметры, влияющие на компоновку).
5.7. Команды GHCi
Все команды GHCi начинаются с “:” и состоят из одного имени команды, за которым следует ноль или более параметров. Имя команды может быть сокращено, при этом неоднозначности разрешаются в пользу более часто используемых команд.
-
:abandon -
Отменяет текущую оценку (доступно только при остановке на точке останова).
-
:add[*] ⟨module⟩ -
Добавляет ⟨модуль⟩(и) в текущий набор целей и выполняет перезагрузку. Обычно предварительно скомпилированный код для модуля загружается, если доступен, или же модуль компилируется в байт-код. Использование префикса
*заставляет модуль загружаться в виде байт-кода.⟨модуль⟩ может быть путём к файлу. Символ “
~” в начале ⟨модуль⟩ будет заменён содержимым переменной окруженияHOME.
-
:all-types -
Выводит все типы, собранные для выражений и (локальных) связываний, которые в настоящее время загружены (в то время, когда
:set +cбыл активен) вместе с соответствующим фрагментом исходного кода, например:GhciTypes> :all-types GhciTypes.hs:(38,13)-(38,24): Maybe Id GhciTypes.hs:(45,10)-(45,29): Outputable SpanInfo GhciTypes.hs:(45,10)-(45,29): (Rational -> SpanInfo -> SDoc) -> Outputable SpanInfo
-
:back ⟨n⟩ -
Возвращается назад на ⟨n⟩ шагов в истории. ⟨n⟩ равно одному, если не указано. См. Отслеживание и история для получения дополнительной информации об отладочных средствах GHCi. См. также:
:trace,:history,:forward.
-
:break [⟨identifier⟩ | [⟨module⟩] ⟨line⟩ [⟨column⟩]] -
Устанавливает точку останова на указанную функцию или строку и столбец. См. Установка точек останова.
-
:browse[!] [[*] ⟨module⟩] -
Отображает идентификаторы, экспортированные модулем ⟨модуль⟩, который должен быть загружен в GHCi или являться членом пакета. Если ⟨модуль⟩ опущен, используется последний загруженный модуль.
Как и все остальные команды GHCi, вывод всегда отображается в текущем пространстве имен GHCi (Что на самом деле находится в пространстве имен на приглашении?).
Существует два варианта команды browse:
-
Если символ
*помещен перед именем модуля, то все идентификаторы в пространстве имен в ⟨модуле⟩ (а не только экспортированные) отображаются.Формат с
*доступен только для интерпретируемых модулей; для скомпилированных модулей (включая модули из пакетов) доступен только формат без*для:browse. -
Конструкторы данных и методы классов обычно отображаются в контексте их определения типа данных или класса. Однако, если к команде добавлен символ
!, то есть:browse!, они перечисляются по отдельности. Формат с!также сопровождает список комментариями, указывающими возможные импорты для каждой группы записей. Вот пример:Prelude> :browse! Data.Maybe -- not currently imported Data.Maybe.catMaybes :: [Maybe a] -> [a] Data.Maybe.fromJust :: Maybe a -> a Data.Maybe.fromMaybe :: a -> Maybe a -> a Data.Maybe.isJust :: Maybe a -> Bool Data.Maybe.isNothing :: Maybe a -> Bool Data.Maybe.listToMaybe :: [a] -> Maybe a Data.Maybe.mapMaybe :: (a -> Maybe b) -> [a] -> [b] Data.Maybe.maybeToList :: Maybe a -> [a] -- imported via Prelude Just :: a -> Maybe a data Maybe a = Nothing | Just a Nothing :: Maybe a maybe :: b -> (a -> b) -> Maybe a -> b
Этот вывод показывает, что в контексте текущего сеанса (т.е. в пространстве имен
Prelude) первая группа элементов изData.Maybeне находятся в пространстве имен (хотя они доступны в полном квалифицированном виде в сеансе GHCi — см. Что на самом деле находится в пространстве имен на приглашении?), тогда как вторая группа элементов находятся в пространстве имен (черезPrelude) и поэтому доступны либо без квалификатора, либо с квалификаторомPrelude..
-
-
:cd ⟨dir⟩ -
Изменяет текущую рабочую директорию на ⟨директория⟩. Символ “
~” в начале ⟨директория⟩ будет заменен содержимым переменной окруженияHOME. См. также команду:show pathsдля отображения текущей рабочей директории.Примечание: изменение директории приводит к разгрузке всех загруженных в настоящее время модулей. Это связано с тем, что путь поиска обычно выражается с использованием относительных директорий, и изменение пути поиска в середине сеанса не поддерживается.
-
:cmd ⟨expr⟩ -
Выполняет ⟨выражение⟩ как вычисление типа
IO String, а затем выполняет полученную строку как список команд GHCi. Несколько команд разделяются символом новой строки. Команда:cmdполезна с:defи:set stop.
-
:complete ⟨type⟩ [⟨n⟩-][⟨m⟩] ⟨string-literal⟩ -
Эта команда позволяет запрашивать завершение команд из GHCi даже при взаимодействии через канал вместо полноценной терминальной среды и предназначена для интеграции завершения GHCi с текстовыми редакторами и IDE.
При вызове
:completeвыводит ⟨n⟩-й по ⟨m⟩-й кандидаты на завершение для частичного ввода ⟨строка-литерал⟩ для области завершения, обозначенной ⟨тип⟩. В настоящее время поддерживается только областьrepl, которая обозначает тип завершения, который бы предоставлялся интерактивно GHCi в поле ввода.Если опущены, ⟨n⟩ и ⟨m⟩ по умолчанию равны первому или последнему доступному кандидату на завершение соответственно. Если кандидатов меньше, чем запрошено в аргументе диапазона, ⟨n⟩ и ⟨m⟩ неявно ограничиваются числом доступных кандидатов на завершение.
Вывод
:completeначинается с заголовочной строки, содержащей три поля, разделенные пробелами:- Целое число, обозначающее количество
lнапечатанных завершений, - целое число, обозначающее общее количество доступных завершений, и, наконец,
- строка-литерал, обозначающая общий префикс, который должен быть добавлен к возвращаемым кандидатам на завершение.
За заголовочной строкой следуют ⟨l⟩ строк, каждая из которых содержит один кандидат на завершение, закодированный как (цитируемая) строка-литерал. Вот некоторые примеры вызовов, показывающие различные случаи:
Prelude> :complete repl 0 "" 0 470 "" Prelude> :complete repl 5 "import For" 5 21 "import " "Foreign" "Foreign.C" "Foreign.C.Error" "Foreign.C.String" "Foreign.C.Types" Prelude> :complete repl 5-10 "import For" 6 21 "import " "Foreign.C.Types" "Foreign.Concurrent" "Foreign.ForeignPtr" "Foreign.ForeignPtr.Safe" "Foreign.ForeignPtr.Unsafe" "Foreign.Marshal" Prelude> :complete repl 20- "import For" 2 21 "import " "Foreign.StablePtr" "Foreign.Storable" Prelude> :complete repl "map" 3 3 "" "map" "mapM" "mapM_" Prelude> :complete repl 5-10 "map" 0 3 ""
- Целое число, обозначающее количество
-
:continue -
Продолжить текущее вычисление, когда оно остановлено на точке останова.
-
Генерирует файл «tags» для редакторов типа Vi (
:ctags) или редакторов типа Emacs (:etags). Если имя файла не указано, используется значение по умолчаниюtagsилиTAGSсоответственно. Создаются теги для всех функций, конструкторов и типов в загруженных в настоящее время модулях. Для работы этих команд все модули должны быть интерпретируемыми.
-
:def[!] ⟨name⟩ ⟨expr⟩ -
:defиспользуется для определения новых команд или макросов в GHCi. Команда:def ⟨name⟩ ⟨expr⟩определяет новую команду GHCi:name, реализованную выражением Haskell ⟨выражение⟩, которое должно иметь типString -> IO String. При вводе:name argsв приглашении GHCi выполнит выражение(name args), возьмет полученныйString, и передаст его обратно в GHCi в виде новой последовательности команд. Отдельные команды в результате должны быть разделены символом “\n”.Это немного запутанно, поэтому вот несколько примеров. Начнем с новой команды GHCi, которая не принимает аргументов и не производит результатов, она просто выводит текущую дату и время:
Prelude> let date _ = Data.Time.getZonedTime >>= print >> return "" Prelude> :def date date Prelude> :date 2017-04-10 12:34:56.93213581 UTC
Вот пример команды, принимающей аргумент. Это повторная реализация
:cd:Prelude> let mycd d = System.Directory.setCurrentDirectory d >> return "" Prelude> :def mycd mycd Prelude> :mycd ..
Или я мог бы определить простой способ вызова “
ghc --make Main” в текущей директории:Prelude> :def make (\_ -> return ":! ghc --make Main")
Мы можем определить команду, которая считывает ввод GHCi из файла. Это может быть полезно для создания набора связей, которые мы хотим многократно загружать в сеанс GHCi:
Prelude> :def . readFile Prelude> :. cmds.ghci
Обратите внимание, что мы назвали команду
:., по аналогии с командой Unix «.», которая делает то же самое.Ввод
:defв отдельной строке выводит список определенных в настоящее время макросов. Попытка переопределить существующее имя команды приводит к ошибке, за исключением использования формы:def!, в котором старая команда с этим именем беззвучно перезаписывается. Однако для встроенных команд старую команду можно использовать, поместив перед именем команды двойное двоеточие (например,::load). Невозможно переопределить команды:{,:}и:!.
-
:delete * | ⟨num⟩ ... -
Удалить одну или несколько точек останова по номеру (используйте
:show breaksдля просмотра номера каждой точки останова). Форма*удаляет все точки останова.
-
:disable * | ⟨num⟩ ... -
Отключить одну или несколько точек останова по номеру (используйте
:show breaksдля просмотра номера и состояния каждой точки останова). Форма*отключает все точки останова.
-
:doc ⟨name⟩ -
(Экспериментально: эта команда, вероятно, значительно изменится в GHC 8.8.)
Отображает документацию для данного имени. В настоящее время команда ограничена отображением документации непосредственно на соответствующем объявлении, игнорируя документацию для аргументов, конструкторов и т.д.
-
:edit ⟨file⟩ -
Открывает редактор для редактирования файла ⟨файл⟩ или последнего загруженного модуля, если ⟨файл⟩ опущен. Если при последней загрузке произошли ошибки, курсор будет помещен на строку первой ошибки. Редактор, который будет вызван, определяется из переменной окружения
EDITORили по умолчанию из вашей системы, еслиEDITORне задана. Вы можете изменить редактор, используя:set editor.
-
:enable * | ⟨num⟩ ... -
Включить одну или несколько отключенных точек останова по номеру (используйте
:show breaksдля просмотра номера и состояния каждой точки останова). Форма*включает все отключенные точки останова.
-
См.
:ctags.
-
:force ⟨identifier⟩ ... -
Выводит значение ⟨идентификатор⟩ так же, как и
:print. В отличие от:print,:forceвычисляет каждую ленивую вычислительную операцию, встречающуюся во время прохождения значения. Это может привести к исключениям или бесконечным циклам, или к дополнительным точкам останова (которые игнорируются, но отображаются).
-
:forward ⟨n⟩ -
Переместиться вперед на ⟨n⟩ шагов в истории. ⟨n⟩ равно 1, если опущен. См. Отслеживание и история для получения дополнительной информации об отладочных возможностях GHCi. См. также:
:trace,:history,:back.
-
:help -
:? -
Отображает список доступных команд.
-
: -
Повторить предыдущую команду.
-
:history [num] -
Отображает историю шагов вычисления. С числом отображает столько шагов (по умолчанию: 20). Используется с
:trace; см. Отслеживание и история. Чтобы установить количество записей истории, хранимых в GHCi, используйте флаг-fghci-hist-size=⟨n⟩.
-
:info[!] ⟨name⟩ -
Отображает информацию о заданном(ых) имени(ях). Например, если ⟨имя⟩ — это класс, то будут напечатаны методы класса и их типы; если ⟨имя⟩ — это конструктор типа, то будет напечатано его определение; если ⟨имя⟩ — это функция, то будет напечатан её тип. Если ⟨имя⟩ загружено из файла исходного кода, GHCi также отобразит расположение его определения в исходном коде.
Для типов и классов GHCi также суммирует упоминающие их экземпляры. Чтобы избежать отображения нерелевантной информации, экземпляр показывается только в том случае, если (а) его заголовок упоминает ⟨имя⟩, и (б) все остальные вещи, упомянутые в экземпляре, находятся в области видимости (либо квалифицированные, либо иным образом) в результате команд
:loadили:module.Команда
:info!работает аналогичным образом, но она удаляет ограничение (б), отображая все экземпляры, которые находятся в области видимости и упоминают ⟨имя⟩ в своем заголовке.
-
:issafe [⟨module⟩] -
Отображает информацию Safe Haskell о заданном модуле (или текущем модуле, если опущен). Включает тип доверия модуля и содержащего его пакета.
-
:kind[!] ⟨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 :: * -> *
Если вы укажете необязательный «
!», GHC дополнительно нормализует тип, раскрыв псевдонимы типов и вычислив применения функций типа, и отобразит нормализованный результат.
-
:list ⟨identifier⟩ -
Выводит исходный код вокруг определения ⟨идентификатора⟩ или текущей точки останова, если она не задана. Для этого необходимо, чтобы идентификатор был определен в интерпретируемом модуле. Если ваше устройство вывода поддерживает, то GHCi выделит активное подвыражение жирным шрифтом.
-
:list [⟨module⟩] ⟨line⟩ -
Выводит исходный код вокруг заданной строки номера ⟨module⟩. Для этого требуется, чтобы модуль был интерпретирован. Если ваше устройство вывода поддерживает, то GHCi выделит активное подвыражение жирным шрифтом.
-
:load[!] [*]⟨module⟩ -
Рекурсивно загружает указанные ⟨модули⟩ и все зависящие от них модули. Здесь каждое ⟨модуль⟩ должно быть именем модуля или именем файла, но не должно быть именем модуля в пакете.
Все ранее загруженные модули, кроме модулей пакетов, забываются. Новый набор модулей известен как целевой набор. Обратите внимание, что
:loadможет быть использован без аргументов, чтобы разгрузить все загруженные в данный момент модули и привязки.Обычно прекомпилированный код для модуля будет загружен, если доступен, или в противном случае модуль будет скомпилирован в байт-код. Использование префикса
*принудительно загружает модуль как байт-код.Добавление необязательного «
!» преобразует ошибки типов в предупреждения во время загрузки. Это позволяет использовать части модуля, которые корректны, даже если в некоторых определениях есть ошибки типов. По сути, флаг «-fdefer-type-errors» устанавливается перед загрузкой и сбрасывается после загрузки, если он не был установлен ранее. См. Откладывание ошибок типов до выполнения для дополнительной мотивации и подробностей.После команды
:load, текущий контекст устанавливается в:- ⟨модуль⟩, если он был загружен успешно, или
- последний успешно загруженный модуль, если любые другие модули были загружены в результате текущей команды
: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.
-
:instances ⟨type⟩ -
Отображает все доступные экземпляры классов для аргумента ⟨тип⟩. Команда сопоставит ⟨тип⟩ с первым параметром каждого экземпляра, а затем проверит, что все ограничения выполнимы.
В сочетании с
PartialTypeSignaturesпользователь может вставлять подстановочные знаки в запрос и узнавать ограничения, необходимые для каждого подстановочного знака для соответствия ⟨тип⟩ с экземпляром.Вывод представляет собой список всех соответствующих экземпляров, упрощенных и инстанцированных по возможности.
Например:
> :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’
-
:main ⟨arg1⟩ ... ⟨argn⟩ -
Когда программа компилируется и выполняется, она может использовать функцию
getArgsдля доступа к аргументам командной строки. Однако мы не можем просто передать аргументы функцииmainво время тестирования в ghci, так как функцияmainне принимает свои аргументы напрямую.Вместо этого мы можем использовать команду
:main. Это запускает то, чтоmainнаходится в области видимости, при этом любые аргументы обрабатываются так же, как аргументы командной строки, например:Prelude> main = System.Environment.getArgs >>= print Prelude> :main foo bar ["foo","bar"]
Мы также можем привести аргументы, содержащие символы, такие как пробелы, и они обрабатываются как строки Haskell, или мы можем просто использовать синтаксис списка Haskell:
Prelude> :main foo "bar baz" ["foo","bar baz"] Prelude> :main ["foo", "bar baz"] ["foo","bar baz"]
Наконец, другие функции могут быть вызваны, либо с флагом
-main-isили с командой:run:Prelude> foo = putStrLn "foo" >> System.Environment.getArgs >>= print Prelude> bar = putStrLn "bar" >> System.Environment.getArgs >>= print Prelude> :set -main-is foo Prelude> :main foo "bar baz" foo ["foo","bar baz"] Prelude> :run bar ["foo", "bar baz"] bar ["foo","bar baz"]
-
:module +|- [*]⟨mod1⟩ ...
-
import ⟨mod⟩ -
Устанавливает или изменяет текущий контекст для операторов, набираемых в командной строке. Форма
import modэквивалентна:module +mod. См. Что действительно находится в области видимости в командной строке? для получения дополнительных сведений.
-
:print ⟨names⟩ -
Выводит значение, не принуждая к его вычислению.
:printможет использоваться для значений, типы которых неизвестны или частично известны, что может быть случаем для локальных переменных с полиморфными типами в точке останова. Во время проверки значения времени выполнения:printпытается восстановить тип значения и расширит тип в среде GHCi, если это возможно. Если обнаружатся невычисленные компоненты (ленивые вычисления), тогда:printпривязывает свежую переменную с именем, начинающимся с_tк каждому ленивому вычислению. См. Точки останова и проверка переменных для получения дополнительной информации. См. также команду:sprint, которая работает как:print, но не привязывает новых переменных.
-
:quit -
Выходит из GHCi. Вы также можете выйти, набрав Control-D в командной строке.
-
:reload[!] -
Попытка перезагрузить текущий целевой набор (см.
:load), если любой из модулей в наборе или любой зависимый модуль изменился. Обратите внимание, что это может потребовать загрузки новых модулей или удаления модулей, которые больше не косвенно требуются целевым набором.Добавление необязательного «
!» преобразует ошибки типов в предупреждения во время загрузки. Это позволяет использовать части модуля, которые корректны, даже если в некоторых определениях есть ошибки типов. По сути, флаг «-fdefer-type-errors» устанавливается перед загрузкой и сбрасывается после загрузки, если он не был установлен ранее. См. Откладывание ошибок типов до выполнения для получения дополнительной мотивации и подробностей.
-
:run -
См.
:main.
-
:script [⟨n⟩] ⟨filename⟩ -
Выполняет строки файла в качестве серии команд GHCi. Эта команда совместима с многострочными операторами, заданными с помощью
:set +m
-
:set [⟨option⟩ ...] -
Устанавливает различные параметры. См. Команды :set и :seti для списка доступных параметров и Параметры режима интерактивного использования для списка флагов, специфичных для GHCi. Команда
:setсама по себе отображает, какие параметры установлены в данный момент. Она также отображает текущие динамические настройки флагов, при этом флаги, специфичные для GHCi, отображаются отдельно.
-
:set args ⟨arg⟩ -
Устанавливает список аргументов, которые возвращаются, когда программа вызывает
System.getArgs.
-
:set editor ⟨cmd⟩ -
Устанавливает команду, используемую
:edit, на ⟨cmd⟩.
-
:set local-config ⟨source|ignore⟩ -
Если
ignore, файлы./.ghciбудут игнорироваться (инициализация недоверенных локальных скриптов представляет собой угрозу безопасности). По умолчаниюsource. Установите эту директиву в вашем пользовательском.ghciскрипте, т.е. перед инициализацией локального скрипта.Даже если установлено значение
ignore, локальный скрипт всё равно будет обработан, если он задан с помощью-ghci-scriptв командной строке или инициализирован с помощью:script.
-
:set prog ⟨prog⟩ -
Устанавливает строку, которая будет возвращена, когда программа вызовет
System.getProgName.
-
:set prompt ⟨prompt⟩ -
Устанавливает строку, которая будет использоваться в качестве приглашения в GHCi. Внутри ⟨prompt⟩ следующие последовательности заменяются:
-
%sименами модулей, которые в данный момент находятся в области видимости. -
%lномером строки (как указано в сообщениях компилятора) текущего приглашения. -
%dдатой в формате «День Неделя Месяц» (например, «Вторник Май 26»). -
%tтекущим временем в формате 24-часовых HH:MM:SS. -
%Tтекущим временем в формате 12-часовых HH:MM:SS. -
%@текущим временем в формате 12-часовых HH:MM am/pm. -
%Aтекущим временем в формате 24-часовых HH:MM. -
%uименем пользователя текущего пользователя. -
%wтекущей рабочей директорией. -
%oоперационной системой. -
%aархитектурой машины. -
%Nименем компилятора. -
%Vверсией компилятора. -
%call(cmd [args])результатом вызоваcmd args. -
%%значением%.
Если ⟨prompt⟩ начинается с
", он анализируется как строка Haskell; в противном случае он обрабатывается как литерная строка. -
-
:set prompt-cont ⟨prompt⟩ -
Устанавливает строку, которая будет использоваться в качестве приглашения для продолжения (используется при использовании команды
:{) в GHCi.
-
:set prompt-function ⟨prompt-function⟩ -
Устанавливает функцию, которая будет использоваться для отображения приглашения в GHCi. Функция должна иметь тип
[String] -> Int -> IO String. Эта функция вызывается каждый раз при создании приглашения. Первый аргумент представляет имена модулей, которые в данный момент находятся в области видимости (имя «главного» модуля начнётся с*; см. Что реально находится в области видимости в приглашении? для получения дополнительной информации). Второй аргумент — номер строки (как указано в сообщениях компилятора) текущего приглашения.
-
:set prompt-cont-function ⟨prompt-function⟩ -
Устанавливает функцию, которая будет использоваться для приглашения продолжения (используется при использовании команды
:{) для отображения в GHCi.
-
:set stop ⟨num⟩ ⟨cmd⟩ -
Устанавливает команду, которая будет выполнена при достижении точки останова или выборе нового элемента в истории. Наиболее распространенное использование
:set stop— отображение исходного кода в текущей позиции, например,:set stop :list.Если перед командой указано число, то команды выполняются при достижении только указанной точки останова. Это может быть весьма полезно: например,
:set stop 1 :continueфактически отключает точку останова 1, выполняя:continueпри её достижении. В этом случае GHCi всё равно выведет сообщение о достижении точки останова. Если вы не хотите такого сообщения, вы можете использовать команду:disable. Более того, с помощью хитрой комбинации:defи:cmdвы можете использовать:set stopдля реализации условных точек останова:*Main> :def cond \expr -> return (":cmd if (" ++ expr ++ ") then return \"\" else return \":continue\"") *Main> :set stop 0 :cond (x < 3)Игнорирование точек останова для определенного количества итераций также возможно с помощью аналогичных методов.
-
: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 [args|prog|prompt|editor|stop] -
Отображает указанное значение (см.
:set).
-
:sprint ⟨expr⟩ -
Выводит значение без принудительного вычисления.
:sprintаналогично:print, с той разницей, что невычисленные подвыражения не присваиваются новым переменным, они просто обозначаются_.
-
:step [⟨expr⟩] -
Включить все точки останова и начать вычисление выражения в режиме пошагового выполнения. В этом режиме вычисление будет остановлено после каждой редукции, позволяя просмотреть локальные переменные. Если ⟨expr⟩ не задано, вычисление возобновится в последней точке останова. См. Пошаговое выполнение.
-
:steplocal -
Включить только точки останова в текущей связи верхнего уровня и возобновить вычисление в последней точке останова. Продолжение с помощью
:steplocalневозможно, если последняя точка останова была достигнута ошибкой (-fbreak-on-error) или исключением (-fbreak-on-exception).
-
:stepmodule -
Включить только точки останова в текущем модуле и возобновить вычисление в последней точке останова.
-
:trace ⟨expr⟩ -
Вычисляет заданное выражение (или из последней точки останова, если выражение не задано), и дополнительно записывает шаги вычисления для последующего просмотра с помощью
:history. См. Отслеживание и история.
-
:type ⟨expression⟩ -
Выводит тип ⟨выражения⟩, включая явные кванторы forall для полиморфных типов. Выводимый тип — тот, который был бы выведен для переменной, присвоенной выражению, но без применённого ограничения мономорфизма.
*X> :type length length :: Foldable t => t a -> Int
-
:type +v ⟨expression⟩ -
Выводит тип ⟨выражения⟩, но без использования переменных типа или ограничений класса. Это полезно, когда вы используете
TypeApplicationsи заботитесь о различии между указанными переменными типа (доступными для применения типа) и выведенными переменными типа (недоступными). В этом режиме иногда выводятся ограничения (например,Show Int), которые легко могут быть решены, но решение этих ограничений может повлиять на переменные типа, поэтому GHC воздерживается.*X> :set -fprint-explicit-foralls *X> :type +v length length :: forall (t :: * -> *). Foldable t => forall a. t a -> Int
-
:type +d ⟨expression⟩ -
Вычисляет и выводит тип ⟨выражение⟩, если возможно, устанавливая значения по умолчанию для переменных типа. В этом режиме, если вычисленный тип ограничен каким-либо интерактивным классом (
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.
-
:undef ⟨name⟩ -
Удаляет пользовательскую команду ⟨имя⟩ (см.
:defвыше).
-
:unset ⟨option⟩ -
Сбрасывает определенные параметры. Смотрите Команды :set и :seti для списка доступных параметров.
-
:uses ⟨module⟩ ⟨line⟩ ⟨col⟩ ⟨end-line⟩ ⟨end-col⟩ [⟨name⟩] -
Отчитывает о всех локальных использованиях чего-либо в заданной позиции в модуле, например:
:uses GhciFind.hs 53 66 53 70 name GhciFind.hs:(46,25)-(46,29) GhciFind.hs:(47,37)-(47,41) GhciFind.hs:(53,66)-(53,70) GhciFind.hs:(57,62)-(57,66)
Эта команда полезна для выделения и навигации по всем использованиям идентификатора в редакторах и IDE.
-
:: ⟨builtin-command⟩ -
Выполняет встроенную команду GHCi (например,
::type 3). То есть, поиск в списке встроенных команд, исключая определенные макросы. См. также::def.
-
:! ⟨command⟩ -
Выполняет командную строку ⟨команда⟩.
5.8. Команды :set и :seti
Команда :set устанавливает два типа параметров: параметры GHCi, начинающиеся с “+”, и параметры командной строки, начинающиеся с “-“.
Примечание
В настоящее время команда :set не поддерживает какой-либо вид цитирования в своих аргументах: кавычки не будут удалены и не могут использоваться для группировки слов вместе. Например, :set -DFOO='BAR BAZ' не выполнит ожидаемых действий.
5.8.1. Параметры GHCi
Параметры GHCi можно установить с помощью :set и сбросить с помощью :unset.
Доступные параметры GHCi:
-
:set +c -
Собрать информацию о типе и расположении после загрузки модулей. Команды
:all-types,:loc-at,:type-atи:usesтребуют, чтобы+cбыл активен.
-
:set +m -
Включить разбор многострочных команд. Многострочная команда запрашивается, когда текущая строка ввода содержит открытые контексты выравнивания (см. Многострочный ввод).
-
:set +r -
Обычно любая оценка выражений верхнего уровня (иначе известные как CAF или Постоянные прикладные формы) в загруженных модулях сохраняется между оценками. Включение
+rприводит к тому, что все вычисления выражений верхнего уровня отбрасываются после каждой оценки (они все еще сохраняются *во время* одной оценки).Этот параметр может помочь, если оцененные выражения верхнего уровня потребляют большое количество памяти, или если вам нужны повторяемые показатели производительности.
-
:set +s -
Отображает некоторые статистические данные после оценки каждого выражения, включая затраченное время и количество выделенных байт. ПРИМЕЧАНИЕ: значение выделенной памяти точно только до размера области выделения менеджера памяти, поскольку оно рассчитывается на каждом GC. Следовательно, вы можете увидеть значения ноль, если не произошло ни одного GC.
-
:set +t -
Отобразить тип каждой переменной, связанной после ввода оператора в командной строке. Если оператор представляет собой единственное выражение, то единственной связью переменной будет переменная
it.
5.8.2. Установка параметров GHC командной строки в GHCi
Обычные параметры GHC командной строки также можно установить с помощью :set. Например, чтобы включить -Wmissing-signatures, вы введете:
Prelude> :set -Wmissing-signatures
Любой параметр GHC командной строки, обозначенный как динамический (см. таблицу в Справочник по флагам), можно установить с помощью :set. Чтобы сбросить параметр, можно установить обратный параметр:
Prelude> :set -Wno-incomplete-patterns -XNoMultiParamTypeClasses
Справочник по флагам содержит список обратных параметров, где это применимо.
Определенные статические параметры (-package ⟨pkg⟩, -I⟨dir⟩, -i⟨dir⟩[:⟨dir⟩]* и -l ⟨lib⟩, в частности) также будут работать, но некоторые могут вступить в силу только при следующей перезагрузке.
5.8.3. Установка параметров только для интерактивной оценки
GHCi фактически поддерживает два набора параметров:
- Параметры загрузки применяются при загрузке модулей
- Интерактивные параметры применяются при оценке выражений и команд, введённых в командной строке GHCi.
Команда :set изменяет оба, но есть также команда :seti (для «установить интерактивные»), которая влияет только на установленные интерактивные параметры.
Часто бывает полезно изменить интерактивные параметры, не применяя их к загруженным модулям. Например:
:seti -XMonoLocalBinds
Нежелательно, чтобы MonoLocalBinds применялся к загруженным модулям: это может вызвать ошибку компиляции, но чаще всего это вызовет дополнительную перекомпиляцию, поскольку GHC будет думать, что ему нужно перекомпилировать модуль, потому что изменились флаги.
Если вы устанавливаете параметры языка в файле .ghci , рекомендуется использовать :seti вместо :set, если только вы не хотите применить их ко всем модулям, которые вы загружаете в GHCi.
Два набора параметров можно просмотреть, используя команды :set и :seti соответственно без аргументов. Например, в чистой сессии GHCi мы можем увидеть что-то вроде этого:
Prelude> :seti base language is: Haskell2010 with the following modifiers: -XNoMonomorphismRestriction -XNoDatatypeContexts -XNondecreasingIndentation -XExtendedDefaultRules GHCi-specific dynamic flag settings: other dynamic, non-language, flag settings: -fimplicit-import-qualified warning settings:
Два набора параметров инициализируются следующим образом. Во-первых, оба набора параметров инициализируются как описано в Файлы .ghci и .haskeline. Затем интерактивные параметры изменяются следующим образом:
- Параметр
-XExtendedDefaultRulesвключён, чтобы применить специальные правила установки по умолчанию к выражениям, набранным в командной строке (см. Установка типов по умолчанию в GHCi). - Ограничение мономорфизма отключено (см. Отключение ненавистного ограничения мономорфизма).
5.9. Файлы .ghci и .haskeline
5.9.1. Файлы .ghci
При запуске, если не указан флаг -ignore-dot-ghci, GHCi читает и выполняет команды из следующих файлов в указанном порядке, если они существуют:
-
ghcappdata/ghci.conf, где ⟨ghcappdata⟩ зависит от вашей системы, но обычно представляет собой что-то вроде$HOME/.ghcв Unix илиC:/Documents and Settings/user/Application Data/ghcв Windows. $HOME/.ghci./.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 в оболочке — что должно произойти? Текущий алгоритм, который мы используем для завершения команд, следующий:
- Сначала ищем точное совпадение имени среди определённых макросов.
- Ищем точное совпадение имени в списке встроенных команд.
- Выполняем поиск по префиксу в списке встроенных команд — если совпадает встроенная команда, но макрос определён с тем же именем, что и встроенная команда, выбираем макрос.
- Выполняем поиск по префиксу в списке встроенных команд.
- Выполняем поиск по префиксу в списке определённых макросов.
Вот несколько примеров:
-
У вас есть макрос
:timeи вы вводите:t 3Получаете
:type 3 -
У вас есть макрос
:typeи вы вводите:t 3Получаете
:type 3с вашим определённым макросом, а не со встроенной командой. -
У вас есть макрос
:timeи макрос:type, и вы вводите:t 3Получаете
:type 3с вашим определённым макросом.
Для приоритета встроенных команд можно использовать :: ⟨builtin-command⟩, например, ::type 3.
5.9.2. Файл .haskeline
GHCi использует Haskeline в качестве внутренней реализации. Вы можете настроить его, среди прочего, для удаления дубликатов из истории GHCi. См.: Настройки пользователей Haskeline.
5.10. Компиляция в объектный код внутри GHCi
По умолчанию GHCi компилирует исходный код Haskell в байт-код, который интерпретируется системой выполнения. GHCi также может компилировать код Haskell в объектный код: для включения этой функции используйте флаг -fobject-code в командной строке или с помощью :set (опция -fbyte-code возвращает компиляцию байт-кода). Компиляция в объектный код занимает больше времени, но, как правило, код будет выполняться в 10-20 раз быстрее, чем байт-код.
Компиляция в объектный код внутри GHCi особенно полезна, если вы разрабатываете приложение, поскольку команда :reload обычно выполняется гораздо быстрее, чем перезапуск GHC с --make из командной строки, так как все файлы интерфейса уже кэшированы в памяти.
Существуют недостатки компиляции в объектный код: например, нельзя установить точки останова в объектных модулях. В GHCi будут видны только экспорты модуля объектного кода, а не все верхнеуровневые определения, как в интерпретируемых модулях.
5.11. Запуск интерпретатора в отдельном процессе
Обычно GHCi выполняет интерпретируемый код в том же процессе, что и сам GHC, поверх той же RTS и с общей кучей. Однако, если задан флаг -fexternal-interpreter, GHC запустит отдельный процесс для выполнения интерпретируемого кода и будет взаимодействовать с ним с помощью сообщений через канал.
-
-fexternal-interpreter -
- Since
-
8.0.1
Выполнять интерпретируемый код (для GHCi, Template Haskell, квазицитирования или аннотаций) в отдельном процессе. Интерпретатор будет выполняться в режиме профилирования, если активен
-prof, и в режиме динамической компоновки, если активен-dynamic.Есть несколько замечаний, которые, надеюсь, будут устранены в будущем: эта опция в настоящее время не реализована в Windows (она является бесполезной операцией), и внешний интерпретатор не поддерживает отладчик GHCi, поэтому точки останова и пошаговое выполнение не работают с
-fexternal-interpreter.См. также флаги
-pgmi ⟨cmd⟩(Замена программы для одной или нескольких фаз) и-opti ⟨option⟩(Принудительное применение опций к определённой фазе).
Почему это может быть полезно? Основная причина в том, что RTS, выполняющий интерпретируемый код, может быть другого типа (профилирования или динамической компоновки) по сравнению с самим GHC. Например:
- Мы можем использовать профайлер для сбора трассировок стека при использовании GHCi (см. Трассировки стека в GHCi).
- При компиляции кода Template Haskell с
-prof, нам не нужно сначала компилировать модули без-prof(см. Использование Template Haskell с профилированием), поскольку мы можем выполнить профилируемый объектный код в интерпретаторе.
Эта функция экспериментальна в GHC 8.0.x, но может стать стандартной в будущих выпусках.
5.12. Запуск интерпретатора на другом хосте
При использовании флага -fexternal-interpreter GHC запустит и будет взаимодействовать с отдельным процессом с помощью каналов. В некоторых сценариях (например, при кросс-компиляции) предпочтительно использовать сетевое взаимодействие. GHC предоставляет два инструмента для этого, которые можно найти в каталоге utils.
-
remote-iservнеобходимо скомпилировать с кросс-компилятором, чтобы его можно было выполнить на удалённом хосте. Или, если его использовать на том же хосте, то и компилятор stage2 справится. -
iserv-proxyнеобходимо скомпилировать на машине сборки с помощью компилятора сборки.
После запуска remote-iserv ⟨tmp_dir⟩ ⟨port⟩ на целевом хосте и предоставления временной папки (куда он скопирует необходимые библиотеки для загрузки) и порта, он будет ожидать подключения прокси.
Предоставление -pgmi /path/to/iserv-proxy, -pgmo ⟨option⟩ и -pgmo ⟨port⟩ в дополнение к -fexternal-interpreter затем заставит ghc использовать прокси.
Использование этого способа имеет некоторые ограничения. Ввод-вывод файлов и процессов будет выполняться на целевом компьютере. Таким образом, такие пакеты, как git-embed, file-embed и другие, могут вести себя непредсказуемо, если целевой компьютер и компьютер пользователя не используют одну и ту же файловую систему.
5.13. ЧАВО и моменты, на которые следует обратить внимание
- Интерпретатор не может загрузить модули с объявлениями внешнего экспорта!
-
К сожалению, нет. Мы ещё не реализовали эту функцию. Пожалуйста, вручную скомпилируйте любые проблемные модули перед загрузкой их в GHCi.
-O не работает с GHCi!
По техническим причинам, компилятор байткода не хорошо взаимодействует с одним из этапов оптимизации, поэтому оптимизация отключена при использовании интерпретатора. Это не такая большая потеря: вы получите гораздо больший выигрыш, скомпилировав части вашего кода, которые должны работать быстро, вместо их интерпретации с включенной оптимизацией.
Модули, использующие необрамлённые кортежи или суммы, автоматически включат -fobject-code
Интерпретатор байткода не поддерживает большинство применений необрамлённых кортежей или сумм, поэтому GHCi автоматически скомпилирует эти модули и все модули, от которых они зависят, в объектный код вместо байткода.
GHCi проверяет наличие необрамлённых кортежей и сумм достаточно консервативным способом: он просто проверяет, включает ли модуль языковые расширения UnboxedTuples или UnboxedSums. Не всегда код, включающий UnboxedTuples или UnboxedSums, требует -fobject-code, поэтому, если вы действительно хотите скомпилировать код, использующий UnboxedTuples/UnboxedSums, в байткод, вы можете сделать это явно, включив флаг -fbyte-code. Если вы это сделаете, имейте в виду, что интерпретатор байткода выдаст ошибку, если он столкнётся с кодом, связанным с необрамлёнными кортежами/суммами, который он не может обработать.
Кстати, предыдущий пункт, что -O несовместим с GHCi, связан с тем, что компилятор байткода не может обрабатывать необрамлённые кортежи или суммы.
- Конкурентные потоки не продолжают выполняться, когда 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). - Я не могу использовать Control-C для прерывания вычислений в GHCi на Windows.
- Режим буферизации по умолчанию отличается в GHCi и GHC.
-
В GHC обработчик stdout по умолчанию имеет построчную буферизацию. Однако в GHCi мы отключаем буферизацию stdout, так как это обычно то, что вам нужно в интерпретаторе: вывод появляется по мере его генерации.
Если вы хотите поведение с построчной буферизацией, как в GHC, вы можете запустить свою программу так:
main = do { hSetBuffering stdout LineBuffering; ... }
-
5 -
Обратите внимание, что пакеты содержат только скомпилированный код, поэтому отладка пакета требует поиска исходного кода и прямой загрузки его.
-
6 -
Изначально мы предоставляли связывание для всех переменных в области видимости, а не только для свободных переменных выражения, но обнаружили, что это значительно влияло на производительность, поэтому текущее ограничение только на свободные переменные.
© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/8.10.2/docs/html/users_guide/ghci.html