考虑这些功能
f1 :: Maybe Int
f1 = return 1
f2 :: [Int]
f2 = return 1
Run Code Online (Sandbox Code Playgroud)
两者都有相同的说法return 1。但结果是不同的。f1赋予价值Just 1并f2赋予价值[1]
看起来 Haskellreturn根据返回类型调用了两个不同的版本。我想更多地了解这种函数调用。编程语言中有此功能的名称吗?
Tim*_*Tim 11
这是一个冗长的答案!
正如您可能从评论和 Thomas 出色(但技术性很强)的回答中看到的那样,您提出了一个非常难的问题。做得好!
我没有试图解释技术答案,而是试图让您大致了解 Haskell 在幕后所做的工作,而没有深入研究技术细节。希望它可以帮助您全面了解正在发生的事情。
return是类型推断的一个例子。
大多数现代语言都有一些多态的概念。例如var x = 1 + 1将设置为x等于 2。在静态类型语言中,2 通常是一个 int。如果你说var y = 1.0 + 1.0那么y将是一个浮动。运算符+(它只是一个具有特殊语法的函数)
大多数命令式语言,尤其是面向对象的语言,只能以一种方式进行类型推断。每个变量都有一个固定类型。当您调用一个函数时,它会查看参数的类型并选择适合该类型的该函数的一个版本(或者如果找不到则抱怨)。
当您将函数的结果分配给变量时,该变量已经具有类型,如果它与返回值的类型不一致,则会出现错误。
因此,在命令式语言中,类型推导的“流程”跟随您程序中的时间推导变量的类型,对其进行处理并推导出结果的类型。在动态类型语言(例如 Python 或 javascript)中,在计算变量的值之前不会分配变量的类型(这就是为什么似乎没有类型的原因)。在静态类型语言中,类型是提前计算出来的(由编译器),但逻辑是相同的。编译器计算出变量的类型,但它通过以与程序运行相同的方式遵循程序的逻辑来实现。
在 Haskell 中,类型推断也遵循程序的逻辑。作为 Haskell,它以非常纯粹的数学方式(称为 System F)这样做。类型语言(即类型推导的规则)类似于 Haskell 本身。
现在记住 Haskell 是一种懒惰的语言。在需要它之前,它不会计算出任何东西的价值。这就是为什么在 Haskell 中拥有无限数据结构是有意义的。Haskell 从来没有想过数据结构是无限的,因为它不会在需要时才去计算它。
现在所有这些懒惰的魔法也发生在类型级别。就像 Haskell 在真正需要之前不会计算出表达式的值是什么一样,Haskell 在真正需要时才会计算出表达式的类型。
考虑这个函数
func (x : y : rest) = (x,y) : func rest
func _ = []
Run Code Online (Sandbox Code Playgroud)
如果你问的Haskell这个函数的类型,它有一个看的定义,认为[]和:并推导出它的工作与名单。但它永远不需要查看 x 和 y 的类型,它只知道它们必须相同,因为它们最终出现在同一个列表中。因此,它推导出函数的类型,[a] -> [a]其中 a 是尚未计算出来的类型。
到目前为止还没有魔法。但是注意到这个想法与在 OO 语言中如何实现之间的区别是很有用的。Haskell 不会将参数转换为 Object,执行它的操作然后再转换回来。只是没有明确询问 Haskell 列表的类型是什么。所以它不在乎。
现在尝试在 ghci 中输入以下内容
maxBound - length ""
maxBound : "Hello"
Run Code Online (Sandbox Code Playgroud)
现在刚刚发生了什么!?minBound bust 是一个字符,因为我把它放在一个字符串的前面,它必须是一个整数,因为我把它加到 0 并得到一个数字。此外,这两个值显然非常不同。
那么minBound的类型是什么?让我们问ghci!
:type minBound
minBound :: Bounded a => a
Run Code Online (Sandbox Code Playgroud)
啊!这意味着什么?基本上这意味着它没有费心弄清楚到底是什么a,但是Bounded如果你输入:info Bounded你会得到三行有用的行
class Bounded a where
minBound :: a
maxBound :: a
Run Code Online (Sandbox Code Playgroud)
和很多不太有用的行
所以,如果a是Bounded有价值观minBound和类型的maxBound a。事实上,引擎盖Bounded下只是一个值,它的“类型”是一个包含 minBound 和 maxBound 字段的记录。因为它是一个值,Haskell 在真正需要之前不会查看它。
所以我似乎在你问题的答案区域的某个地方徘徊。在我们继续之前return (您可能已经从评论中注意到这是一个非常复杂的野兽。)让我们看看read.
再次ghci
read "42" + 7
read "'H'" : "ello"
length (read "[1,2,3]")
Run Code Online (Sandbox Code Playgroud)
希望你不会太惊讶地发现有定义
read :: Read a => String -> a
class Read where
read :: String -> a
Run Code Online (Sandbox Code Playgroud)
soRead a只是一个包含单个值的记录,它是一个函数String -> a。很容易假设有一个读取函数可以查看字符串,计算字符串中包含的类型并返回该类型。但它恰恰相反。它完全忽略字符串,直到需要它为止。当需要该值时,Haskell 首先计算出它所期望的类型,一旦完成,它就会获取读取函数的适当版本并将其与字符串组合。
现在考虑一些稍微复杂的事情
readList :: Read a => [String] -> a
readList strs = map read strs
Run Code Online (Sandbox Code Playgroud)
在引擎盖下 readList 实际上有两个参数 readList' (Read a) -> [String] -> [a] readList' {read = f} strs = map f strs
再一次,由于 Haskell 是懒惰的,它只在需要找出返回值时才费心查看参数,在这一点上它知道是什么a,因此编译器可以去优化正确版本的 Read。在那之前它不在乎。
希望这能让您对正在发生的事情以及为什么 Haskell 可以“重载”返回类型有所了解。但重要的是要记住它不是传统意义上的超载。每个函数只有一个定义。只是其中一个参数是一袋函数。read_str永远不知道它在处理什么类型。它只知道它获取一个函数String -> a和一些字符串,为了执行应用程序,它只需将参数传递给map. map反过来甚至不知道它得到字符串。当您深入了解 Haskell 时,函数对它们所处理的类型不太了解就变得非常重要。
现在让我们来看看return。
还记得我说过 Haskell 中的类型系统与 Haskell 本身非常相似。请记住,在 Haskell 中,函数只是普通值。这是否意味着我可以拥有一个将类型作为参数并返回另一种类型的类型?当然可以!
您已经看到一些类型函数Maybe接受一个类型a并返回另一个类型,它可以是Just a或Nothing。 []接受一个类型a并返回一个as列表。Haskell 中的类型函数通常是容器。例如,我可以定义一个类型函数BinaryTree,它a在树状结构中存储's的负载。当然还有很多更陌生的。
所以,如果这些类型函数类似于普通类型,我可以有一个包含类型函数的类型类。一个这样的类型类是Monad
class Monad m where
return a -> m a
(>>=) m a (a -> m b) -> m b
Run Code Online (Sandbox Code Playgroud)
所以这里m有一些类型函数。如果我想定义Monadform我需要定义return它下面的可怕的操作符(称为绑定)
正如其他人指出的那样,return对于一个相当无聊的功能来说,这是一个非常具有误导性的名称。设计 Haskell 的团队后来意识到了他们的错误,他们对此深表歉意。return只是一个普通函数,它接受一个参数并返回其中Monad包含该类型的a 。(你从来没有问过 Monad 到底是什么,所以我不会告诉你)
让我们定义Monad为m = Maybe!首先我需要定义return. 应该return x是什么?记住我只允许定义一次函数,所以我无法查看,x因为我不知道它是什么类型。我总是可以 return Nothing,但这似乎浪费了一个完美的函数。让我们定义一下,return x = Just x因为这实际上是我唯一能做的事情。
可怕的绑定怎么办?我们能说什么x >>= f?wellx是Maybe a某种未知类型的 aa并且f是一个接受 ana并返回 a的函数Maybe b。不知何故,我需要将这些结合起来得到一个 Maybe b`
所以我需要定义Nothing >== f. 我无法调用,f因为它需要一个类型的参数,a而我没有类型的值,a我什至不知道“a”是什么。我只有一个选择,那就是定义
Nothing >== f = Nothing
Run Code Online (Sandbox Code Playgroud)
怎么样Just x >>= f?嗯,我知道x是类型a和f需要a作为参数,这样我就可以设置y = f a和推断y的类型的b。现在我需要做一个Maybe b,我有一个b...
只是 x >>= f = 只是 (fx)
所以我有一个Monad!如果m是List怎么办?好吧,我可以遵循类似的逻辑并定义
return x = [x]
[] >>= f = []
(x : xs) >>= a = f x ++ (xs >>= f)
Run Code Online (Sandbox Code Playgroud)
万岁另一个Monad!完成这些步骤并说服自己没有其他合理的方法来定义这一点是一个很好的练习。
那么当我打电话时会发生什么return 1?
没有!
Haskell 的 Lazy 记得。该thunk的 return 1(技术术语),只是坐在那里,直到有人需要的价值。一旦 Haskell 需要该值,它就知道该值应该是什么类型。特别是它可以推断出m是List。现在它知道 Haskell 可以找到Monadfor的实例List。一旦这样做,它就可以访问正确的返回版本。
所以最后 Haskell 准备好调用 return,在这种情况下返回 [1]!
该return函数来自 Monad 类:
class Applicative m => Monad (m :: * -> *) where
...
return :: a -> m a
Run Code Online (Sandbox Code Playgroud)
所以 return 接受任何类型a的值并产生一个类型的值m a。m正如您所观察到的,monad是多态的,它使用 Haskell 类型类Monad来实现临时多态。
此时您可能意识到return这不是一个好的、直观的名称。它甚至不是像许多其他语言那样的内置函数或语句。事实上,存在一个更好的命名和相同操作的函数 - pure。在几乎所有情况下return = pure。
也就是说,该函数return与函数pure(来自 Applicative 类)相同 - 我经常对自己想“这个 monadic 值纯粹是底层的a”,如果还没有约定,我会尝试使用 pure 而不是 return代码库。
您可以将return(或纯)用于属于Monad. 这包括 Maybe monad 来获取 type 的值Maybe a:
instance Monad Maybe where
...
return = pure -- which is from Applicative
...
instance Applicative Maybe where
pure = Just
Run Code Online (Sandbox Code Playgroud)
或者让列表 monad 获得一个值[a]:
instance Applicative [] where
{-# INLINE pure #-}
pure x = [x]
Run Code Online (Sandbox Code Playgroud)
或者,作为一个更复杂的例子,Aeson 的 parse monad 来获取 type 的值Parser a:
instance Applicative Parser where
pure a = Parser $ \_path _kf ks -> ks a
Run Code Online (Sandbox Code Playgroud)