Haskell中的重载内置函数



在Haskell中,如何重载诸如!!之类的内置函数?

我最初试图弄清楚如何重载内置函数!!以支持自己的数据类型。具体而言,!!的类型为:

[a] -> Int -> a

我想保留它现有的功能,但也可以在它的类型签名看起来更像的地方调用它

MyType1 -> MyType2 -> MyType3

我最初想这样做是因为MyType1就像一个列表,我想使用!!运算符,因为我的操作与从列表中选择项目非常相似。

如果我重载+之类的东西,我可以将函数的一个实例添加到适用的类型类中,但我认为这不是一个选项。

我甚至不相信我真的想再重载这个函数了,但我仍然对如何实现它感兴趣。事实上,关于重载诸如!!之类的运算符是否是一个好主意的评论也将受到赞赏。

在Haskell中,几乎所有运算符都是库定义的。您使用最多的许多是在默认情况下导入的Prelude模块的"标准库"中定义的。Gabriel的回答说明了如何避免导入其中的一些定义,这样你就可以自己制作了。

不过,这并不是重载,因为运算符仍然只是一个意思;Haskell为重载提供的主要方法是类型类机制。

类型类标识一组支持一些常见函数的类型。当您将这些函数与类型一起使用时,Haskell会计算出适用于您使用的类型类的正确实例,并确保使用了正确的函数实现。大多数类型类只有几个函数,有些只有一两个,需要实现这些函数才能创建新实例。它们中的许多还提供了许多根据核心函数实现的辅助函数,并且您可以将所有这些函数与您创建类实例的类型一起使用。

碰巧其他人已经创建了行为有点像列表的类型,因此已经有了一个名为ListLike的类型类。我不确定你的类型与列表的接近程度,所以它可能不太适合ListLike,但你应该看看它,因为如果你能让你的类型成为ListLike实例,它会给你很多功能。

您实际上不能重载Haskell中现有的非类型类函数。

您可以在一个新的类型类中定义一个新的函数,它足够通用,可以将原始函数和您想要作为重载的新定义都包含在内。您可以将其命名为与标准函数相同的名称,并避免导入标准函数。这意味着在您的模块中,您可以使用名称!!来获得新定义的功能和原始定义(解析将由类型决定)。

示例:

{-# LANGUAGE TypeFamilies #-}
import Prelude hiding ((!!))
import qualified Prelude
class Indexable a where 
    type Index a
    type Elem a
    (!!) :: a -> Index a -> Elem a

instance Indexable [a] where 
    type Index [a] = Int 
    type Elem [a] = a
    (!!) = (Prelude.!!)

newtype MyType1 = MyType1 String
    deriving Show
newtype MyType2 = MyType2 Int
    deriving Show
newtype MyType3 = MyType3 Char
    deriving Show
instance Indexable MyType1 where 
    type Index MyType1 = MyType2
    type Elem MyType1 = MyType3
    MyType1 cs !! MyType2 i = MyType3 $ cs !! i

(我用类型族来暗示,对于一个可以被索引的给定类型,索引的类型和元素的类型会自动跟随;当然,这可以用不同的方法来完成,但更详细地说,这会偏离过载问题。)

然后:

*Main> :t (!!)
(!!) :: Indexable a => a -> Index a -> Elem a
*Main> :t ([] !!)
([] !!) :: Int -> a
*Main> :t (MyType1 "" !!)
(MyType1 "" !!) :: MyType2 -> MyType3
*Main> [0, 1, 2, 3, 4] !! 2
2
*Main> MyType1 "abcdefg" !! MyType2 3
MyType3 'd'

需要强调的是,这对序言中定义的现有!!函数和使用它的任何其他模块都没有任何作用。这里定义的!!是一个新的、完全无关的函数,它恰好具有相同的名称,并在一个特定的实例中委托给Prelude.!!。任何现有的代码都无法在不进行修改的情况下在MyType1上开始使用!!(尽管您可以更改的其他模块当然可以导入您的新!!来获得此功能)。任何导入此模块的代码都必须对!!的所有使用进行模块限定,或者使用相同的import Prelude hiding ((!!))行来隐藏原始代码。

隐藏前奏曲的(!!)操作符,您可以定义自己的(!!)操作符:

import Prelude hiding ((!!))
(!!) :: MyType1 -> MyType2 -> MyType3
x !! i = ... -- Go wild!

如果愿意,您甚至可以为新的(!!)运算符创建一个类型类。

最新更新