使用functor-ish和非functor-ish函数定义一个类

时间:2013-03-01 16:20:14

标签: haskell typeclass functor

我想定义一个类m,它提供了一个functor-ish操作 像这样的类型签名:

mapify ::(a - > b) - > m a - > m b

不过,我还需要一些其他的非仿函数操作。我会 我喜欢写下以下内容:

class MyMap m where
  type Key m
  type Value m
  keys :: m -> [Key m]
  elems :: m -> [Value m]
  mapify :: (a -> b) -> m a -> m b -- WON'T WORK!!!

我理解为什么这样做不起作用。我想出的解决方案是将它分成两个类,一个是“正常”的,另一个是在Functor上建模的。

{-# LANGUAGE TypeFamilies #-}

import qualified Data.Map.Lazy as M

class MyMap m where
  type Key m
  type Value m
  keys :: m -> [Key m]
  elems :: m -> [Value m]

class MyMapF m where
  mapify :: (a -> b) -> m a -> m b

instance MyMap (M.Map k v) where
  type Key (M.Map k v) = k
  type Value (M.Map k v) = v
  keys = M.keys
  elems = M.elems

instance MyMapF (M.Map k) where
  mapify = M.map

工作正常,但是有更好的方法吗?


编辑:我非常喜欢sabauma提出的解决方案。但是,当我尝试创建一个使用此类的函数时,我无法获得类型签名。

doSomething
  :: (MyMap m1, MyMap m2, Container m1 ~ Container m2) => -- line 22
    (Value m1 -> Value m2) -> m1 -> m2                    -- line 23
doSomething f m = mapify f m                              -- line 24

我得到的错误是:

../Amy3.hs:22:6:
    Couldn't match type `b0' with `Value (Container m0 b0)'
      `b0' is untouchable
           inside the constraints (MyMap m1,
                                   MyMap m2,
                                   Container m1 ~ Container m2)
           bound at the type signature for
                      doSomething :: (MyMap m1, MyMap m2, Container m1 ~ Container m2) =>
                                     (Value m1 -> Value m2) -> m1 -> m2
    Expected type: a0 -> b0
      Actual type: Value m1 -> Value m2

../Amy3.hs:24:19:
    Could not deduce (m2 ~ Container m0 b0)
    from the context (MyMap m1, MyMap m2, Container m1 ~ Container m2)
      bound by the type signature for
                 doSomething :: (MyMap m1, MyMap m2, Container m1 ~ Container m2) =>
                                (Value m1 -> Value m2) -> m1 -> m2
      at ../Amy3.hs:(22,6)-(23,38)
      `m2' is a rigid type variable bound by
           the type signature for
             doSomething :: (MyMap m1, MyMap m2, Container m1 ~ Container m2) =>
                            (Value m1 -> Value m2) -> m1 -> m2
           at ../Amy3.hs:22:6
    In the return type of a call of `mapify'
    In the expression: mapify f m
    In an equation for `doSomething': doSomething f m = mapify f m

../Amy3.hs:24:28:
    Could not deduce (m1 ~ Container m0 a0)
    from the context (MyMap m1, MyMap m2, Container m1 ~ Container m2)
      bound by the type signature for
                 doSomething :: (MyMap m1, MyMap m2, Container m1 ~ Container m2) =>
                                (Value m1 -> Value m2) -> m1 -> m2
      at ../Amy3.hs:(22,6)-(23,38)
      `m1' is a rigid type variable bound by
           the type signature for
             doSomething :: (MyMap m1, MyMap m2, Container m1 ~ Container m2) =>
                            (Value m1 -> Value m2) -> m1 -> m2
           at ../Amy3.hs:22:6
    In the second argument of `mapify', namely `m'
    In the expression: mapify f m
    In an equation for `doSomething': doSomething f m = mapify f m
Failed, modules loaded: none.

2 个答案:

答案 0 :(得分:6)

一种可能性是使用另一个关联的“容器”类型进行编码 类型。

import qualified Data.Map.Lazy as M

class MyMap m where
  type Key m
  type Value m
  type Container m :: * -> *
  keys :: m -> [Key m]
  elems :: m -> [Value m]
  mapify :: (a -> b) -> Container m a -> Container m b

instance MyMap (M.Map k v) where
  type Key (M.Map k v) = k
  type Value (M.Map k v) = v
  type Container (M.Map k v) = M.Map k
  keys = M.keys
  elems = M.elems
  mapify = M.map

Container Map的{​​{1}}是Map k,所以你捆绑了 Map及其关联的密钥类型。这样,您的mapify功能就会提升 你的功能进入容器。是否“更好”取决于你, 我猜,但它确实减少了类型类的数量。你不应该 需要MyMapF类与您的示例,因为MyMapF与。{1}}相同 标准Functor类型类。

好的,可以通过修改mapify的定义来修复该错误 略。

class MyMap m where
  type Key m
  type Value m
  type Container m :: * -> *
  keys :: m -> [Key m]
  elems :: m -> [Value m]
  -- mapify :: (a -> b) -> Container m a -> Container m b

  -- Make sure the type-checker knows that m2 is just the container of m with
  -- a different value
  mapify :: (MyMap m2, m2 ~ Container m (Value m2)) => (Value m -> Value m2) -> m -> m2


instance MyMap (M.Map k v) where
  type Key (M.Map k v) = k
  type Value (M.Map k v) = v
  type Container (M.Map k v) = M.Map k
  keys = M.keys
  elems = M.elems
  mapify = M.map

doSomething
  :: (MyMap m1, MyMap m2, m2 ~ Container m1 (Value m2)) =>
     (Value m1 -> Value m2) -> m1 -> m2
doSomething f m = mapify f m

这将进行类型检查。我认为问题只是类型检查器需要 一个更强的暗示,你正在做的就是改变Value的{​​{1}} 例如,不更改底层容器。

答案 1 :(得分:2)

我意识到这个问题有点老了,但是我跑过来寻找一些与你无关的想法,特别是因为它看起来比sabauma提出的要简单一点。

import qualified Data.Map.Lazy as M

class MyMap m where
  type Key m
  keys :: m a -> [Key m]
  elems :: m a -> [a]
  mapify :: (a -> b) -> m a -> m b

instance MyMap (M.Map k) where
  type Key (M.Map k) = k
  keys = M.keys
  elems = M.elems
  mapify = M.map

另请参阅keys包。