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