在Windows下,让控制台I/O在Haskell中使用Unicode字符似乎相当困难。下面是一个悲哀的故事:
- 在考虑在Windows下的控制台中执行Unicode I/O之前,您需要确保使用的控制台字体可以呈现所需的字符。光栅字体(默认)具有无限差的覆盖率(并且不允许复制粘贴它们不能表示的字符),以及MS提供的TrueType选项(Consolas,Lucida Console)的覆盖范围不太大(尽管这些将允许复制粘贴它们不能表示的字符)。您可以考虑安装DejaVu Sans Mono(按照这里底部的说明;你可能需要重新启动才能正常工作)。在这之前,没有应用程序能够进行大量的Unicode I/O;不仅仅是Haskell。
1.完成这些之后,你会注意到一些应用程序将能够在Windows下进行控制台I/O。但是让它工作仍然相当复杂。基本上有两种方法可以在Windows下写入控制台。(以下是适用于任何语言的,不仅仅是Haskell;不要担心,Haskell将在一点上进入画面!).
1.选项A是使用通常的C库风格的基于字节的i/o函数;希望操作系统将根据某种编码来解释这些字节,这种编码可以编码所有你想要的奇怪和奇妙的字符。例如,在Mac OS X上使用等效的技术,其中标准系统编码通常是UTF8,这工作得很好;你发送UTF8输出,你会看到漂亮的符号。
1.在Windows上,它的效果不太好。Windows期望的默认编码通常不会是覆盖所有Unicode符号的编码。所以如果你想以这种方式看到漂亮的符号,无论如何,你需要 * 更改 * 编码。一种可能性是让你的程序使用SetConsoleCP
win32命令。(因此,您需要绑定到Win32库。)或者,如果您不愿意这样做,您可以期望程序的用户为您更改代码页(然后他们必须在运行程序之前调用chcp
命令)。
1.选项B是使用支持Unicode的win32控制台API命令,如WriteConsoleW
。在这里,您将UTF 16直接发送到Windows,Windows会很高兴地呈现它:没有编码不匹配的危险,因为Windows * 总是 * 期望这些函数使用UTF 16。
不幸的是,这两个选项在Haskell中都不能很好地工作。首先,据我所知,没有库使用选项B,所以这不是很容易。这就剩下选项A。如果你使用Haskell的I/O库,(putStrLn
等等),这就是库会做的事情。在现代版本的Haskell中,它会仔细询问Windows当前的代码页是什么,并以正确的编码输出字符串。这种方法有两个问题:
- 一个不是一个showstopper,但它是恼人的。如上所述,默认编码几乎永远不会编码你想要的字符:您需要用户更改为这样的编码。因此,您的用户在运行您的程序之前需要
chcp cp65001
(你可能会觉得强迫你的用户这样做是令人反感的)。或者,你需要绑定到SetConsoleCP
,并在程序中执行等效操作。(然后使用hSetEncoding
,以便Haskell库将使用新编码发送输出),这意味着你需要 Package win32库的相关部分,使它们成为Haskell可见的。 - 更严重的是,有一个bug in Windows(分辨率:不会修复),这会导致bug in Haskell,这意味着如果您选择了任何可以覆盖所有Unicode的代码页(如cp 65001),Haskell的I/O例程将发生故障并失败。(或者你的用户)将编码正确地设置为覆盖所有美妙的Unicode字符的编码,然后在告诉Haskell使用该编码输出内容时“做好一切”,你仍然会输。
上面列出的bug仍然没有得到解决,并被列为低优先级;基本结论是选项A(在我上面的分类中)是不可行的,需要切换到选项B才能获得可靠的结果。目前还不清楚解决这个问题的时间表,因为它看起来像是一些相当大的工作。
问题是:与此同时,有人能提出一个解决方案,允许在Windows下的Haskell中使用Unicode控制台I/O吗?
另请参阅这个Python bug tracker database entry,在Python 3中解决相同的问题(已提出修复,但尚未被接受到代码库中),以及这个Stack Overflow答案,为Python中的这个问题提供了一个解决方案(基于我的分类中的“选项B”)。
1条答案
按热度按时间klr1opcd1#
我想我会回答我自己的问题,并列出 * 一个可能的 * 答案,以下是我目前实际上正在做的事情。很有可能一个人可以做得更好,这就是为什么我问这个问题!但我认为将以下内容提供给人们是有意义的。它基本上是从Python到Haskell的翻译。它使用问题中提到的“选项B”。
基本思想是创建一个模块IOUtil.hs,包含以下内容,您可以将其
import
到代码中:字符串
然后,您使用其中包含的I/O函数而不是标准库的I/O函数。它们将检测输出是否被重定向;如果不是(即,如果我们正在写入“真实的”控制台),那么我们将绕过通常的Haskell I/O函数,并使用
WriteConsoleW
(支持Unicode的win32控制台函数)直接写入win32控制台。在非Windows平台上,条件编译意味着这里的函数只调用标准库的函数。如果你需要打印到标准错误,你应该使用(例如)
ePutStrLn
,而不是hPutStrLn stderr
;我们不定义hPutStrLn
。(定义一个是读者的练习!)