Как взломать GHCi (или Hugs), чтобы он печатал символы Unicode без экранирования? - PullRequest
23 голосов
/ 04 апреля 2011

Посмотрите на проблему: как правило, в интерактивной среде Haskell нелатинские символы Юникода (которые составляют часть результатов) печатаются экранированными, даже если локаль допускает такие символы (в отличие от прямого вывода через putStrLn, putChar, который выглядит хорошо и читабельно) - примеры показывают GHCi и Hugs98:

$ ghci
GHCi, version 7.0.1: http://www.haskell.org/ghc/  :? for help
Prelude> "hello: привет"
"hello: \1087\1088\1080\1074\1077\1090"
Prelude> 'Я'
'\1071'
Prelude> putStrLn "hello: привет"
hello: привет
Prelude> :q
Leaving GHCi.
$ hugs -98
__   __ __  __  ____   ___      _________________________________________
||   || ||  || ||  || ||__      Hugs 98: Based on the Haskell 98 standard
||___|| ||__|| ||__||  __||     Copyright (c) 1994-2005
||---||         ___||           World Wide Web: http://haskell.org/hugs
||   ||                         Bugs: http://hackage.haskell.org/trac/hugs
||   || Version: September 2006 _________________________________________

Hugs mode: Restart with command line option +98 for Haskell 98 mode

Type :? for help
Hugs> "hello: привет"
"hello: \1087\1088\1080\1074\1077\1090"
Hugs> 'Я'
'\1071'
Hugs> putStrLn "hello: привет"
hello: привет

Hugs> :q
[Leaving Hugs]
$ locale
LANG=ru_RU.UTF-8
LC_CTYPE="ru_RU.UTF-8"
LC_NUMERIC="ru_RU.UTF-8"
LC_TIME="ru_RU.UTF-8"
LC_COLLATE="ru_RU.UTF-8"
LC_MONETARY="ru_RU.UTF-8"
LC_MESSAGES="ru_RU.UTF-8"
LC_PAPER="ru_RU.UTF-8"
LC_NAME="ru_RU.UTF-8"
LC_ADDRESS="ru_RU.UTF-8"
LC_TELEPHONE="ru_RU.UTF-8"
LC_MEASUREMENT="ru_RU.UTF-8"
LC_IDENTIFICATION="ru_RU.UTF-8"
LC_ALL=
$ 

Мы можем догадаться, что это потому, что print и show используются для форматирования результата, и эти функции делают все возможное для форматирования данных каноническим, максимально переносимым способом - поэтому они предпочитают экранировать странные символы (возможно, это даже прописано в стандарте для Haskell):

$ ghci
GHCi, version 7.0.1: http://www.haskell.org/ghc/  :? for help
Prelude> show 'Я'
"'\\1071'"
Prelude> :q
Leaving GHCi.
$ hugs -98
Type :? for help
Hugs> show 'Я'
"'\\1071'"
Hugs> :q
[Leaving Hugs]
$ 

Но все же было бы неплохо, если бы мы знали, как взломать GHCi или Hugs, чтобы напечатать эти символы довольно понятным для человека способом, то есть напрямую, без экранирования.Это может быть оценено при использовании интерактивной среды Haskell в образовательных целях для обучения / демонстрации Haskell перед неанглоязычной аудиторией, которой вы хотите показать некоторые Haskell на данных на их родном языке.

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

Подводя итог моему вопросу: Какие есть способы взломать существующие интерактивные среды Haskell для более удобной печати Unicode в результатах?(«Friendlier» означает даже «проще» в моем случае: я бы хотел, чтобы print в GHCi или Hugs показывал нелатинские символы простым прямым способом, как это делают putChar, putStrLn, то есть без экранирования.)

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

Ответы [ 7 ]

19 голосов
/ 11 апреля 2011

Один из способов взломать это - обернуть GHCi в оболочку оболочки, которая читает его стандартный вывод и удаляет символы Unicode.Конечно, это не способ Haskell, но он выполняет свою работу:)

Например, это оболочка ghci-esc, которая использует sh и python3 (здесь важно 3):

#!/bin/sh

ghci "$@" | python3 -c '
import sys
import re

def tr(match):
    s = match.group(1)
    try:
        return chr(int(s))
    except ValueError:
        return s

for line in sys.stdin:
    sys.stdout.write(re.sub(r"\\([0-9]{4})", tr, line))
'

Использование ghci-esc:

$ ./ghci-esc
GHCi, version 7.0.2: http://www.haskell.org/ghc/  :? for help
> "hello"
"hello"
> "привет"
"привет"
> 'Я'
'Я'
> show 'Я'
"'\Я'"
> :q
Leaving GHCi.

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

