You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何定义多态的Tagless Final列表?

解决Tagless Final多态列表的String实例问题

我在阅读Oleg Kiselyov的《Typed Tagless-Final Interpretations: Introductory Course》后,尝试将普通Haskell列表转为Tagless Final列表时遇到了问题。

可正常运行的Int类型Tagless Final列表

以下代码实现了仅支持Int元素的Tagless Final列表,运行正常:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE MultiParamTypeClasses #-}

module TList

where

class TList repr  where
  tnil :: repr
  tcons :: Int -> repr -> repr

tlist0 :: TList repr => repr
tlist0 = tnil

tlist1 :: TList repr => repr
tlist1 = tcons 1 $ tcons 2 $ tcons 3 tnil

instance TList String where
  tnil = "tnil"
  tcons hd tail = "tcons " ++ show hd ++ " $ " ++ tail

view :: String -> String
view = id

执行view tlist1会得到字符串:"tcons 1 $ tcons 2 $ tcons 3 $ tnil"。

尝试定义多态列表遇到的问题

为了支持任意元素类型,我定义了多态版本的PList类:

class PList repr a where
  pnil :: repr a
  pcons :: a -> repr a -> repr a

plist0 :: forall (repr :: * -> *) a. PList repr a => repr a
plist0 = pnil

plist1 :: PList repr Int => repr Int
plist1 = pcons 1 $ pcons 2 $ pcons 3 pnil

这段代码可以编译,但尝试为String定义实例时失败:

错误的实例代码

instance PList String where
  pnil = "pnil"
  pcons hd tail = "pcons " ++ show hd ++ " $ " ++ tail

报错信息

[1 of 1] Compiling TList            ( List/List.hs, interpreted ) [Source file changed]

List/List.hs:30:10: error:
    • Expecting one more argument to ‘PList String’
      Expected a constraint,
        but ‘PList String’ has kind ‘* -> Constraint’
    • In the instance declaration for ‘PList String’
   |
30 | instance PList String where
   |          ^^^^^^^^^^^^

List/List.hs:30:16: error:
    • Expected kind ‘* -> *’, but ‘String’ has kind ‘*’
    • In the first argument of ‘PList’, namely ‘String’
      In the instance declaration for ‘PList String’
   |
30 | instance PList String where
   |                ^^^^^^

改为instance PList String Int where仍报错:

List/List.hs:30:16: error:
    • Expected kind ‘* -> *’, but ‘String’ has kind ‘*’
    • In the first argument of ‘PList’, namely ‘String’
      In the instance declaration for ‘PList String Int’
   |
30 | instance PList String Int where
   |                ^^^^^^

核心问题是:PList类要求第一个参数是* -> *种类的类型(即接受类型参数的构造器),但String是*种类的具体类型,无法直接匹配。

解决方案:包装String为* -> *种类的类型

我们可以定义一个新的包装类型,将String提升为带类型参数的构造器,满足PList的种类要求:

完整修正代码

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE MultiParamTypeClasses #-}

module TList

where

class PList repr a where
  pnil :: repr a
  pcons :: a -> repr a -> repr a

plist0 :: forall (repr :: * -> *) a. PList repr a => repr a
plist0 = pnil

plist1 :: PList repr Int => repr Int
plist1 = pcons 1 $ pcons 2 $ pcons 3 pnil

-- 定义包装类型,将String转为* -> *种类
newtype ShowList a = ShowList String

-- 为包装类型实现PList实例
instance Show a => PList ShowList a where
  pnil = ShowList "pnil"
  pcons hd (ShowList tail) = ShowList $ "pcons " ++ show hd ++ " $ " ++ tail

-- 提取包装后的字符串
viewShow :: ShowList a -> String
viewShow (ShowList s) = s

现在执行viewShow plist1就能得到预期结果:"pcons 1 $ pcons 2 $ pcons 3 $ pnil"。

原理说明

ShowList a是一个带类型参数的新类型,虽然内部只存储String,但它的种类是* -> *,完全符合PList对repr参数的要求。同时我们添加了Show a约束,确保列表元素可以被转换为字符串输出。

内容的提问来源于stack exchange,提问作者Jogger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 14:18:30