如何创建一个将考虑不同容器类型的类型类?

时间:2018-02-13 22:34:46

标签: haskell typeclass

我已经在这个问题上挣扎了一两个小时,我发现很难将心理概念转化为实际的代码。感觉我错过了一些与类型相关的知识 - 但我不确定是什么。

基本上我们有以下代码:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE AllowAmbiguousTypes #-}

module Routes where

data RouteUrl a = Dashboard
              | CreatePost
              | ListPost
              | EditPost a
              | DeletePost a
              | Register
              | Login deriving Show

data PublicUrl = PublicUrl (RouteUrl Int)
data PlaceHolderUrl = PlaceHolderUrl (RouteUrl String)

data UrlSum = UrlPub PublicUrl | UrlPla PlaceHolderUrl

getUrl :: UrlSum -> String
getUrl (UrlPub (PublicUrl (Dashboard))) =       "/dashboard"
getUrl (UrlPla (PlaceHolderUrl (Dashboard))) =  "/dashboard"
getUrl (UrlPub (PublicUrl (EditPost x))) =      "/editpost/" ++ show x
getUrl (UrlPla (PlaceHolderUrl (EditPost x))) = "/editpost/(abcxyz)" ++ show x

以上工作正常,但我试图找到一种方法来避免重复"/dashboard""/editpost/..."部分。

输出的唯一区别应该是PlaceHolderUrl我们应该在参数前加上(abcxyz)

因此,在我的心智模型中,我只是在思考"查看外部类型(PublicUrl或PlaceHolderUrl),并使用为该类型分配的适当函数" 我以为我可以按照以下方式做点什么:

getUrl' :: (RenderUrlComponent u b) Int => u -> String
getUrl' u = case (getRouteUrl u) of
  Dashboard -> "/dashboard"
  (EditPost x) -> "/editpost/" ++ renderUrlComponent u

class RenderUrlComponent a b where
  renderUrlComponent :: a -> String
  getRoute :: a -> RouteUrl b

instance RenderUrlComponent PublicUrl Int where
  renderUrlComponent publicUrl = getRoute publicUrl :: RouteUrl Int
  getRoute (PublicUrl x) = x

但是除了上述内容之外还有以下错误:

• Expecting one fewer arguments to ‘RenderUrlComponent u b’
  Expected kind ‘* -> Constraint’,
    but ‘RenderUrlComponent u b’ has kind ‘Constraint’
• In the type signature:
    getUrl' :: (RenderUrlComponent u b) Int => u -> String    | 26 |

我无法弄清楚如何从getRoute publicUrl :: RouteUrl Int中获取Int,而不必采用模式匹配,在这种情况下,我回过头来解决我遇到的问题。< / p>

我似乎正在寻找以下内容:

x是y的实例,如果它包含在z中。

2 个答案:

答案 0 :(得分:4)

只需创建一个帮助函数来抽象出通用性,不需要(或证明)这里的类。

module Routes where

data RouteUrl a = Dashboard
              | CreatePost
              | ListPost
              | EditPost a
              | DeletePost a
              | Register
              | Login deriving Show

data PublicUrl = PublicUrl (RouteUrl Int)
data PlaceHolderUrl = PlaceHolderUrl (RouteUrl String)

data UrlSum = UrlPub PublicUrl | UrlPla PlaceHolderUrl

getUrlRU :: (Show a) => (String -> String) -> RouteUrl a -> String
getUrlRU prefix ru =
  case ru of
    Dashboard  -> "/dashboard"
    EditPost x -> "/editpost/" ++ prefix (show x)
    ....

getUrl :: UrlSum -> String
getUrl (UrlPub (PublicUrl x)) = getUrlRU id x
getUrl (UrlPla (PlaceHodlerUrl x)) = getUrlRU ("(abcxyz)" ++) x

如果您不想要,则不必传递函数,您可以将字符串作为字符串prefix ++ (show x)。传递一个函数会给你一些灵活性,如果你以某种方式过度简化你的问题可能会有所帮助。

答案 1 :(得分:0)

回过头来看,我似乎过于复杂的事情,但是为了好玩,我设法让它使用类型类,基本上它使用幻像类型,你还需要将RouteUrl传递给参数修饰符函数f虽然被忽略,但编译器可以推断出要使用的实例...

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# OPTIONs -Wno-incomplete-patterns #-}

module Routes where

data RouteUrl a b = Dashboard
              | CreatePost
              | ListPost
              | EditPost a
              | DeletePost a
              | Register
              | Login deriving Show

data PublicUrl
data PlaceHolderUrl
data NormalizedUrl

class Show a => RouteParam a b where
  f :: RouteUrl a b -> String -> String
  normalParam :: RouteUrl a b -> RouteUrl String NormalizedUrl
  normalParam (Dashboard) = Dashboard
  normalParam (CreatePost) = CreatePost
  normalParam (ListPost) = ListPost
  normalParam (EditPost x ) = EditPost $ show x
  normalParam (DeletePost x ) = DeletePost $ show x
  normalParam (Register) = Register
  normalParam (Login) = Login

instance RouteParam Int PublicUrl where
  f _ = id

instance RouteParam String PlaceHolderUrl where
  f _ = ("(abcxyz)" ++)

renderUrl :: RouteParam a b => RouteUrl a b -> String
renderUrl r = case normalParam  r of
      Dashboard -> "/dashboard/"
      EditPost x -> "/editpost/" ++ (f r x)

renderPublicUrl:: RouteUrl Int PublicUrl -> String
renderPublicUrl r = renderUrl r

renderPlaceHolderUrl :: RouteUrl String PlaceHolderUrl -> String
renderPlaceHolderUrl r = renderUrl r