【问题标题】:Mapping over a heterogenous data structure with a generic function使用通用函数映射异构数据结构
【发布时间】:2013-04-06 22:31:14
【问题描述】:

我正在研究 HList 实现,但我一直在尝试为其实现 map 函数。我尝试了很多不同的方法,但每一种我都会遇到与该函数相关的编译器错误。

以下是我想如何使用通用函数Just 将其应用于输入数据结构的所有元素的示例。

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

-- | An input heterogenous data structure
recursivePairs :: (Int, (Char, (Bool, ())))
recursivePairs = (1, ('a', (True, ())))

-- | This is how I want to use it
recursivePairs' :: (Maybe Int, (Maybe Char, (Maybe Bool, ())))
recursivePairs' = hMap Just recursivePairs

class HMap f input output where
  hMap :: f -> input -> output

-- | A counterpart of a Nil pattern match for a list
instance HMap f () () where
  hMap _ _ = ()

-- | A counterpart of a Cons pattern match for a list
instance 
  ( HMap f iTail oTail, 
    Apply f iHead oHead ) =>
  HMap f (iHead, iTail) (oHead, oTail) 
  where
    hMap f (head, tail) = (apply f head, hMap f tail)

class Apply f input output where
  apply :: f -> input -> output

instance Apply (input -> output) input output where
  apply = id

这样我得到以下编译器错误:

No instance for (Apply (a0 -> Maybe a0) Int (Maybe Int))
  arising from a use of `hMap'
The type variable `a0' is ambiguous

有没有办法解决这个问题,如果没有,为什么?

【问题讨论】:

  • 我认为问题在于类型系统没有意识到您在每个连续的应用程序上使用不同的具体类型实例化 Just,因为您对 hMap 的定义不断重复使用相同的 f .第一次应用类型为Int -> Maybe Int,第二次应用类型为Char -> Maybe Char。但是,我仍然不太确定如何解决它。
  • @GabrielGonzalez 是的,这正是问题所在。如果您将fundep | input output -> f 添加到Apply 类,错误消息会说它正在寻找实例,例如(Bool -> Maybe Bool) Char (Maybe Char)。我正在考虑使用cast 来断开f 在类型级别上的两种用法,但这感觉不太自然,并且依赖Typeable 也不是很吸引人。

标签: haskell hlist


【解决方案1】:

问题是您尝试使用具有不同参数的多态函数,但您的 Apply 实例采用函数(单类型)。您可以通过多种方式轻松解决此问题

data JustIfy = JustIfy
instance Apply JustIfy a (Maybe a) where
  apply _ = Just

recursivePairs' :: (Maybe Int, (Maybe Char, (Maybe Bool, ())))
recursivePairs' = hMap JustIfy recursivePairs

可以很好地使用您的代码

编辑:对同一件事更通用的方法是(需要RankNTypes

--A "universal" action that works on all types
newtype Univ f = Univ (forall x. x -> f x)
instance Apply (Univ f) x (f x) where
   apply (Univ f) x = f x

recursivePairs' :: (Maybe Int, (Maybe Char, (Maybe Bool, ())))
recursivePairs' = hMap (Univ Just) recursivePairs

或者如果您使用的是最新的 ish 版本的 GHC 并且愿意打开更多扩展

newtype Univ' c f = Univ' (forall x. c x => x -> f x)
instance c x => Apply (Univ' c f) x (f x) where
  apply (Univ' f) x = f x

class All x
instance All x

recursivePairs' :: (Maybe Int, (Maybe Char, (Maybe Bool, ())))
recursivePairs' = hMap (Univ' Just :: Univ' All Maybe) recursivePairs

这很好,从那时起它可以让你做一些事情,比如在你映射的函数中包含一个“显示”。

如需更通用的解决方案,请查看Oleg's Type level lambda caclulus,它允许您在值级别编写代码,然后自动神奇地推断出适当的类型级别程序。不幸的是,Oleg 的解决方案在这一点上相当陈旧,并且使用了我不太喜欢的 LC 的名义实现。我一直在考虑如何做得更好,但可能会推迟到类型家庭出现可判定的平等。

我的观点是,现在应该使用 GADT 和 DataKinds 而不是元组来完成 HLists。类型族比函数依赖更可取,但目前更受限制,因为它们缺乏可判定的相等性。

【讨论】:

  • 谢谢。你说有多种方法可以解决这个问题 - 你能详细说明一下吗?我正在寻找最佳解决方案,因此无需以任何方式坚持提供的代码,这只是一个抽象示例。有没有办法解决这个问题,而不必为我想与hMap 一起使用的每个函数声明特定实例?
  • @NikitaVolkov 我添加了更多通用解决方案
【解决方案2】:

虽然以下内容并不能完全回答这个问题(所以我不会接受它),但它确实解决了有关映射结构的问题,而不需要应用函子的任何额外实例:

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

import Control.Applicative

main = do
  print $ (hPure recursivePairs :: (Maybe Int, (Maybe Char, (Maybe Bool, ()))))
  print $ (hPure recursivePairs :: ([Int], ([Char], ([Bool], ()))))

recursivePairs :: (Int, (Char, (Bool, ())))
recursivePairs = (1, ('a', (True, ())))

class HPure input output where
  hPure :: input -> output

instance HPure () () where
  hPure _ = ()

instance  
  ( Applicative f, 
    HPure iTail oTail ) => 
  HPure (iHead, iTail) (f iHead, oTail) 
  where hPure (iHead, iTail) = (pure iHead, hPure iTail)

输出:

(Just 1,(Just 'a',(Just True,())))
([1],("a",([True],())))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-11
    • 2012-11-26
    • 2012-05-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多