「Text.Show」Day 2. - 30天Hackage之旅

第二天稼钩,讓我們回歸基礎(chǔ)顾稀,來看看 Text.Show 這個 base 中非常基本的函數(shù)庫坝撑。

初步使用

對 Text.Show 的最基礎(chǔ)使用静秆,莫過于自動推導(dǎo) Show 的實例,可以分別使用 deriving 的兩種形式來完成這個功能:

-- Option 1: deriving 子句

data MyType = ThisType Int | ThatType MyType
  deriving (Show)

-------------------------------------------------------

-- Option 2: 獨立的 deriving 聲明

-- 需要開啟這個擴展巡李,才能使用獨立 deriving
{-# LANGUAGE StandaloneDeriving #-}
data MyType = ThisType Int | ThatType MyType
-- 故意隔開一行抚笔,表示不是子句
deriving instance Show MyType

在 GHCi 中簡單調(diào)用 show 來看看輸出的結(jié)果:

Prelude> let a = ThatType $ ThatType $ ThisType 10
Prelude> print a
-- 輸出結(jié)果為:
ThatType (ThatType (ThisType 10))

可以看到推導(dǎo)出的 MyTypeShow 實例,非常聰明的給我們的數(shù)據(jù)加上了括號和空格击儡,這樣看上去就完全和它的實際意義吻合了塔沃。

初探源碼

讓我們來看一下 Text.Show 內(nèi)部都是怎么實現(xiàn)的,Show 這個 type class 都定義了什么接口方法?

class  Show a  where
    {-# MINIMAL showsPrec | show #-}
    showsPrec :: Int    -- ^ the operator precedence of the enclosing
                        -- context (a number from @0@ to @11@).
                        -- Function application has precedence @10@.
              -> a      -- ^ the value to be converted to a 'String'
              -> ShowS

    -- | A specialised variant of 'showsPrec', using precedence context
    -- zero, and returning an ordinary 'String'.
    show      :: a   -> String
    showList  :: [a] -> ShowS

    showsPrec _ x s = show x ++ s
    show x          = shows x ""
    showList ls   s = showList__ shows ls s

從以上代碼可已看出以下幾點:

  • 有一個叫 Shows 的類型出現(xiàn)了兩次蛀柴,那是什么鬼螃概?
  • Show 這個類型類的最小實現(xiàn)是 show 或者 showsPrec
  • 除此之外的方法,就只有 showList鸽疾,為什么需要 showList 呢吊洼?

Shows 類型

看一下 Shows 類型的定義:

type ShowS = String -> String

這種定義方法十分有趣,我們可以直接把 Shows 看作是字符串類型制肮,只不過兩個字符串的鏈接冒窍,變得不太一樣:

-- 普通字符串
str1 = "foo"
str2 = "bar"
str3 = str1 ++ str2

-- Shows 
str1 = (++) "foo"
str2 = (++) "bar"
str3 = str1 . str2

使用 Shows 最大的好處是,獲得 O(1) 的拼接效率豺鼻,這特別適合像 Show 這樣需要拼接眾多小字符串的場景综液。事實上,像 Shows 這樣表示字符串或數(shù)組的方式儒飒,被稱作 Difference List 谬莹,即把一個數(shù)組表示為 [a] -> [a]。在 Hackage 上還專門有一個庫桩了,實現(xiàn)了一個叫 DList 的類型附帽,來對應(yīng)這個概念,其中包含了一部分非常常用的數(shù)組操作函數(shù)井誉。

show 函數(shù)

Text.Show 中蕉扮,我們平時用的做多的肯定就是 show,而實際上颗圣,默認(rèn)的 show 的定義非常簡單:

show x = showsPrec 0 x ""

其實就是 precedence = 0 的情況下喳钟,最終拼接一個空字符串,獲得返回結(jié)果欠啤。

showsPrec 函數(shù)

我們可以拆開 showsPrec 的名字荚藻,來理解它的存在價值,即 'shows' + 'Prec(edence)' 洁段,也就是說 showsPrec 是一個最終返回 Shows 類型的,會考慮代碼組合優(yōu)先級的函數(shù)共郭。

Shows 的部分已經(jīng)講過了祠丝,現(xiàn)在來看下 Precedence 是干嘛用的。簡單說就是:

  • 大目標(biāo):當(dāng)我們需要輸出一個自定義類型的 toString 字符串除嘹,其實我們是希望能夠一眼看出它在計算機內(nèi)部實際代表的內(nèi)容
  • 小目標(biāo):在輸出一個表達(dá)式時写半,toString 字符串需要能表達(dá)出操作符優(yōu)先級,有必要的話尉咕,加上括號以作區(qū)隔叠蝇。

下面我們以 Text.Show 源代碼中的示例為參考:

infixr 5 :^:
data Tree a =  Leaf a  |  Tree a :^: Tree a

-- the derived instance of 'Show' is equivalent to

instance (Show a) => Show (Tree a) where
        showsPrec d (Leaf m) = showParen (d > app_prec) $
             showString "Leaf " . showsPrec (app_prec+1) m
          where app_prec = 10
        showsPrec d (u :^: v) = showParen (d > up_prec) $
             showsPrec (up_prec+1) u .
             showString " :^: "      .
             showsPrec (up_prec+1) v
          where up_prec = 5

-- Note that right-associativity of @:^:@ is ignored.  For example,
--
-- * @'show' (Leaf 1 :^: Leaf 2 :^: Leaf 3)@ produces the string
--   @\"Leaf 1 :^: (Leaf 2 :^: Leaf 3)\"@.

上面的例子可以看到,這個 Tree a 的 Show 實例定義年缎,就是在轉(zhuǎn)為字符串時悔捶,顯示地把右結(jié)合的那組括號打印出來铃慷,即原來的 Leaf 1 :^: Leaf 2 :^: Leaf 3 輸出后變成了 "Leaf 1 :^: (Leaf 2 :^: Leaf 3)"

  • 第一個 pattern match 匹配了 Leaf m 的情況蜕该,表示只有在當(dāng)前優(yōu)先級大于 10 的情況下犁柜,也就是函數(shù)調(diào)用 (function application) 的優(yōu)先級時,才在 Leaf m 外圍增加括號堂淡。且對于更深一步的內(nèi)容馋缅,采用優(yōu)先級 11 進行處理。

  • 第二個 pattern match 匹配了 Tree a :^: Tree a 的情況绢淀,當(dāng)優(yōu)先級的要求超過 5 (也就是 :^: 所定義的優(yōu)先級) 時萤悴,才需要在外面加上括號。且對于更深一步的內(nèi)容皆的,采用優(yōu)先級 6 進行處理稚疹。

到這里,我們已經(jīng)可以看出祭务, showsPrec 的第一個參數(shù) precedence内狗,其實表達(dá)的就是,需要我內(nèi)部最外層的操作符達(dá)到什么樣的優(yōu)先級义锥,才不需要在外圍加括號柳沙。

到這里,當(dāng)我們再看到 Data.SetShow 實例拌倍,應(yīng)該就會很好理解了:

instance Show a => Show (Set a) where
  showsPrec p xs = showParen (p > 10) $
    showString "fromList " . shows (toList xs)

showList 函數(shù)

眾所周知赂鲤,String 就是 [Char],即內(nèi)部類型為 Char 的數(shù)組柱恤,而打印一個 String 的結(jié)果也和打印其他數(shù)組不同:

Prelude> print [1,2,3]
--- 結(jié)果為:
[1,2,3]

Prelude> print ['a','b','c']
--- 結(jié)果為:
“abc"

其原因是数初, instance Show Char 中定義了特殊的 showList

instance  Show Char  where
    showsPrec _ '\'' = showString "'\\''"
    showsPrec _ c    = showChar '\'' . showLitChar c . showChar '\''

    showList cs = showChar '"' . showLitString cs . showChar '"'

看著很不錯,但這就是為什么一定要有 showList 的原因嗎梗顺?為什么不能直接給 [Char] 定義 Show 實例呢泡孩?類似如下:

instance Show [Char] where
  ... 

其實這里使用了一個 trick,并被國外的某位仁兄定義為 「The “extra-method” trick pattern」寺谤。其初衷就是為了避免 overlapping instances 仑鸥。來看下 Text.Show 源代碼的這部分定義:

instance Show a => Show [a]  where
  showsPrec _         = showList

在有這個通用 Show [a] 存在的情況下,如果還要定義 instance Show [Char]变屁,就會造成 overlapping instances眼俊。當(dāng)然也可以這么繼續(xù)寫,前提是開啟 OverlappingInstances 擴展 (這是一個會造成 instance 小混亂的開關(guān)粟关,不開為妙)疮胖。這時候,trick 就來了,即使把新加一個 showList 方法澎灸,并讓它在 class 中的默認(rèn)實現(xiàn)與 instance Show Char 中的實現(xiàn)不同院塞,實現(xiàn)多態(tài),這是可以的;骱ⅰ迫悠!

這就是產(chǎn)生 showList 的原因。

總結(jié)

可以看到巩梢,看似簡單的 Text.Show 创泄,其背后還是有非常多值得借鑒和理解的知識點的。下一次我們繼續(xù)來看一個基礎(chǔ)模塊 Read ~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末括蝠,一起剝皮案震驚了整個濱河市鞠抑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌忌警,老刑警劉巖搁拙,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異法绵,居然都是意外死亡箕速,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門朋譬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盐茎,“玉大人,你說我怎么就攤上這事徙赢∽帜” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵狡赐,是天一觀的道長窑业。 經(jīng)常有香客問我,道長枕屉,這世上最難降的妖魔是什么常柄? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮搀庶,結(jié)果婚禮上拐纱,老公的妹妹穿的比我還像新娘。我一直安慰自己哥倔,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布揍庄。 她就那樣靜靜地躺著咆蒿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沃测,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天缭黔,我揣著相機與錄音,去河邊找鬼蒂破。 笑死馏谨,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的附迷。 我是一名探鬼主播惧互,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼喇伯!你這毒婦竟也來了喊儡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤稻据,失蹤者是張志新(化名)和其女友劉穎艾猜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捻悯,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡匆赃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了今缚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片算柳。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖荚斯,靈堂內(nèi)的尸體忽然破棺而出埠居,到底是詐尸還是另有隱情,我是刑警寧澤事期,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布滥壕,位于F島的核電站,受9級特大地震影響兽泣,放射性物質(zhì)發(fā)生泄漏绎橘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一唠倦、第九天 我趴在偏房一處隱蔽的房頂上張望称鳞。 院中可真熱鬧,春花似錦稠鼻、人聲如沸冈止。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熙暴。三九已至闺属,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間周霉,已是汗流浹背掂器。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留俱箱,地道東北人国瓮。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像狞谱,于是被迫代替她去往敵國和親乃摹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容