参数化类

Chi*_*dio 11 haskell class parametrize

我有一堂课如下:

class Token a where
    symbol :: a -> String
Run Code Online (Sandbox Code Playgroud)

我还希望 的所有实例都有一个返回参数化类型的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)
Run Code Online (Sandbox Code Playgroud)

但是当我尝试同时使用两者时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)
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?


编辑:读我自己的问题时,我开始想知道:这是否应该是两个不同的类,并且我的data Egal节目将两者实例化?

Dan*_*ner 17

正如您在此处定义的那样,您可以拥有具有相同a但冲突的实例b。像这样:

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

instance Token Char Bool where
    convert = (>'m')
    symbol c = [c, c, c, c]
Run Code Online (Sandbox Code Playgroud)

现在,应该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"
Run Code Online (Sandbox Code Playgroud)

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

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

instance Token Char Char where {- ... -}
Run Code Online (Sandbox Code Playgroud)

TypeFamilies扩展名:

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

instance Token Char where
    type Conversion Char = Char
    {- ... -}
Run Code Online (Sandbox Code Playgroud)

在大多数情况下,它们或多或少具有相同的效果:冲突的实例被标记,并且不存在任何歧义。


chi*_*chi 14

在评论中,您写道类型a只能转换为单一类型b。您当前的设计允许有多个b相同的 s a

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

instance Token A B2 where
   convert = ...
   symbol = ...
Run Code Online (Sandbox Code Playgroud)

GHC 为何抱怨?上面,两个定义都有symboltype A -> String,因此 GHC 无法仅从 types 中选择要使用的实例。

一种(非理想的解决方案)是启用“模糊类型”Haskell 扩展并在每次symbol调用时手动选择实例。

main = putStrLn $ symbol @A @B1 someAvalue
Run Code Online (Sandbox Code Playgroud)

最好告诉 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
Run Code Online (Sandbox Code Playgroud)