2013-08-22 29 views
3

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

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

[a] -> Int -> a 

,我想保留它现有的功能,还能够在那里它的类型签名看起来更像

MyType1 -> MyType2 -> MyType3 

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

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

我不确定我实际上甚至想重载这个函数,但我仍然对如何完成它感兴趣。其实,如果超载运营商如!!甚至是一个好主意的意见,也将不胜感激。

+2

这是通过让用户导入两个不同的'(!!)'并且至少有一个合格的,或者通过使用'(!)'来查找/索引来解决的。你不能真正重载Haskell中的任意函数/运算符。 – kqr

+0

请注意,您在技术上不能“超载”'!!',如果超载,则表示临时的基于非类型类型的多态性。您可以像“超载”fmap,“<$>”或“>> =”那样“重载”它,但它们必须被限制为明确的类型类型(如monad或applicative或“list-like”)他们的类型签名被推广到整个类型类。你最好的选择是为类型类定义你自己的新的,泛化的'(!!)'的类型签名,并且为你想要的所有东西(!!)'定义实例。 –

回答

5

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

2

隐藏前奏的(!!)运营商,您可以定义自己的(!!)运营商:

import Prelude hiding ((!!)) 

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

你甚至可以使一个类型类新(!!)运营商,如果你喜欢。

8

在Haskell中,几乎所有的操作符都是库定义的。许多你使用最多的是在默认导入的Prelude模块的'标准库'中定义的。 Gabriel的答案显示了如何避免导入一些定义,以便您可以自己创建。

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

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

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