haskell 参数化类

ve7v8dk2  于 2023-10-19  发布在  其他
关注(0)|答案(2)|浏览(171)

我有一个类如下:

class Token a where
    symbol :: a -> String

我还希望Token的所有示例都有一个返回参数化类型的函数convert。转换本身工作正常:

class Token a b where
    convert :: a -> b

data Egal = One | Two

instance Token Egal Int where
    convert One = 111
    convert Two = 222

main = print $ show (convert One :: Int)

但是当我尝试同时使用symbolconvert时,我会得到关于二义性的错误。这是我的代码:

class Token a b where
    convert :: a -> b
    symbol :: a -> String

data Egal = One | Two

instance Token Egal Int where
    convert One = 111
    convert Two = 222
    symbol One = "one"
    symbol Two = "two"

main = print $ show (convert One :: Int)

我做错了什么?
编辑:阅读我自己的问题,我开始想知道:这是两个不同的类,我的data Egal显示示例化两者吗?

jdgnovmf

jdgnovmf1#

正如您在这里定义的那样,您可以拥有具有相同a但冲突b s的示例。就像这样:

instance Token Char Char where
    convert = id
    symbol c = [c]

instance Token Char Bool where
    convert = (>'m')
    symbol c = [c, c, c, c]

那么,symbol 'x'应该是"x"还是"xxxx"?两者都是可能的,这取决于选择上述示例中的哪一个;对于symbol应该使用哪个示例,因此应该得到哪个答案,这是不明确的。有多种方法可以解决这个问题。一种方法是简单地允许模糊性,并给予您自己指定在调用站点使用哪个示例的能力。您可以打开AllowAmbiguousTypesTypeApplications扩展;然后:

> symbol @_ @Char 'x' -- or, more explicitly, symbol @Char @Char 'x'
"x"
> symbol @_ @Bool 'x' -- explicitly, symbol @Char @Bool 'x'
"xxxx"

但在许多情况下,您确实希望编译器检查您是否创建了多个具有冲突a的示例。然后您可以使用FunctionalDependencies扩展名:

class Token a b | a -> b where
    convert :: a -> b
    symbol :: a -> String

instance Token Char Char where {- ... -}

TypeFamilies扩展名:

class Token a where
    type Conversion a
    convert :: a -> Conversion a
    symbol :: a -> String

instance Token Char where
    type Conversion Char = Char
    {- ... -}

在大多数情况下,它们或多或少具有相同的效果:冲突的示例被标记,并且不存在模糊性。

d7v8vwbk

d7v8vwbk2#

在注解中,您写道类型a只能转换为单一类型b。您当前的设计允许在同一个a中使用多个b

instance Token A B1 where
   convert = ...
   symbol = ...

instance Token A B2 where
   convert = ...
   symbol = ...

为什么GHC会抱怨?上面,symbol的两个定义都有A -> String类型,所以GHC不能只从类型中选择使用哪个示例。
一种(非理想的解决方案)是启用“模糊类型”Haskell扩展,并在每次调用symbol时手动选择示例。

main = putStrLn $ symbol @A @B1 someAvalue

最好告诉Haskell类型b是由类型a单独确定的,这样就不会有歧义。这可以使用函数依赖扩展来实现,但在我看来,最好使用类型族扩展来实现。

class Token a where              -- no b here
    type Converted a             -- the type after conversion
    convert :: a -> Converted a  -- we return that type
    symbol :: a -> String

data Egal = One | Two

instance Token Egal where
    type Converted Egal = Int   -- here we chose Int
    convert One = 111
    convert Two = 222
    symbol One = "one"
    symbol Two = "two"

main = print $ convert One -- no annotation needed

相关问题