haskell - 使用具有约束字段的数据类型代替约束

标签 haskell operator-overloading typeclass

TL,DR; 扩展一个约束,特别...?我的路线是“健忘的”,或者说是不平等的

大家好,我目前正在尝试制作一个重载函数,它可以采用约束(在我们的例子中,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

我意识到实例签名不是 kosher,这就是为什么(从技术上讲)这行不通,但是有没有其他方法可以做到这一点?理想情况下,我希望所有对 bar 的调用都可以通过上下文推断出来——这样 bar "foo"::IsString a => a,而不必限制OverloadedString 到实际类型。

还有其他方法可以实现吗?我对疯狂的想法持开放态度:)

最佳答案

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

如果您想将类型 转换为所有 a。 IsString a => a 转换为您可以使用 GADT 执行的数据类型。它一点用处都没有,因为 类型的唯一可能值是 forall a。 IsString a => afromString 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 的东西,它还允许 mappendMonoid 操作、mconcatmempty。这意味着类型 forall a。 (IsString a, Monoid a) => a 应该能够容纳不同于 String 的东西。

data MonoidalString where
    MonoidalString :: (forall a. (IsString a, Monoid a) => a) -> MonoidalString

MonoidalString 形成一个 Monoid。请注意,mconcatmappend 由于排名 N 类型,不能以无点样式编写。

instance Monoid MonoidalString where
    mempty = MonoidalString mempty
    (MonoidalString x) `mappend` (MonoidalString y) = MonoidalString (x `mappend` y)
    mconcat ms = MonoidalString (mconcat (map toStringPlus ms))

MonoidalString 也可以是 IsStringToStringPlus 的实例,其方式与前面的 AString 相同部分。

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

instance ToStringPlus MonoidalString where
    toStringPlus (MonoidalString a) = a

这让我们可以在评论中为您的请求赋予意义“我正在尝试将在 IsString 和任何 Foo 的所有实例上已经是多态的东西转换为某些东西那是多态的...]”。我们可以使用 Monoid 操作 组合一些已经在 IsString 的所有实例上多态的东西,“poly string”,使用 MonoidalString 来获得在 IsStringMonoid 的所有实例上都是多态的东西。

给定一些 existing::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 => aD 只要有 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

我们也可以给bar传递一个单态的MonoidalStringexisting::MonoidalString

    existing = ("MS" :: MonoidalString)
bar existing                            :: (Monoid a, IsString a) => a

关于haskell - 使用具有约束字段的数据类型代替约束,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27531715/

相关文章:

haskell,数据二叉树的仿函数

haskell - 如何修复 '*** Exception: Prelude.head: empty list' 这里

haskell - 对于缺乏类型类回溯的情况,是否有解决方法?

haskell - Monoid vs MonadPlus

Scala 中类型类的性能

haskell - 推断两个记录中公共(public)字段的类型

haskell - 平衡语言,2 个非终结符,列表理解

C++ std::move 指针

C++,()运算符重载,它的工作是什么

c++ - 本地覆盖字符串提取运算符