函数式编程并不是使Haskell出色的原因

2020-12-14 21:54:01

该职位主要针对非Haskeller。 Haskell经常出现在hackernews或/ r / programming上,但是其内容通常在宣传功能性编程,强类型和纯净性的某些方面。

Haskell体现了所有这些内容,但是实用性不是来自强类型函数编程,而是来自运行时的功能。还存在其他强类型的功能语言,例如OCaml,但许多方面还不那么成熟。在这里,我探索了一些运行时属性,这些属性极大地增强了Haskell的功能,性能和便利性。

存在许多Haskell实现(FLRC,UHC,JHC),但是我们将专门介绍GHC,它是迄今为止使用最多,功能最强大的。

在几乎所有语言中,执行线程都会引发异常。在C ++中,引发异常看起来像这样:

void doAction(){if(1 == 0){throw std :: runtime_error("不可能发生的事情"); }}尝试{doAction();} catch(const std :: exception& err){cout<< err.what()<< std :: endl;}

在Haskell中,任何线程都可以向另一个线程抛出异常。让我们生成一个线程,然后通过从其外部抛出异常立即杀死它。不需要修改线程或函数本身。

这种能力最终变得异常强大。突然之间,通常可以实现为语言基本属性的功能现在可以在其中表达。

一个例子是超时:: Int-> IO a-> IO(也许是)。超时以毫秒为单位进行超时,该操作要运行,并且可能会或可能不会返回结果,具体取决于该操作是否及时完成。在内部它产生带有计时器的线程,并且如果达到最后期限,则会对其他线程计算产生异常。

使用Go语言,无法在外部杀死goroutine。一种常见的模式出现了,其中作者手动等待频道以确保可以控制:

stop:= make(chan bool)go func(){for {select {case<-stop:return}}}()stop<-true

如果犯了一个错误,资源将泄漏,并且必须不断复制更复杂的控制流。在Haskell中,即使利用拥有的资源(例如套接字)杀死线程也是安全的,因为在括号上构建的结构会自动清除资源。

无所畏惧的并发很难。正在使语言变得更加难以处理的语言功能正在开发中。虽然Haskell不会阻止您死锁您的程序,但运行时具有工具来检测何时发生这种情况并在死锁的代码中引发诸如BlockedIndefinitelyOnMVar的异常。

Haskell是惰性的,因此虽然不那么常见,但是如果检测到无效的无限循环,则可能会抛出NonTermination异常。但是,这并不完美。

Haskell的尾部调用优化限制了所需的堆栈大小,但是会引发可恢复的StackOverflow。对于数字错误,存在各种算术异常,例如Overflow或DivideByZero。

每个线程可以通过setAllocationCounter独立设置分配限制,这将导致AllocationLimitExceeded异常。这对于处理多租户很有用。

大多数语言都有一些绿色线程的实现,但很少有将其作为主要的计算模式。 Go和Erlang在编写联网应用程序方面的成功与这种并发模型紧密相关。将Rust实现为库的语言(如Rust)具有相当的人体工程学含义。

标准POSIX pthread比绿色线程消耗更多的资源,并利用操作系统调度程序。在我的系统上:

默认的线程堆栈大小为8 MB。从技术上讲,Linux会通过虚拟内存进行延迟分配,但是即使“产生并杀死”线程也可能消耗数千个CPU周期。

扩展到大量并发用户(通常称为C10K问题)通常是通过异步IO操作来完成的。异步IO多路复用与低级别的并行性相结合是NGINX之类的服务如何获得如此高性能的方式。

GHC内部使用epoll或kqueue,具体取决于平台。并且已经在尝试对新的io_uring Linux API的支持,这可以带来可观的性能提升。

在绿色线程实现中,Haskell是最强大的。例如,用threadStatus :: ThreadId-> IO ThreadStatus可以检查线程的状态,如果被阻塞,甚至可以查看*为什么*:

数据BlockReason = BlockedOnMVar-^在' MVar' | BlockedOnBlackHole-^在另一个线程进行的计算中被阻止| BlockedOnException-^在' throwTo'中被阻止 | BlockedOnSTM-^在' retry'中被阻止 在STM交易中| BlockedOnForeignCall-^当前在国外呼叫中| 其他阻止 某些语言(例如Java)以调整功能而闻名。 垃圾收集器不能完全适合每个工作负载。 在吞吐量和等待时间之间存在折衷。 默认情况下,运行时使用分代复制收集器。 GHC有各种各样的标志可以翻转,旋钮可以旋转。 尤其令人感兴趣的是--numa,它可以优化具有较高通信开销的高内核数多CPU服务器。