2013-03-01 2 views
3

Я хочу, чтобы определить класс m, который обеспечивает функтор-иш операции с сигнатуру типа, как это:Определение класса с функтора-иш и не функторных иш функций

mapify :: (а -> b) -> ma -> mb

Мне также нужны были некоторые другие операции без функтора. Я бы хотелось написать что-то вдоль линий:

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 

Это хорошо работает, но есть ли лучший способ?


EDIT: Мне очень нравится решение, предлагаемое 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. 
+0

Интересно, не работает ли 'класс Functor m => MyMap m где ...'? Таким образом, вы можете просто повторно использовать часть «Functor», и вам не нужно беспокоиться о 'm' vs' m a'. – Xeo

ответ

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 является Map k, так что вы связывайте Map с соответствующим ключом типа. Таким образом, ваша функция mapify поднимает функцию в контейнер. Является ли это «лучше» или нет, Я думаю, но он сокращает количество классов типов. Вы не должны нуждаться в классе MyMapF, например, MyMapF - это то же, что и у 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 экземпляра MyMap без изменения основного контейнера.

+0

Спасибо! Это намного лучше для меня, потому что в области, с которой я работаю, все функции «принадлежат» вместе.Кстати, я не могу использовать «Functor», потому что есть несколько операций «функтор-иш», которые мне нужно предоставить, например «mapValueWithKey». Однако теперь у меня проблема с сигнатурой типа (см. Выше). – mhwombat

+0

@mhwombat Мне пришлось немного изменить определение класса, но эта новая версия, похоже, проверяет тип. Возможно, кто-то еще знает лучший способ кодировать это? – sabauma

2

Я понимаю, что этот вопрос немного устарел, но я наткнулся на него, ища что-то несвязанное и имел идею, которая вам может понравиться, тем более, что она кажется немного проще, чем предложенная сабаума.

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.