在Haskell中重载内置函数

时间:2013-08-22 05:07:30

标签: haskell operator-overloading overloading

在Haskell中,如何重载内置函数,例如!!

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

[a] -> Int -> a

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

的地方调用它
MyType1 -> MyType2 -> MyType3

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

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

我不相信我实际上甚至想要重载此功能,但我仍然对它将如何完成感兴趣。实际上,关于是否重载!!这样的运算符的评论甚至是一个好主意也将受到赞赏。

3 个答案:

答案 0 :(得分:8)

在Haskell中,几乎所有运算符都是库定义的。您最常使用的许多都是在默认情况下导入的Prelude模块的“标准库”中定义的。 Gabriel的答案显示了如何避免导入其中一些定义,以便您可以创建自己的定义。

然而,这不是重载,因为运营商仍然只是意味着一件事;你为它定义的新含义。 Haskell为重载提供的主要方法,即使用运算符,使其具有不同类型的不同实现,是类型类机制。

类型类标识一组支持某些常用功能的类型。当您使用带有类型的函数时,Haskell会找出适用于您的用法的类型类的正确实例,并确保使用正确的函数实现。大多数类型只有一些函数,有些只需要一两个,需要实现才能创建一个新实例。它们中的许多都提供了许多以核心功能实现的辅助功能,并且您可以将它们全部用于您创建类的实例的类型。

碰巧其他人已经制作了类似于列表的类型,因此已经有一个名为ListLike的类型类。我不确定你的类型与列表有多接近,所以它可能不适合ListLike,但你应该看看它,因为它可以为你提供很多功能,如果你可以让你的类型成为ListLike实例

答案 1 :(得分:5)

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

您可以做的是在新类型中定义 new 函数,这通常足以包含原始函数和您想要作为重载的新定义。您可以为其指定与标准函数相同的名称,并避免导入标准函数。这意味着在您的模块中,您可以使用名称!!来获取新定义的功能和原始定义(分辨率将由类型指示)。

示例:

{-# 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 ((!!))行来隐藏原始模块。

答案 2 :(得分:2)

隐藏Prelude的(!!)运算符,您可以定义自己的(!!)运算符:

import Prelude hiding ((!!))

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

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