使用带有约束字段的数据类型代替约束

时间:2014-12-17 17:47:58

标签: haskell operator-overloading typeclass

TL,DR; 扩展约束,ad-hoc ......?我的路线是“健忘”,或者是不相等的

各位大家好,我正在尝试制作一个可以采用约束(在我们的例子中为IsString)的重载函数,或者具有相同约束字段的数据类型。到目前为止,这是我的代码:

{-# LANGUAGE
    OverloadedStrings
  , FlexibleInstances
  , UndecidableInstances
  , InstanceSigs
  , TypeFamilies
#-}

import Data.Monoid

class Bar a where
  bar :: ( IsString b
         , Monoid b ) => a -> b

-- | This instance won't work.
instance ( IsString a
         , Monoid a ) => RelativeUrl a where
  bar :: ( IsString b
         , Monoid b
         , a ~ b ) => a -> b
  bar = id

-- | This is the data type "extending" @IsString@
data Foo a where
  Foo :: ( IsString a, Monoid a ) =>
         a -> Foo a

-- | This is where my dreams end :(
instance Bar (Foo a) where
  bar :: ( IsString b
         , Monoid b
         , a ~ b ) => a -> b
  bar (Foo a) = a

我意识到实例签名不是犹太教,这就是(技术上)这不起作用的原因,但有没有其他方法可以做到这一点?理想情况下,所有对bar的调用都可以通过上下文推断 - 例如bar "foo" :: IsString a => a,而不必将OverloadedString限制为实际类型。

还有另一种方法可以达到这个目的吗?我愿意接受疯狂的想法:)

1 个答案:

答案 0 :(得分:3)

Bar类可以转换为IsString的任何内容。我认为Monoid实例存在某种效率。我们可以为Barbar提供更多有启发性的名称。

class ToStringPlus a where
  toStringPlus :: ( IsString b,
                    Monoid b ) => a -> b

您希望bar "foo" :: IsString a => a。启用OverloadedStrings "foo" :: IsString a -> a。您正在询问如何将已在IsString的所有实例上已经多态的值转换为在IsString的所有实例上具有多态性的值。您不需要像toStringPlus "foo"这样的内容,只需使用"foo"

隐藏IsString

如果您想将类型forall a. IsString a => a转换为数据类型,则可以使用GADT。它根本没用,因为forall a. IsString a => a类型唯一可能的值是fromString x x :: String。此类型可以保存String可以容纳的完全相同的值,而实用程序String没有提供。

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}

import Data.String

data AString where
    AString :: (forall a. IsString a => a) -> AString

instance IsString AString where
    fromString x = AString (fromString x)

instance ToStringPlus AString where
    toStringPlus (AString a) = a

更有用的东西

AString不是很有用,因为它只能保存与String相同的值。 ToStringPlus类允许转换为使用String以上内容的内容,还允许Monoidmappendmconcat的{​​{1}}操作}。这意味着类型mempty应该能够保存不同于forall a. (IsString a, Monoid a) => a的内容。

String

data MonoidalString where MonoidalString :: (forall a. (IsString a, Monoid a) => a) -> MonoidalString 形成MonoidalString。请注意,由于排名N类型,Monoidmconcat无法以无点样式编写。

mappend

instance Monoid MonoidalString where mempty = MonoidalString mempty (MonoidalString x) `mappend` (MonoidalString y) = MonoidalString (x `mappend` y) mconcat ms = MonoidalString (mconcat (map toStringPlus ms)) 也可以是MonoidalStringIsString的实例,其方式与上一节中的ToStringPlus相同。

AString

这让我们在评论中为您的请求赋予意义“我正在尝试将已经多态的内容转换为instance IsString MonoidalString where fromString x = MonoidalString (fromString x) instance ToStringPlus MonoidalString where toStringPlus (MonoidalString a) = a 和任何IsString的所有实例[以及多态的内容......] ”。我们可以使用Foo操作将MonoidIsString的所有实例已经多态的内容与"poly string"结合起来,以获得具有多态性的内容在MonoidalStringIsString的所有实例中。

鉴于某些内容Monoidexisting :: MonoidalString,我们可以将它们与"poly string" :: IsString a => a结合使用。

mappend

我们可以使用它来制作一个小示例程序,以展示 existing :: MonoidalString "poly string" :: IsString a => a "poly string" `mappend` existing :: MonoidalString toStringPlus ("poly string" `mappend` existing) :: (Monoid b, IsString b) => b

的所有功能
MonoidalString

再次禁止

如果你想创建一个接受main = do let existing = ("MS" :: MonoidalString) putStr . toStringPlus $ mconcat ["poly string", mempty `mappend` " ", existing] bar类型参数的函数forall a. Ctx a => a,只要有D,你就可以这样做。然后函数的类型为instance Ctx D。这是有效的,因为D -> ...可以在任何需要forall a. Ctx a => a的地方使用。

我们可以使用它为最后一个例子写一个D

bar

我们可以传递给bar :: (IsString a, Monoid a) => MonoidalString -> a bar = toStringPlus 多态字符串bar

"foo" :: IsString a => a

我们也可以传递到单形 "foo" :: IsString a => a bar "foo" :: (Monoid a, IsString a) => a MonoidalString

existing :: MonoidalString