Подтвердить что ты не робот

Как взломать GHCi (или объятия), чтобы он печатал символы Unicode без привязки?

Посмотрите на проблему. Обычно в интерактивной среде Haskell символы латинского Unicode (которые составляют часть результатов) печатаются с экранированием, даже если локаль допускает такие символы (в отличие от прямого вывода через 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 в результатах есть? ( "Дружелюбие" означает даже "более простой" в моем случае: мне бы хотелось, чтобы print в GHCi или Hugs показывал нелатинским символам простой прямой путь, как это делали putChar, putStrLn, т.е. unescaped.)

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

4b9b3361

Ответ 1

Вариант 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 может изменить то, что напечатано, введя различные экземпляры в контекст.

Ответ 2

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

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

#!/bin/sh

ghci "[email protected]" | 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.

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

Ответ 3

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

Вероятно, включен в 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 - привлекательная, но не практичная

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

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

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

Повторный анализ Show

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

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

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

На самом деле, если мы заботимся о том, чтобы отлаживать GHCi (а не просто демонстрировать Haskell и желать иметь хороший вывод), необходимость показывать буквы, отличные от ASCII, должны исходить из какого-то неотъемлемого присутствия этих символов в вашей программе (в противном случае, для отладки вы можете заменить их латинскими символами или не заботиться о том, чтобы показывать коды). Другими словами, в этих символах или строках с точки зрения проблемной области есть СРЕДСТВО. (Например, я недавно занимался грамматическим анализом русского языка, а русские слова как часть примерного словаря были "неотъемлемо" присутствуют в моей программе. Его работа имела бы смысл только с этими конкретными словами. читать их при отладке.)

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

А затем, hooray!, вы просто определяете свой экземпляр 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 их красиво, я стал более безопасным, потому что я не смог бы смешивать разные типы слов при составлении кода.

Ответ 4

В следующей версии 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 [email protected](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 '此'
'此'

Ответ 5

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

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

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

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

Ответ 6

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

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

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

Ответ 7

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