4

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

我最初试图弄清楚如何重载内置函数!!以支持自己的数据类型。具体来说,!!属于以下类型:

[a] -> Int -> a

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

MyType1 -> MyType2 -> MyType3

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

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

我不相信我实际上什至想要重载这个函数,但我仍然对如何完成它感兴趣。实际上,如果重载操作符这样的评论!!甚至是一个好主意,也会受到赞赏。

4

3 回答 3

8

在 Haskell 中,几乎所有运算符都是库定义的。您最常使用的许多是在默认导入的 Prelude 模块的“标准库”中定义的。加布里埃尔的回答显示了如何避免导入其中一些定义,以便您可以自己制作。

但这并不是重载,因为运算符仍然只意味着一件事;您为它定义的新含义。Haskell 为重载提供的主要方法是类型类机制,即以一种对不同类型具有不同实现的方式使用运算符。

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

碰巧其他人已经制作了一些行为很像列表的类型,因此已经有一个类型类称为ListLike. 我不确定你的类型与列表到底有多接近,所以它可能不适合 ListLike,但你应该看看它,因为如果你可以让你的类型成为 ListLike 实例,它会给你很多功能.

于 2013-08-22T06:45:01.540 回答
6

您实际上不能在 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 ((!!))行来隐藏原始的。

于 2013-08-22T23:43:42.330 回答
2

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

import Prelude hiding ((!!))

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

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

于 2013-08-22T05:36:23.827 回答