11 голосов
/ 29 октября 2012

Был достигнут некоторый прогресс в этой проблеме; спасибо Бравиту (Виталию Брагилевскому)!

Возможно, включено в GHC 7.6.1. (Это? ..)

Как заставить его печатать кириллицу сейчас :

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

module UPPrinter where
import System.IO
import Text.PrettyPrint.Leijen

upprint a = (hPutDoc stdout . pretty) a >> putStrLn ""

И запустить ghci таким образом: ghci -interactive-print=UPPrinter.upprint UPPrinter

Конечно, это можно записать раз и навсегда в .ghci.

Практическая задача: придумать альтернативный вариант Show

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

Использование чужой работы: другие красивые принтеры

Выше предлагается Text.PrettyPrint.Leijen, вероятно, потому что известно, что такие символы не экранируются в строках.

Наш собственный Шоу, основанный на Шоу - привлекательный, но не практичный

Как насчет написания нашего Show, скажем, ShowGhci, как было предложено в ответе здесь. Это практично? ..

Чтобы сохранить работу по определению экземпляров для альтернативного класса Show (например, ShowGhci), можно поддаться искушению использовать существующие экземпляры Show по умолчанию, только переопределите экземпляр для String и Char. Но это не сработает, потому что если вы используете showGhci = show, то для любых сложных данных, содержащих строки, show «сложно компилируется», чтобы вызвать старый show, чтобы показать строку. В этой ситуации требуется возможность передавать разные словари, реализующие один и тот же интерфейс класса, в функции, которые используют этот интерфейс (show передаст его до значения show s). Какие-нибудь расширения GHC для этого?

Основываясь на Show и желании переопределить только экземпляры для Char и String не очень практично, если вы хотите, чтобы оно было "универсальным" (широко применимым), как Show.

Повторный разбор show

