haskell 如何破解GHCi(或拥抱),使其打印Unicode字符未转义?

3b6akqbq  于 12个月前  发布在  其他
关注(0)|答案(8)|浏览(99)

看看这个问题:通常,在交互式Haskell环境中,非拉丁文Unicode字符(构成结果的一部分)被转义打印,即使区域设置允许这样的字符(而不是通过putStrLnputChar直接输出,这看起来很好可读)-示例显示了GHCi和Hugs 98:

$ 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=
$

字符串
我们可以猜测,这是因为printshow被用来格式化结果,这些函数尽可能以规范的、最大限度的可移植方式格式化数据--所以它们更喜欢转义奇怪的字符(也许,它甚至在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?(在我的情况下,“更友好”意味着更“简单”:我希望GHCI或Hugs中的print能够像putCharputStrLn那样简单直接地显示非拉丁字符,即未转义。

(也许,除了GHCi和Hugs 98之外,我还将研究一下现有的Emacs与Haskell交互的模式,看看它们是否可以以漂亮的、非转义的方式呈现结果。

js81xvg6

js81xvg61#

一种破解方法是将GHCi Package 到一个shell Package 器中,该 Package 器读取其标准输出并取消转义Unicode字符。当然,这不是Haskell的方法,但它可以完成这项工作:)
例如,这是一个使用shpython3的 Package 器ghci-esc(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.


请注意,并不是上面所有的unescaping都正确完成了,但这是一种向观众显示Unicode输出的快速方法。

wkftcu5l

wkftcu5l2#

这个问题已经取得了一些进展;感谢bravit(Vitaly Bragilevsky)!:

可能已纳入GHC 7.6.1。(是吗?..)
如何让它现在打印西里尔文:
传递给GHCi的参数应该是一个可以打印西里尔字母的函数。在Hackage上没有找到这样的函数。所以,我们必须创建一个简单的 Package 器,就像现在一样:

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

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

字符串
运行ghcighci -interactive-print=UPPrinter.upprint UPPrinter
当然,这可以一次性地写进.ghci中。

实际问题:提出一个替代的nice Show

所以,现在有一个实际问题:用什么来代替标准Show(标准Show违背我们的意愿,避免了想要的符号)?

使用他人的作品:其他漂亮的打印机

上面建议使用Text.PrettyPrint.Leijen,可能是因为已知字符串中不转义这样的符号。

基于Show的自有Show --吸引人,但不实用

我们自己写Show怎么样,比如说,ShowGhci,就像这里的回答中建议的那样。它实用吗?
保存为替代Show类定义示例的工作(像ShowGhci),人们可能会试图默认使用Show的现有示例,只为StringChar重新定义示例。但这行不通,因为如果使用showGhci = show,然后对于任何包含字符串的复杂数据show都是“硬编译”的,以调用旧的show来显示字符串。这种情况要求能够将实现相同类接口的不同字典传递给使用此接口的函数(show会把它传递给子show s)。对此有任何GHC扩展吗?
基于Show并只想重新定义CharString的示例是不太实际的,如果你想让它像Show一样“通用”(广泛适用)的话。

重新解析show

一个更实际(简短的)解决方案是这里的另一个答案:解析show的输出以检测字符和字符串,并重新格式化它们。(虽然在语义上看起来有点丑陋,但在大多数情况下,解决方案是简短和安全的(如果show中没有用于其他目的的引号;对于标准的东西来说肯定不是这样,因为show的想法是或多或少正确地解析Haskell。

程序中的语义类型

还有一句话
实际上,如果我们关心GHCi中的调试,(而不是简单地演示Haskell并希望有一个漂亮的输出),显示非ASCII字母的需要必须来自这些字符在程序中的某些固有存在(否则,为了调试,你可以用拉丁字符代替它们,或者不太关心是否显示代码)。换句话说,从问题域的Angular 来看,这些字符或字符串中有一些含义。(例如,我最近一直在从事俄语的语法分析,而作为示例词典一部分的俄语单词是“固有的”,它的工作只有用这些特定的词才有意义。所以我需要在调试时阅读它们。)
但是看,如果字符串有一些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


(the这里额外的fromString是因为我可能会将内部表示从String切换到ByteString或其他)
除了能够很好地show它们之外,我更安全了,因为在编写代码时,我不能混合使用不同类型的单词。

fnatzsnv

fnatzsnv3#

在Ghci的下一个版本7.6.1中,事情会有所改变,因为它提供了一个新的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 '此'
'此'

83qze16e

83qze16e4#

选项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依赖于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可以通过将不同的示例引入上下文来改变打印的内容。

btqmn9zl

btqmn9zl5#

您可以切换到使用'text'包进行IO。

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

字符串
这个包是标准Haskell发行版t he Haskell Platform的一部分,它提供了一个高效的打包的、不可变的Unicode文本类型和IO操作。
使用一个.ghci文件,你可以将-XOverloadStrings设置为默认打开,然后编写一个:def宏来引入一个:text命令,该命令只通过text显示一个值。

8mmmxcuj

8mmmxcuj6#

现在我知道ghci的-interactive-print,这是一个很好的功能。非常感谢你写的问题和答案!顺便说一下,现有的漂亮的打印机,我可以在网上找到have some corner cases,和写好的Unicode show的问题原来比它看起来更复杂。
因此,我决定为此编写一个Haskell包unicode-show,即(希望)prints cornercase strings and compound types well
最好的祝愿,这个包是有用的人谁搜索这个Q&A:)

ozxc1zmp

ozxc1zmp7#

理想的情况是ghci的补丁允许用户:set一个函数来显示show以外的结果。目前还没有这样的功能存在。然而,Don对:def宏的建议(带或不带文本包)一点也不坏。

sf6xfgos

sf6xfgos8#

一个可能的好解决方案是:
1.安装pretty-simple,例如使用cabal

cabal install --lib pretty-simple

字符串
1.添加到~/.ghci

import qualified Text.Pretty.Simple
:set -interactive-print=Text.Pretty.Simple.pPrint


pretty-simple库在打印各种类型的数据时提供了额外的好处。

相关问题