Haskell中能否使用语法糖构建存在量化类型的列表?
为什么OverloadedLists无法实现需求
OverloadedLists扩展的脱糖逻辑是先将[a, b, c]转换为标准同构列表a : b : c : [],再调用fromList/fromListN转换为目标类型。标准列表要求所有元素类型完全一致,这一步的类型检查发生在fromList调用之前,所以哪怕你的ShowList支持异质元素,也过不了标准列表的类型校验,这条路是走不通的。
可行的替代方案
你可以通过两种常用方案实现接近原生列表的语法糖:
方案1:可变参数类型类实现
这种方案不需要Template Haskell,靠类型类的多态实现可变参数的构造函数,语法上和普通函数调用接近:
首先开启必要扩展:
{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE InstanceSigs #-}
然后定义类型类和实现:
data ShowList = Nil | forall a. Show a => a :* ShowList infixr 5 :* -- 可变参数构造类型类 class MkShowList r where mkShowList' :: ShowList -> r instance MkShowList ShowList where mkShowList' :: ShowList -> ShowList mkShowList' = id instance (Show a, MkShowList r) => MkShowList (a -> r) where mkShowList' :: ShowList -> a -> r mkShowList' acc x = mkShowList' (x :* acc) -- 导出的构造函数,自动补全末尾的Nil mkShowList :: MkShowList r => r mkShowList = mkShowList' Nil
使用的时候直接传任意多个Show实例即可:
myShowList :: ShowList myShowList = mkShowList 'x' () "test" 123
这种方案的缺点是参数之间用空格分隔,不是逗号分隔的列表格式,但不需要额外的Template Haskell扩展。
方案2:Template Haskell准引号实现
如果你一定要用逗号分隔的类列表语法,可以用准引号实现,语法可以做到非常接近你要的效果:
首先开启必要扩展:
{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE QuasiQuotes #-} import Language.Haskell.TH import Language.Haskell.TH.Quote data ShowList = Nil | forall a. Show a => a :* ShowList infixr 5 :*
然后定义准引号:
showList :: QuasiQuoter showList = QuasiQuoter { quoteExp = \s -> do exps <- parseExp s >>= \case ListE es -> pure es _ -> fail "showList quasiquoter only accepts comma-separated list expressions" foldr (\e acc -> [| $e :* $acc |]) [| Nil |] exps , quotePat = undefined , quoteType = undefined , quoteDec = undefined }
使用的时候语法如下:
myShowList :: ShowList myShowList = [showList| 'x', (), "test", 123 |]
这种方案的语法和你想要的原生列表语法非常接近,只需要在列表外面套一层准引号标记即可。
总结
你想要的完全原生的['x', ()]语法是不可能实现的,因为内置列表的同构限制是在语法脱糖的第一步就生效的,你只能选择上面两种替代方案,或者手动用:*拼接。
内容的提问来源于stack exchange,提问作者Joseph Sible-Reinstate Monica
相关产品推荐
相关产品推荐