Более практичное (и короткое) решение заключается в другом ответе: проанализируйте выходные данные из show, чтобы обнаружить символы и строки, и переформатируйте их. (Хотя семантически кажется немного уродливым, решение в большинстве случаев короткое и безопасное (если в show нет кавычек, используемых для других целей; не должно быть в случае стандартных вещей, потому что идея show состоит в том, чтобы будь более-менее правильным парсером Haskell.)

Семантические типы в ваших программах

И еще одно замечание.

На самом деле, если мы заботимся об отладке в GHCi (а не просто демонстрируем Haskell и хотим получить симпатичный вывод), необходимость показывать не-ASCII буквы должна исходить из некоторого присущего этим символам в вашей программе (в противном случае, для отладки вы можете заменить их латинскими символами или не заботиться о том, чтобы вам показывали коды). Другими словами, есть некоторые MEANING в этих символах или строках с точки зрения проблемной области. (Например, недавно я занимался грамматическим анализом русского языка, и русские слова в качестве части словаря-примера «присутствовали» в моей программе. Его работа имела смысл только с этими конкретными словами. Поэтому мне нужно было прочитайте их при отладке.)

Но посмотрите, если строки имеют некоторое ЗНАЧЕНИЕ, они больше не являются простыми строками; это данные значимого типа. Возможно, программа станет еще лучше и безопаснее, если вы объявите специальный тип для таких значений.

А потом, ура! Вы просто определяете свой экземпляр Show для этого типа. И вы в порядке с отладкой вашей программы в GHCi.

Как пример, в моей программеДля грамматического анализа я сделал:

newtype Vocable = Vocable2 { ortho :: String } deriving (Eq,Ord)
instance IsString Vocable -- to simplify typing the values (with OverloadedStrings)
    where fromString = Vocable2 . fromString

и

newtype Lexeme = Lexeme2 { lemma :: String } deriving (Eq,Ord)
instance IsString Lexeme -- to simplify typing the values (with OverloadedStrings)
    where fromString = Lexeme2 . fromString

(дополнительный fromString здесь, потому что я мог бы переключить внутреннее представление с String на ByteString или что-то еще)

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

9 голосов
/ 22 января 2013

Все изменится в следующей версии 7.6.1 Ghci, так как она предоставляет новую опцию Ghci под названием: -interactive-print.Здесь скопировано из ghc-manual: (И я написал myShow и myPrint следующим образом)

2.4.8. Using a custom interactive printing function

[New in version 7.6.1] By default, GHCi prints the result of expressions typed at the prompt using the function System.IO.print. Its type signature is Show a => a -> IO (), and it works by converting the value to String using show.

This is not ideal in certain cases, like when the output is long, or contains strings with non-ascii characters.

The -interactive-print flag allows to specify any function of type C a => a -> IO (), for some constraint C, as the function for printing evaluated expressions. The function can reside in any loaded module or any registered package.

As an example, suppose we have following special printing module:

     module SpecPrinter where
     import System.IO

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

The sprint function adds an exclamation mark at the end of any printed value. Running GHCi with the command:

     ghci -interactive-print=SpecPrinter.sprinter SpecPrinter

will start an interactive session where values with be printed using sprint:

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

A custom pretty printing function can be used, for example, to format tree-like and nested structures in a more readable way.

The -interactive-print flag can also be used when running GHC in -e mode:

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


module MyPrint (myPrint, myShow) where
-- preparing for the 7.6.1
myPrint :: Show a => a -> IO ()
myPrint = putStrLn . myShow

myShow :: Show a => a -> String
myShow x = con (show x) where
  con :: String -> String
  con [] = []
  con li@(x:xs) | x == '\"' = '\"':str++"\""++(con rest)
                | x == '\'' = '\'':char:'\'':(con rest')
                | otherwise = x:con xs where
                  (str,rest):_ = reads li
                  (char,rest'):_ = reads li

И они хорошо работают:

*MyPrint> myPrint "asf萨芬速读法"
"asf萨芬速读法"
*MyPrint> myPrint "asdffasdfd"
"asdffasdfd"
*MyPrint> myPrint "asdffa撒旦发"
"asdffa撒旦发"
*MyPrint> myPrint '此'
'此'
*MyPrint> myShow '此'
"'\27492'"
*MyPrint> myPrint '此'
'此'
7 голосов
/ 11 апреля 2011

Вариант 1 (неверно):

Изменить эту строку кода:

https://github.com/ghc/packages-base/blob/ba98712/GHC/Show.lhs#L356

showLitChar c s | c > '\DEL' =  showChar '\\' (protectEsc isDec (shows (ord c)) s)

И перекомпилировать ghc.

Опция2 (много работы):

Когда тип GHCi проверяет проанализированный оператор, он заканчивается в tcRnStmt, который опирается на mkPlan (оба в https://github.com/ghc/ghc/blob/master/compiler/typecheck/TcRnDriver.lhs).). Эта попытка проверить тип нескольких вариантовоператор, который был введен в том числе:

let it = expr in print it >> return [coerce HVal it]

В частности:

print_it  = L loc $ ExprStmt (nlHsApp (nlHsVar printName) (nlHsVar fresh_it))
                                      (HsVar thenIOName) placeHolderType

Все, что может потребоваться изменить здесь, это printName (который связывается с System.IO.print). Если этовместо этого он привязан к чему-то вроде printGhci, который был реализован следующим образом:

class ShowGhci a where
    showGhci :: a -> String
    ...

-- Bunch of instances?

instance ShowGhci Char where
    ...  -- The instance we want to be different.

printGhci :: ShowGhci a => a -> IO ()
printGhci = putStrLn . showGhci

Ghci может затем изменить то, что печатается, привлекая различные экземпляры в контекст.

4 голосов
/ 04 февраля 2016

Теперь, когда я знаю -interactive-print от ghci, это отличная функция.Большое спасибо за написание вопроса и ответов!Кстати, у существующих симпатичных принтеров, которые я могу найти в Интернете , есть несколько угловых случаев , и проблема написания хорошего Unicode show оказалась более сложной, чем кажется.

Поэтому для этой цели я решил написать пакет Haskell unicode-show , который (будем надеяться) хорошо печатает угловые строки и составные типы .

С наилучшими пожеланиями, чтобыЭтот пакет полезен для людей, которые искали этот Q & A:)

4 голосов
/ 11 апреля 2011

Вы можете переключиться на использование пакета 'text' для ввода-вывода.Например,

Prelude> :set -XOverloadedStrings
Prelude> Data.Text.IO.putStrLn "hello: привет"
hello: привет

Пакет является частью стандартного дистрибутива Haskell, t he Haskell Platform , и обеспечивает эффективный упакованный неизменяемый текстовый тип Unicode с операциями ввода-вывода.Поддерживается множество кодировок .

Используя файл .ghci, вы можете установить -XOverloadStrings включенным по умолчанию и написать макрос :def, чтобы ввести команду :text, которая показываетзначение только через text.Это будет работать.

3 голосов
/ 11 апреля 2011

Что было бы идеально, так это патч для ghci, позволяющий пользователю :set использовать функцию для отображения результатов, отличных от show. В настоящее время такой функции не существует. Однако предложение Дона для макроса :def (с текстовым пакетом или без него) совсем не плохо.

...