show wrt转义字符的语义

cut*_*lus 4 haskell ghc

考虑以下示例(?>= ghci,$= shell):

?> writeFile "d" $ show "d"
$ cat d
"d"

?> writeFile "d" "d"
$ cat d
d

?> writeFile "backslash" $ show "\\"
$ cat backslash
"\\"

?> writeFile "backslash" "\\"
$ cat backslash
\

?> writeFile "cat" $ show "" -- U+1F408
$ cat cat
"\128008"

?> writeFile "cat" ""
$ cat cat

Run Code Online (Sandbox Code Playgroud)

我理解另一种方式"\128008"只是""在Haskell源代码中表示的另一种方式 .我的问题是:为什么""示例的行为类似于反斜杠而不是像"d"?既然它是一个可打印的角色,它不应该像一个字母吗?

更一般地说,确定字符是显示为可打印字符还是转义码的规则是什么?我查看 了Haskell 2010语言报告中的第6.3节,但没有指定确切的行为.

cut*_*lus 6

TL:DR; ASCII范围(0-127)内的可打印字符将为shown作为图形字符.*其他所有内容都将被转义.

*除了双引号(因为它们用于字符串分隔符)和反斜杠(因为它们需要用于转义).

让我们看一下源代码来解决这个问题吧!

既然我们有String = [Char],我们应该instance Show Char在源头寻找.它可以在这里找到 .它被定义为:

-- | @since 2.01
instance  Show Char  where
    showsPrec _ '\'' = showString "'\\''"
    showsPrec _ c    = showChar '\'' . showLitChar c . showChar '\''

    showList cs = showChar '"' . showLitString cs . showChar '"'
Run Code Online (Sandbox Code Playgroud)

所以显示一个String(使用showList)基本上是一个包装器 ShowLitString,并显示一个Char是一个包装器ShowLitChar.我们来看看这些功能.

showLitString :: String -> ShowS
-- | Same as 'showLitChar', but for strings
-- It converts the string to a string using Haskell escape conventions
-- for non-printable characters. Does not add double-quotes around the
-- whole thing; the caller should do that.
-- The main difference from showLitChar (apart from the fact that the
-- argument is a string not a list) is that we must escape double-quotes
showLitString []         s = s
showLitString ('"' : cs) s = showString "\\\"" (showLitString cs s)
showLitString (c   : cs) s = showLitChar c (showLitString cs s)
   -- [explanatory comments ...]
Run Code Online (Sandbox Code Playgroud)

正如您可能预期的那样,showLitString主要是一个包装器 showLitChar.[注意:如果你不熟悉这个ShowS类型,这是一个很好的 答案,可以理解为什么它可能有用.]不是我们想要的,所以让我们去showLitChar(我省略了定义的部分内容)与问题相关).

-- | Convert a character to a string using only printable characters,
-- using Haskell source-language escape conventions.  For example:
-- [...]
showLitChar                :: Char -> ShowS
showLitChar c s | c > '\DEL' =  showChar '\\' (protectEsc isDec (shows (ord c)) s)
-- ^ Pattern matched for cat
showLitChar '\DEL'         s =  showString "\\DEL" s
showLitChar '\\'           s =  showString "\\\\" s
-- ^ Pattern matched for backslash
showLitChar c s | c >= ' '   =  showChar c s
-- ^ Pattern matched for d
-- Some more escape codes
showLitChar '\a'           s =  showString "\\a" s
-- similarly for '\b', '\f', '\n', '\r', '\t', '\v' etc.
-- showLitChar ... = ...
Run Code Online (Sandbox Code Playgroud)

现在你看到问题所在.ord c是一个int,第一个是所有非ASCII字符(ord '\DEL' == 127).对于ASCII范围内的字符,将打印可打印字符,其余字符将被转义.对于外面的字符,所有字符都被转义.

代码没有回答问题的"为什么"部分.答案(我认为)是我们看到的第一个评论:

-- | @since 2.01
instance  Show Char  where
Run Code Online (Sandbox Code Playgroud)

如果我在猜测,这种行为一直存在以保持向后兼容性.我不需要猜测:请参阅评论以获得一些好的答案.

奖金

我们可以git blame使用GHC的Github镜像在线进行;).让我们看看这段代码的编写时间(责备链接).相关提交是15岁(!).但是,确实提到了Unicode.

Data.Char模块中存在区分不同类型的Unicode字符的功能.看源头:

isPrint    c = iswprint (ord c) /= 0

foreign import ccall unsafe "u_iswprint"
  iswprint :: Int -> Int
Run Code Online (Sandbox Code Playgroud)

如果你跟踪引入的提交iswprint,你将在这里登陆 .这项承诺是在13年前作出的. 也许在这两年里写的代码足够他们不想破解?我不知道.如果一些GHC开发人员能够更多地了解这一点,那就太棒了:).Daniel Wagner和Paul Johnson在评论中指出了一个很好的理由 - 使用非Unicode系统操作必须是高优先级(大约15年前),因为当时Unicode相对较新.

  • "显示"的输出是7位干净,包含在/附加到电子邮件中的一个很好的属性,因为这意味着它不需要额外的一轮转义,很好,因为几乎所有地方都可以使用ASCII,并且很好,因为许多流行的编码同意用于7位干净文本的字节数. (3认同)
  • 15年前与非unicode系统兼容比现在更重要.但是,当您阅读转义字符串时,您还可以看到确切的内容,包括控制字符.这很有用,因为打印时不同的字符串可以看起来相同.转义版本可让您了解实际情况. (3认同)