29

查看问题:通常,在交互式 Haskell 环境中,非拉丁 Unicode 字符(构成结果的一部分)会被转义打印,即使语言环境允许此类字符(与通过 直接输出相反putStrLnputChar这看起来很好并且可读)——示例显示 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=
$ 

我们可以猜测这是因为printandshow用于格式化结果,并且这些函数尽最大努力以规范、最大可移植的方式格式化数据——所以他们更喜欢转义奇怪的字符(也许,它甚至被拼写成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,即未转义。)

(也许,除了 GHCi 和 Hugs98 之外,我还会看看现有的 Emacs 模式,用于与 Haskell 交互,看看它们是否可以以漂亮的、未转义的方式呈现结果。)

4

7 回答 7

21

破解此问题的一种方法是将 GHCi 包装到一个 shell 包装器中,该包装器读取其标准输出并取消转义 Unicode 字符。这当然不是 Haskell 的方式,但它可以完成工作:)

例如,这是一个ghci-esc使用shand的包装器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 输出的一种快速方法。

于 2011-04-10T23:16:44.403 回答
11

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

可能并入 GHC 7.6.1。(是吗?..)

如何让它现在打印西里尔文

传递给 GHCi 的参数应该是可以打印 Cyrillic 的函数。在 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(标准——Show违背我们的意愿转义想要的符号)?

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

建议在上面,Text.PrettyPrint.Leijen可能是因为已知不会在字符串中转义此类符号。

我们自己的Show基于Show——有吸引力,但不实用

Show比如说,ShowGhci正如这里的答案所建议的那样,写我们自己的怎么样。实用吗?...

为了节省为替代Show类(如ShowGhci)定义实例的工作,默认情况下可能会尝试使用 的现有实例Show,仅重新定义 和 的String实例Char。但这不起作用,因为如果您使用showGhci = show, 那么对于任何包含字符串的复杂数据show都是“硬编译”的,以调用 oldshow来显示字符串。这种情况要求能够将实现相同类接口的不同字典传递给使用该接口的函数(show将其传递给 sub show)。有任何 GHC 扩展吗?

如果您Show希望CharStringShow.

重新解析show

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

程序中的语义类型

还有一句话。

实际上,如果我们关心 GHCi 中的调试(而不是简单地演示 Haskell 并希望获得漂亮的输出),那么显示非 ASCII 字母的需求必须来自程序中这些字符的固有存在(否则,为了调试,您可以用拉丁字符替换它们,或者不太关心显示代码)。换句话说,从问题域的角度来看,这些字符或字符串是有一定意义的。(例如,我最近从事俄语的语法分析,作为示例词典的一部分的俄语单词“固有地”存在于我的程序中。它的工作仅对这些特定单词有意义。所以我需要调试时阅读它们。)

但是看,如果字符串有一些意义,那么它们就不再是普通的字符串了;它是有意义类型的数据。如果您为这种含义声明一个特殊类型,那么程序可能会变得更好、更安全。

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内容是因为我可能会将内部表示从StringtoByteString或其他切换)

除了能够show很好地使用它们之外,我变得更安全了,因为在编写代码时我无法混合不同类型的单词。

于 2012-10-28T23:00:51.967 回答
10

在 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 '此'
'此'
于 2013-01-22T15:20:57.523 回答
7

选项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 可以通过将不同的实例带入上下文来更改打印的内容。

于 2011-04-11T12:26:41.767 回答
4

您可以切换到使用 IO 的“文本”包。例如

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

该包是标准 Haskell 发行版(即Haskell 平台)的一部分,并提供了一种高效的打包、不可变的 Unicode 文本类型和 IO 操作。支持许多编码

使用 .ghci 文件,您可以将 -XOverloadStrings 默认设置为打开,并编写一个:def宏来引入一个仅:text显示值的命令 via text。那会奏效。

于 2011-04-11T00:20:39.693 回答
4

现在我知道了 ghci's -interactive-print,这是一个很棒的功能。非常感谢您撰写问题和答案!顺便说一句,我可以在网上找到的现有漂亮打印机有一些极端情况,而编写好的 Unicode 的问题show比看起来要复杂得多。

因此,我决定为此目的编写一个 Haskell 包unicode-show,它(希望)能很好地打印角字母字符串和复合类型

最好的祝愿,这个包对搜索这个问答的人有用:)

于 2016-02-04T04:53:49.703 回答
3

理想的是 ghci 的补丁,允许用户使用:set一个函数来显示除show. 目前不存在这样的功能。但是,Don 对:def宏(带有或不带有 text 包)的建议一点也不差。

于 2011-04-11T19:58:26.503 回答