如何避免使用GADT实现多个功能?

时间:2013-08-31 12:58:14

标签: haskell gadt

首先,这是我的代码的最小例子:

{-# LANGUAGE GADTs #-}

-- package "url"
import Network.URL (exportURL, URL(..), URLType(..))

data MyURL a where
    GalleryURL :: URL -> MyURL URL
    PictureURL :: URL -> MyURL URL

url = URL { url_type = PathRelative, 
            url_path = "www.google.com", 
            url_params = []}

galleryURL = GalleryURL url

myExportURL :: MyURL a -> String
myExportURL (GalleryURL a) = exportURL a
myExportURL (PictureURL a) = exportURL a

main = print $ myExportURL galleryURL

我使用GADT来避免混合使用不同类型的URL。 myExportURL函数对于所有类型的URL都是相同的。有没有办法使用这样的东西:

myExportURL (_ a) = exportURL a

而不是为GADT的每个子类型(正确的术语是什么?)重复它?

我也欢迎对我想解决的代码或问题的任何其他评论。

3 个答案:

答案 0 :(得分:12)

正确的术语是“为每个构造函数”。

您的GADT看起来很可疑,因为所有构造函数都构造相同的MyURL URL类型。如果这就是你想要的,那么你首先不需要GADT并且可以使用普通的ADT:

data MyURL = GalleryURL URL | PictureURL URL

要缩短myExportUrl,有不同的选项。一种是重构类型:

data URLKind = GalleryURL | PictureURL
data MyURL = MyURL { myUrlKind :: URLKind, myExportURL :: URL }

这样你仍然可以使用“短”结构形式,例如: MyURL PictureURL foo。此外,您还可以使用Haskell为您生成的myExportURL函数。

只有在高级案例中才需要GADT,所以如果你的例子没有充分证明你为什么需要GADT,那就让我们吧吧。

答案 1 :(得分:8)

您的MyURL类型不会阻止您混合图库和图片网址。 GalleryURL和PictureURL都具有相同的类型MyURL URL。尝试这样的事情:

data Gallery = Gallery

data Picture = Picture

data MyURL a where
    MyURL :: a -> URL -> MyURL a

然后你可以像想象的那样编写代码的其余部分:

url = URL { url_type = PathRelative, 
            url_path = "www.google.com", 
            url_params = []}

galleryURL = MyURL Gallery url

myExportURL :: MyURL a -> String
myExportURL (MyURL _ a) = exportURL a

main = print $ myExportURL galleryURL

当所有构造函数适用于所有类型时,您不需要GADT,并且您实际上不需要Gallery和Picture类型的构造函数,因此您可以将这些部分编写为:

data Gallery -- requires the empty type extension
data Picture
data MyURL a = MyURL URL

galleryURL :: MyURL Gallery
galleryURL = MyURL url

myExportURL :: MyURL a -> String
myExportURL (MyURL a) = exportURL a

答案 2 :(得分:0)

其他人建议修改数据结构,这是使用模式同义词的不同方法:

{-# Language GADTs, PatternSynonyms, ViewPatterns, TypeOperators #-}

import Data.Kind

data MyURL a where
  GalleryURL :: URL -> MyURL URL
  PictureURL :: URL -> MyURL URL

url :: MyURL a -> URL
url (GalleryURL u) = u   --  Proof that: URL ~ a
url (PictureURL u) = u   --  Proof that: URL ~ a

-- Works on ‘MyURL a’ for any ‘a’
pattern Url :: URL -> MyURL a 
pattern Url u <- (url -> u)

如果添加了另一个不包含URL的构造函数,我们必须为url添加失败案例

data MyURL a where
  GalleryURL :: URL -> MyURL URL
  PictureURL :: URL -> MyURL URL
  I          :: Int -> MyURL Int

url :: MyURL a -> Maybe URL
url (GalleryURL u) = Just u    --  Proof that: URL ~ a
url (PictureURL u) = Just u    --  Proof that: URL ~ a
url (I i)          = Nothing   --  Proof that: Int ~ a

pattern Url :: URL -> MyURL a 
pattern Url u <- (url -> Just u)

showMyURL :: MyURL a -> String
showMyURL (Url u) = show u
showMyURL (I   i) = show i   --  Proof that: Int ~ a

不过! - 假设我们想要一个评估函数,在给定a时返回MyURL a - 这可以按预期工作

eval :: MyURL a -> a
eval (GalleryURL url) = url   --  Proof that: URL ~ a
eval (PictureURL url) = url   --  Proof that: URL ~ a
eval (I int)          = int   --  Proof that: Int ~ a

但我们的新Url模式同义词失败了!

eval :: MyURL a -> a
eval (Url url) = url    

当我们在模式同义词上模式匹配时,我们得到没有关于a的新信息

pattern Url :: URL -> MyURL a

aURL之间的联系已被切断。我们导入Data.Type.Equality并添加Refl :: a :~: URL等于a的证明URL

-- Gets ‘URL’ from a ‘MyURL URL’
--
-- ‘Refl’ is our proof that the input is ‘MyURL URL’ 
url :: MyURL a -> Maybe (a :~: URL, a)
url (GalleryURL url) = Just (Refl, url)
url (PictureURL url) = Just (Refl, url)
url (I          int) = Nothing

然后我们说Url _提供了a ~ URL匹配时的证据,

pattern Url :: () => a ~ URL => a -> MyURL a
pattern Url url <- (Url -> (Refl, url))

并且可以使用单一模式再次检索URL

eval :: MyURL a -> a
eval (Url url) = url   --  Proof that: URL ~ a
eval (I   int) = int   --  Proof that: Int ~ a