Haskell中的重载内置函数
Posted
技术标签:
【中文标题】Haskell中的重载内置函数【英文标题】:Overload built in function in Haskell 【发布时间】:2013-08-24 16:31:54 【问题描述】:在 Haskell 中,如何重载诸如 !!
这样的内置函数?
我最初试图弄清楚如何重载内置函数!!
以支持自己的数据类型。具体来说,!!
属于以下类型:
[a] -> Int -> a
我想保留它的现有功能,但也能够在其类型签名看起来更像的地方调用它
MyType1 -> MyType2 -> MyType3
我最初想这样做是因为 MyType1 就像一个列表,我想使用 !!
运算符,因为我的操作非常类似于从列表中选择一个项目。
如果我重载+
之类的东西,我可以将我的函数实例添加到适用的类型类中,但我认为这不是一个选项。
我不相信我什至真的想重载这个函数,但我仍然对如何完成它感兴趣。实际上,如果重载 !!
之类的运算符甚至是一个好主意,cmets on 也会受到赞赏。
【问题讨论】:
这在库中通过让用户导入两个不同的(!!)
并且至少有一个合格,或者通过使用(!)
进行查找/索引来解决。在 Haskell 中,你不能真正重载任意函数/运算符。
请注意,从技术上讲,您不能“重载”!!
,如果重载是指临时的非基于类型类的多态性。您可以像“重载”fmap
、<$>
或 >>=
一样“重载”它,但它们必须限制为显式类型类(如 monad 或 applicative,或“list-like”)及其类型签名被推广到整个类型类。你最好的办法是为一个类型类定义你自己的新的、通用的(!!)
的类型签名,并为你想要的所有东西定义实例(!!)
。
【参考方案1】:
在 Haskell 中,几乎所有运算符都是库定义的。您最常使用的许多是在默认导入的 Prelude 模块的“标准库”中定义的。 Gabriel 的回答展示了如何避免导入其中一些定义,以便您可以自己制作。
这不是重载,因为操作符仍然只意味着一件事;您为它定义的新含义。 Haskell 为重载提供的主要方法是 type class 机制。
类型类标识一组支持某些常用功能的类型。当您将这些函数与类型一起使用时,Haskell 会找出适用于您使用的类型类的正确 instance 并确保使用正确的函数实现。大多数类型类只有几个功能,有些只有一两个,需要实现以创建新实例。它们中的许多还提供了许多根据核心功能实现的辅助功能,您可以将所有这些功能与您创建类实例的类型一起使用。
碰巧其他人已经制作了一些行为很像列表的类型,因此已经有一个名为ListLike
的类型类。我不确定您的类型与列表到底有多接近,因此它可能不适合 ListLike,但您应该看看它,因为如果您可以将您的类型设为 ListLike 实例,它将为您提供很多功能.
【讨论】:
【参考方案2】:您实际上不能在 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 ((!!))
行来隐藏原始行。
【讨论】:
【参考方案3】:隐藏 Prelude 的 (!!)
运算符,您可以定义自己的 (!!)
运算符:
import Prelude hiding ((!!))
(!!) :: MyType1 -> MyType2 -> MyType3
x !! i = ... -- Go wild!
如果您愿意,您甚至可以为您的新 (!!)
运算符创建一个类型类。
【讨论】:
以上是关于Haskell中的重载内置函数的主要内容,如果未能解决你的问题,请参考以下文章