如何在HList上编写异构列表?

3

3
你使用 HCons 1 (HCons "2" HNil)。 (注:这是一个 Haskell 编程语言中的数据结构,表示一个由数字1和字符串“2”组成的列表) - Willem Van Onsem
“Example”? 乍一看,它们更像是单元测试而不是教程文档。我完全不怪你对此感到困惑。 - Silvio Mayolo
哦,那是构建列表的唯一语法吗?我的意思是没有诸如 H([1, "2"]) 这样的速记法吗? - Functor
@SilvioMayolo 我也是这么想的,但不幸的是我找不到其他的教程文档。非常难搞。 - Functor
1
@Functor:那是不可能的,因为此时[1,"2"]已经是一个(普通)列表,因此该列表要求元素都是相同类型。可以使用quasiquoters或者模板Haskell,但不能使用简单的列表。 - Willem Van Onsem
那很有道理。谢谢。 - Functor
2个回答

5
你可以使用 hEndhBuild 函数构造 HList,例如:
hello = hEnd (hBuild 1 "2")

对这个列表应用 print 函数是比较困难的。我认为没有一种简单的方法编写高阶函数来映射 print 函数到 HList 上,但是你可以像这样手动遍历 HList:

{-# LANGUAGE DataKinds #-}

import Data.HList

class PrintEach ts where
  printEach :: HList ts -> IO ()
instance PrintEach '[] where
  printEach HNil = pure ()
instance (Show t, PrintEach ts) => PrintEach (t : ts) where
  printEach (HCons x xs) = print x *> printEach xs

hello = hEnd (hBuild 1 "2")

main = printEach hello
-- This prints:
-- 1
-- "2"

谢谢你的回答。我不知道 hbuild API,现在我明白如何构建列表了。实际上,现在我执行 hEnd $ hBuild 1 "2" - Functor
由于使用约束类型的事情即使使用HList的map API也很困难,我再次研究了您的代码。 - Functor
我有一个相关的问题,可以在这个链接中查看 https://stackoverflow.com/questions/71349025/traversing-hlist-heterogeneous-list-manually-in-haskell - Functor

5
您可能不仅想要对HList进行映射,而是想要对其进行遍历,也就是所谓的mapM。您可能不感兴趣的是单个结果-(),而只是副作用,那么应该使用mapM_

HList确实有一个通用工具,称为不出所料的hMapM_。 对于任意受限制地多态函数使用它有点麻烦,但是对于打印来说,库已经准备好使用了:

Prelude Data.HList> let l = 1.*."2".*.HNil :: HList '[Int, String]
Prelude Data.HList> l
H[1,"2"]
Prelude Data.HList> hMapM_ HPrint l
1
"2"

如Noughtmare所评论的,与其他多态函数配合使用(而不必编写自定义类型)的方法是使用 Fun,例如:
Prelude Data.HList> hMapM_ (Fun print :: Fun Show (IO ())) l
1
"2"

这段代码并不是太糟糕,但还是有点让人不爽。我会定义一个同义词,以允许使用TypeApplications来避免冗余:

{-# LANGUAGE TypeFamilies, RankNTypes, ConstraintKinds, UnicodeSyntax #-}

import Data.Kind

fun :: ∀ (cxt :: Type -> Constraint) getb
        . (∀ a. (cxt a) => a -> getb) -> Fun cxt getb
fun = Fun

之后

Prelude Data.HList Data.Kind> :set -XTypeApplications
Prelude Data.HList Data.Kind> hMapM_ (fun @Show print) l
1
"2"

或者我们甚至可以将其变得更好

{-# LANGUAGE FlexibleContexts, AllowAmbiguousTypes #-}

h'MapM_ :: ∀ (cxt :: Type -> Constraint) l m
   . (Monad m, HFoldr (Mapcar (Fun cxt (m ()))) [m ()] l [m ()])
    => (∀ a. (cxt a) => a -> m ()) -> HList l -> m ()
h'MapM_ f = hMapM_ (Fun f :: Fun cxt (m ()))

然后

Prelude Data.HList Data.Kind> h'MapM_ @Show print l
1
"2"

一如既往地感谢。实际上,我正在调查有关“map”的API。现在我可以开始使用了。 - Functor
1
文档还指出更一般的形式:hMapM_ (Fun print :: Fun Show (IO ())) l - Noughtmare
感谢您的额外编辑。现在我在这个问题的顶部开始了相关的提问。请查看,谢谢。https://dev59.com/d3ENtIcB2Jgan1zn5jYz - Functor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接