类型和重载,连接是什么?

lo *_*cre 16 haskell overloading typeclass

我目前正在尝试围绕类型类和实例,我还不太明白它们的意义.到目前为止,我对此事有两个问题:

1)当函数使用该类型类中的某些函数时,为什么必须在函数签名中使用类型类.例:

f :: (Eq a) => a -> a -> Bool
f a b = a == b
Run Code Online (Sandbox Code Playgroud)

为什么要(Eq a)签名.如果==没有定义a那么为什么不在遇到时抛出错误a == b?必须提前声明类型类有什么意义?

2)类型类和函数重载如何相关?

这样做是不可能的:

data A = A
data B = B

f :: A -> A
f a = a

f :: B -> B
f b = b
Run Code Online (Sandbox Code Playgroud)

但是可以这样做:

data A = A
data B = B

class F a where
  f :: a -> a

instance F A where
  f a = a

instance F B where
  f b = b
Run Code Online (Sandbox Code Playgroud)

怎么了?为什么我不能拥有两个具有相同名称但在不同类型上运行的函数...来自C++我觉得很奇怪.但我可能对这些事情到底有什么错误的概念.但是一旦我将它们包装在这些类型类实例中,我就可以.

也可以随意向我输入类别或输入理论词汇,因为我正在学习Haskell的同时学习这些科目,我怀疑Haskell如何在这里做事情的理论基础.

Ale*_*ing 36

我同意Willem Van Onsem的大部分回答,但我认为它忽略了类型类比真正的临时重载的一个主要优点:抽象.想象一下,我们使用ad-hoc重载而不是类型类来定义Monad操作:

-- Maybe
pure :: a -> Maybe a
pure = Just

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
Just x >>= f = f x
Nothing >>= _ = Nothing

-- Either
pure :: a -> Either e a
pure = Right

(>>=) :: Either e a -> (a -> Either e b) -> Either e b
Right x >>= f = f x
Left err >>= _ = Left err
Run Code Online (Sandbox Code Playgroud)

现在,我们知道每一个单子可以来表达pure>>=,如上,但我们知道,他们可以使用等效表示fmap,purejoin.因此,我们应该能够实现join适用于任何 monad 的函数:

join x = x >>= id
Run Code Online (Sandbox Code Playgroud)

但是,现在我们遇到了问题.什么是join类型?

显然,join必须是多态的,因为它适用于任何monad设计.但是给它类型签名forall m a. m (m a) -> m a显然是错误的,因为它不适用于所有类型,只适用于monadic类型.因此,我们需要在我们的类型中表示需要存在某些操作(>>=) :: m a -> (a -> m b) -> m b,这正是类型类约束提供的.

鉴于此,很明显ad-hoc重载使得重载名称成为可能,但是不可能对这些重载名称进行抽象,因为不能保证不同的实现以任何方式相关.你可以定义单子没有类型类,但你不能确定join,when,unless,mapM,sequence,和所有你得到免费的,当你定义只有两个操作其他的好东西.

因此,在Haskell中必须使用类型类来实现代码重用并避免大量重复.但是,您是否同时具有类型类型重载和类型定向的特殊名称重载?是的,事实上,伊德里斯确实如此.但是Idris的类型推断与Haskell的类型推断非常不同,因此在Willem的答案中,由于许多原因,它比Haskell更可行.


Wil*_*sem 16

简而言之:因为这就是Haskell的设计方式.

为什么要(Eq a)签名.如果==没有为a定义那么为什么不在遇到时抛出错误a == b

为什么我们将这些类型放在C++程序的签名中(而不仅仅是作为主体中的断言)?因为这就是C++的设计方式.通常,关于构建什么编程语言的概念是" 明确需要明确的内容 ".

并不是说Haskell模块是开源的.这意味着我们只提供签名.因此,当我们写例如:

Prelude> foo A A

<interactive>:4:1: error:
    • No instance for (Eq A) arising from a use of ‘foo’
    • In the expression: foo A A
      In an equation for ‘it’: it = foo A A
Run Code Online (Sandbox Code Playgroud)

我们经常foo在这里写一些没有Eq类型类的类型.因此,我们会遇到很多错误,这些错误只能在编译时发现(或者如果Haskell在运行时是动态语言).放入Eq a类型签名的想法是我们可以foo提前查找签名,从而确保类型是类型类的实例.

请注意,您不必自己编写类型签名:Haskell通常可以派生函数的签名,但签名应包含调用和有效使用函数所需的所有必要信息.通过添加类型约束,我们加快了开发速度.

怎么了?为什么我不能拥有两个具有相同名称但在不同类型上运行的函数.

再说一遍:这就是Haskell的设计方式.函数式编程语言中的函数是" 一等公民 ".这意味着这些通常都有一个名称,我们希望尽可能避免名称冲突.就像C++中的类通常具有唯一的名称(名称空间除外).

假设您将定义两个不同的功能:

incr :: Int -> Int
incr = (+1)

incr :: Bool -> Bool
incr _ = True

bar = incr
Run Code Online (Sandbox Code Playgroud)

然后incrbar不得不选择?当然我们可以使类型显式(即incr :: Bool -> Bool),但通常我们想避免这种工作,因为它会引入很多噪音.

我们不这样做的另一个好理由是因为通常类型类不仅仅是函数的集合:它将合约添加到这些函数中.例如,Monad类型类必须满足函数之间的某些关系.例如(>>= return)应该等同于id.换句话说,类型类:

class Monad m where
    (>>=) :: m a -> (a -> m b) -> m b
    return :: a -> m a
Run Code Online (Sandbox Code Playgroud)

不描述了两个独立的功能(>>=)return:这是一组功能.你有两者(通常在特定>>=和之间有一些合同return),或者根本没有.


Dav*_*vid 5

这只回答问题1(直接,至少).

类型签名f :: a -> a -> Bool是简写f :: forall a. a -> a -> Bool.如果只适用于已经定义的s,f它将无法真正适用于所有类型.这限制类型已被使用约束表现在.aa(==)(==)(Eq a)f :: forall a. (Eq a) => a -> a -> Bool

"对于所有人来说"/通用量化是Haskell(参数)多态性的核心,除其他外,它提供了参数化的强大而重要的属性.