imz*_*hev 24 unicode formatting locale haskell ghci
看待这个问题:通常情况下,在交互式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=
$
Run Code Online (Sandbox Code Playgroud)
我们可以猜测,这是因为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]
$
Run Code Online (Sandbox Code Playgroud)
但是,如果我们知道如何以非常人性化的方式(即直接,未转义)破解GHCi或Hugs来打印这些角色,那将会很好.在教育目的中使用交互式Haskell环境时,可以理解这一点,在非英语读者面前进行Haskell的教程/演示,您希望在他们的人类语言中显示一些Haskell数据.
实际上,它不仅对教育目的有用,而且对调试也有用!当您具有在表示其他语言的字符串的字符串上定义的函数时,使用非ASCII字符.因此,如果程序是特定于语言的,并且只有另一种语言的单词作为数据有意义,并且您具有仅在这些单词上定义的函数,则在GHCi中调试以查看此数据非常重要.
总结我的问题:有什么方法可以破解现有的交互式Haskell环境,以便在结果中更友好地打印Unicode?("更友好"是指在我的情况下,即使"简单":我想print
在GHCI或拥抱以示非拉丁字符的简单直接的方式所做putChar
,putStrLn
即转义.)
(也许除了GHCi和Hugs98之外,我还将看看现有的Emacs模式,以便与Haskell进行交互,看看他们是否可以以漂亮的非转义方式呈现结果.)
And*_*ikh 19
破解这种方法的一种方法是将GHCi包装到shell包装器中,该包装器读取其stdout和unescapes 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))
'
Run Code Online (Sandbox Code Playgroud)
用法ghci-esc
:
$ ./ghci-esc
GHCi, version 7.0.2: http://www.haskell.org/ghc/ :? for help
> "hello"
"hello"
> "??????"
"??????"
> '?'
'?'
> show '?'
"'\?'"
> :q
Leaving GHCi.
Run Code Online (Sandbox Code Playgroud)
请注意,并非所有上面的unescaping都能正确完成,但这是向您的受众显示Unicode输出的快速方法.
imz*_*hev 11
这个问题取得了一些进展; 感谢bravit(Vitaly Bragilevsky)!:
可能并入GHC 7.6.1.(是吗?..)
传递给GHCi的参数应该是可以打印西里尔文的函数.在Hackage上找不到这样的功能.所以,我们必须创建一个简单的包装器,就像现在一样:
Run Code Online (Sandbox Code Playgroud)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
就像在这里的答案中所建议的那样.是实用的吗?
为了保存定义替代ShowGhci
类(例如Show
)的实例的工作,可能很想使用ShowGhci
默认情况下的现有实例,只为for Show
和重新定义实例String
.但这不起作用,因为如果你使用Char
,那么对于任何包含字符串的复杂数据showGhci = show
都是"硬编译"来调用old show
来显示字符串.这种情况要求能够将实现相同类接口的不同字典传递给使用此接口的函数(show
将其传递给sub show
).对此有任何GHC扩展吗?
基于show
并希望仅重新定义实例Show
并且Char
不太实用,如果您希望它像"通用"(广泛适用)那样String
.
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
Run Code Online (Sandbox Code Playgroud)
和
newtype Lexeme = Lexeme2 { lemma :: String } deriving (Eq,Ord)
instance IsString Lexeme -- to simplify typing the values (with OverloadedStrings)
where fromString = Lexeme2 . fromString
Run Code Online (Sandbox Code Playgroud)
(Show
这里的额外是因为我可能会将内部表示切换fromString
为String
或等等)
除了能够ByteString
很好地使用它们之外,我变得更安全,因为在编写代码时我无法混合使用不同类型的单词.
Ghci的下一个版本7.6.1将会改变,因为它提供了一个名为-interactive-print的新Ghci选项.这里是从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
Run Code Online (Sandbox Code Playgroud)
他们运作良好:
*MyPrint> myPrint "asf?????"
"asf?????"
*MyPrint> myPrint "asdffasdfd"
"asdffasdfd"
*MyPrint> myPrint "asdffa???"
"asdffa???"
*MyPrint> myPrint '?'
'?'
*MyPrint> myShow '?'
"'\27492'"
*MyPrint> myPrint '?'
'?'
Run Code Online (Sandbox Code Playgroud)
修改这行代码:
https://github.com/ghc/packages-base/blob/ba98712/GHC/Show.lhs#L356
showLitChar c s | c > '\DEL' = showChar '\\' (protectEsc isDec (shows (ord c)) s)
Run Code Online (Sandbox Code Playgroud)
并重新编译ghc.
当GHCi类型检查解析的语句时,它最终tcRnStmt
依赖于 mkPlan
(在https://github.com/ghc/ghc/blob/master/compiler/typecheck/TcRnDriver.lhs中).这会尝试键入检查输入的语句的几个变体,包括:
let it = expr in print it >> return [coerce HVal it]
Run Code Online (Sandbox Code Playgroud)
特别:
print_it = L loc $ ExprStmt (nlHsApp (nlHsVar printName) (nlHsVar fresh_it))
(HsVar thenIOName) placeHolderType
Run Code Online (Sandbox Code Playgroud)
这里可能需要改变的是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
Run Code Online (Sandbox Code Playgroud)
然后,Ghci可以通过将不同的实例带入上下文来改变打印的内容.
您可以切换到使用“text”包进行 IO。例如
\n\nPrelude> :set -XOverloadedStrings\nPrelude> Data.Text.IO.putStrLn "hello: \xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82"\nhello: \xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82\n
Run Code Online (Sandbox Code Playgroud)\n\n该软件包是标准 Haskell 发行版Haskell Platform的一部分,并提供了一种高效的打包、不可变的 Unicode 文本类型以及 IO 操作。支持多种编码。
\n\n使用 .ghci 文件,您可以将 -XOverloadStrings 设置为默认打开,并编写一个:def
宏来引入仅:text
显示值的命令text
。那行得通。