制作类型类,不能从上下文中推断

Making a typeclass, cannot deduce from context

我正在使用 Servant 库,我想自动将结果映射到错误代码中。仆人期望类型:Either (Int, String) a

例如,如果我有一个模型函数类型:IO (Maybe User)。我想把它变成 (404, "Not Found") on Nothing,如果有的话 User

为此,我正在编写一个类型类!

class Servile a where
    toStatus :: ToJSON val => a -> Either (Int, String) val

instance ToJSON a => Servile (Maybe a) where
    toStatus Nothing  = Left (404, "Not Found")
    toStatus (Just a) = Right a

我还有其他实例想写,但是这个给了我错误:

Could not deduce (a ~ val)
from the context (ToJSON a)
  bound by the instance declaration at Serials/Api.hs:90:10-38
or from (ToJSON val)
  bound by the type signature for
             toStatus :: ToJSON val => Maybe a -> Either (Int, String) val
  at Serials/Api.hs:91:5-12
  ‘a’ is a rigid type variable bound by
      the instance declaration at Serials/Api.hs:90:10
  ‘val’ is a rigid type variable bound by
        the type signature for
          toStatus :: ToJSON val => Maybe a -> Either (Int, String) val
        at Serials/Api.hs:91:5
Relevant bindings include
  a :: a (bound at Serials/Api.hs:92:20)
  toStatus :: Maybe a -> Either (Int, String) val
    (bound at Serials/Api.hs:91:5)
In the first argument of ‘Right’, namely ‘a’
In the expression: Right a

正确的做法是什么?

我跳上了#haskell,因为我没有很好地表达问题。问题是它不能改变任何 a 来产生这样的 val。事实证明 ToJSON 与此问题无关。

这有效:请注意,我将 toStatus 更改为 a val 而不是 a,并删除了类型变量的实例。

class ToStatus a where
    toStatus :: a val -> Either (Int, String) val

instance ToStatus Maybe where
    toStatus Nothing  = Left (404, "Not Found")
    toStatus (Just v) = Right v

instance Show a => ToStatus (Either a) where
    toStatus (Left a) = Left (500, "Server Error: " <> show a)
    toStatus (Right v) = Right v

另一个解决方案,如果你想使用一些非参数化类型,是使用 TypeFamilies:

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
module Temp where
import Data.Monoid

class ToStatus a where
  type Val a
  toStatus :: a -> Either (Int, String) (Val a)

instance ToStatus (Maybe a) where
  type Val (Maybe a) = a 
  toStatus Nothing  = Left (404, "Not Found")
  toStatus (Just v) = Right v

instance Show a => ToStatus (Either a b) where
  type Val (Either a b) = b 
  toStatus (Left e) = Left (500, "Server Error: " <> show e)
  toStatus (Right v) = Right v

instance ToStatus String where
  type Val String = ()
  toStatus "200 Success" = Right ()
  toStatus err = Left (500, err)