例如,对于大型Web应用程序,在现实世界中使用“强”类型系统吗?


29

我知道这是一个非常广泛,模棱两可甚至是哲学上的问题。在某种程度上,问题中最重要的关键字-“强”类型系统-本身定义不正确。所以,让我尝试解释我的意思。

问题的总体背景

我们已经在Ruby on Rails中构建了一个非常大型的Web应用程序,并且总体上我们对我们的堆栈感到满意。如果需要的话,我们可以非常快速地发货-可以在90%的“业务”案例中使用,而不必担心10%的边缘案例。另一方面,借助代码审查和测试覆盖率,我们可以缓慢而谨慎地进行,并确保覆盖所有基础-再次,仅在需要进行更仔细检查和安全的情况下。

但是,随着团队的壮大,我开始感到不舒服,因为缺少直接放入我们堆栈中的“安全网”。

我们最近开始在Java上进行一些本机Android开发。我(愉快地)想起了编译/静态/强类型语言提供的安全性。

  • IDE本身会捕获错误拼写的变量,错误的数据类型,错误的函数调用以及许多琐碎的错误。全部是因为IDE可以挂接到编译器并验证程序“正确性”的某些方面。
  • 是否需要更改功能签名?简单。编译器+ IDE可以帮助您发现所有调用站点。
  • 是否需要确保始终处理某些异常?已检查的救援例外情况。

现在,尽管这些安全功能各有优点,但我也很清楚它们的缺点。更重要的是,在“繁重的” Java世界中。因此,代替Java,我开始研究人们近来开始使用的大量现代“强类型”语言。例如:Scala,Rust,Haskell等。我最感兴趣的是它们的类型系统和静态/编译时检查的功能。

现在,问题

如何在大型应用程序中使用这些功能强大的类型系统和静态/编译时功能?

例如,对于这些强大的功能,我将如何超越标准的“ hello world”介绍?使用富类型系统来建模业务域问题的模型吗?当您位于30,000 LOC +区域中时,类型系统会帮助还是阻碍?当您的系统与弱类型的外部环境交互时,这些类型的系统(和编译时检查)所提供的安全网会如何处理?通过JSON或XML API,各种数据存储,用户输入等。


12
这是没有主题的,因为没有真正的答案。该问题旨在引起目的的讨论(“我喜欢/不喜欢静态类型,因为...”),而不是事实性的解释。我的建议是选择问题中更具体的部分之一,例如“当您的系统与弱类型的外部世界交互时,这些类型的系统提供的安全网会发生什么?” 并重写有关的问题。您将获得确定的答案,这样将对将来的读者有用。
本杰明·霍奇森

5
资源建议在这里也没有意义(因为这样的答案很快就会过时,我们的确比Google更好)。正如本杰明所说,您的帖子中埋有一些可回答的问题,但是当前状态下的整个帖子本质上是在要求人们使用这些语言来描述他们的经历,这比QuStack或Reddit更好地适合于Quora或Reddit。现场。我并没有投票,因为这个问题问得很好,但这不是StackExchange问​​题。
Ixrec 2015年

4
类型系统是一种工具,与其他任何工具一样,它的功效在很大程度上不取决于工具本身,而取决于选择者。您可以利用Haskell之类的语言的类型系统在类型级别上对有关业务逻辑的不变量进行编码,并在编译时由机器(编译器)检查这些不变量,但是要做到这一点,您需要了解以下语言的语义类型系统和类型检查。正确的问题不是“这是Web工具的好工具”,而是“我面临一些特定的问题;如何使用强大的类型系统来解决这些问题?”
user2407038

5
您描述的大多数内容与类型系统无关。它们纯粹是IDE功能。除了已检查的异常(无双关),您提到的所有功能早已出现在Smalltalk IDE中,而早已出现在用于静态类型语言的IDE中。实际上,使用最广泛的Java IDE之一实际上是经过修改的Smalltalk IDE(IBM VisualAge Smalltalk,它经过修改可以理解Java代码,但仍以Smalltalk编写,并以VisualAge Java的形式发布,然后移植到Java。和释放...
约尔格W¯¯米塔格

4
…VisualAge Java Micro Edition,然后分解为可重用的组件,并以Eclipse的名义作为开放源代码发布。具有讽刺意味的是,在这个问题的上下文中,正是由于 Smalltalk系统的动态特性,Smalltalk IDE如此强大。例如,发明了自动化重构,并在Smalltalk中首次实现了自动化重构。按照您的逻辑,Haskell必须具有最好的IDE,因为它具有您提到的所有语言中最强大,最严格的静态类型系统。但事实并非如此。您还提到了“挂钩到编译器”。这有...
约尔格W¯¯米塔格

Answers:


34

由于目前时间有限,我将给出一个简短的答案,但是我目前正在从事两个大项目(在Haskell中> 100.000 LOC)-flowbox.ioluna-lang.org。我们将Haskell用于所有部分,包括后端,编程语言的编译器,甚至是基于webGL的GUI。我必须承认,强大的类型系统和类似“依赖类型”的机器可以引导您,并使您免于其他语言所熟知的负担和麻烦。我们非常广泛地使用这些类型,并且可以在编译时检查的所有内容都已完成。实际上,在最近三年的开发中,我们从未遇到任何运行时错误或堆栈溢出(这确实令人难以置信)。唯一的错误是程序员明显的逻辑错误。很多人说,如果在Haskell中编译了某些东西,它就可以工作,并且您应该确定有一天它不会在您的脸上吹灭。

回答问题的第一部分:您可以通过阅读一些很棒的博客来了解这些强大的类型系统功能,例如:

实际上,还有很多其他不错的博客(例如Haskell星球)。无论如何,真正了解高级类型系统的最佳方法是开发有用的开源库。我们(按照Flowbox和New Byte Order)正在发布许多库(您可以在Hackage上找到它们),因此,如果您不知道要开发什么,可以随时参与我们的项目–只要您有任何时候就给我发电子邮件想要(邮件可在luna-lang.org上找到)。


4
“很多人都说,如果在Haskell中编译了某些东西,它就可以工作,并且您应该确定有朝一日它不会在您的脸庞上冒出来。”:我仅将Haskell用于小型(即使非平凡的)项目。 ,但我可以确认这一点:一旦满足了Haskell编译器的要求,我几乎找不到任何bug。
Giorgio

1
我可能是最讨厌类型的“ Haskell”人之一(毕竟我写了我自己的Haskell版本,但没有类型)-但出于不同的原因,大多数人都这样做。在软件工程方面,我也有一种感觉,当GHC高兴时,您的程序就可以正常工作。类似Haskell的类型系统是最终的工具,不仅可以检测编程中的人为错误,还可以控制庞大的代码库。(我总是记得乔凡尼在每次必须修理错误型的时候都对梅沃特的装甲辩护。)
MaiaVictor

Gabriel Gonzales的博客是最佳入门。
sam boosalis '16

@ danilo2已将电子邮件发送到您公司网站上的contact @地址。要求您回复。谢谢!
Saurabh Nanda

@SaurabhNanda:我仔细检查了我们的收件箱,没有看到任何消息。您是否已将其发送给联系<at> luna-lang.org?请直接与我联系,写信给我的wojciech <dot> danilo <at> gmail <dot> com,我们将调查引起此问题的原因:)
danilo2 '16

17

好吧,弱类型和强类型的定义很模糊。此外,由于最接近“强类型”的通用用法是指难以转换类型的事物,因此不再进一步描述更强大的类型系统。这就像在说,如果您的负重不超过30磅,则说明您很虚弱,而每个可以举更多的人都属于“强”类别-这是一种误导性区分。

所以我更喜欢定义:

  • 弱类型系统使用类型来阻止您执行某些操作(例如错误)
  • 强类型系统使用类型为您做事

我对您有帮助是什么意思?好吧,让我们研究一下在Servant框架中编写图像转换API(在Haskell中,但是您实际上并不需要知道它,您将会看到...)

{-# LANGUAGE
    TypeOperators,
    DataKinds
    #-}

import Codec.Picture
import Data.Proxy
import Network.Wai.Handler.Warp (run)
import Servant
import Servant.JuicyPixels

main :: IO ()
main = run 8001 conversion

这就是说,我们希望一些模块包括Servant软件包和Servant的JuicyPixels插件,并且该程序的主要切入点是使用Warp后端在服务器上的端口8001上运行“转换”功能。忽略语言位。

conversion :: Application
conversion = serve (Proxy :: Proxy ConversionApi) handler

也就是说,转换功能是服务器,其中API必须与类型“ ConversionApi”匹配,并且请求由该功能处理 handler

type ConversionApi
     = ReqBody '[BMP, GIF, JPEG 50, PNG, TIFF, RADIANCE] DynamicImage
    :> Post '[BMP, GIF, JPEG 50, PNG, TIFF, RADIANCE] DynamicImage

这是指定ConvesionApi类型。它说我们应该接受列表'[BMP,GIF,JPEG 50,PNG,TIFF,RADIANCE]指定的传入内容类型,并将它们作为DynamicImage处理,并且我们应该返回转换为相同内容范围的DynamicImage类型。不必担心:>意味着什么,现在就将其视为快乐的魔法。

因此,根据我的首选定义,弱类型系统现在可以确保类似:

  • 您不会返回错误的外发内容类型
  • 您不会将传入的请求解析为错误的内容类型
  • 如果我们的服务器更为复杂,它将阻止我们创建格式错误的URI,但是实际上我们并未返回任何包含链接的HTML页面(而且类型确保我们不能这样做!)
  • 一个真正雄心勃勃的弱类型系统甚至可能检查以确保我们详尽地处理所有传入和传出的内容类型,从而使该类型还可以用作规范文档,而不仅仅是约束。

考虑到上述定义,所有崇高目标,但实际上不足以成为强类型系统。现在我们去的符合此规范的实际编写代码的一部分。在一个非常强大的类型系统中,我们编写:

handler = return

然后我们完成了。就这样,不再需要编写任何代码。这是一个完全可操作的Web服务器(以我错失的任何错字为模)。该类型已告知编译器根据我们定义和导入的类型和包(技术上为模块)创建Web服务器所需的一切。

那么,您如何学习在主要应用程序规模上做到这一点呢?嗯,与在较小规模的应用程序中使用它们并没有太大区别。绝对类型不关心编写与它们有关的多少代码。

运行时类型检查是您可能要避免的事情,因为这样做可以消除大量的好处,并允许类型使您的项目更复杂地工作,而不是使类型简化。

因此,这基本上只是练习用类型对事物建模的问题。建模事物(或一般构建事物)的两种主要方法是自下而上和自上而下。自上而下的操作从最高级别的操作开始,并且在构建模型时,会有一部分将建模推迟到以后。自下而上的建模意味着您从基础操作开始,就像从基础功能开始一样,然后构建越来越大的模型,直到您完全掌握了项目的操作。自下而上更具体,构建起来可能更快,但自上而下可能会更好地告知您的下层模型其实际行为方式。

实际上,类型是程序与数学之间的关系,因此,对于程序的复杂程度或您可以“完成”对其的了解,实际上并没有上限。实际上,高等大学课程以外的所有资源都专门用于类型如何以某种特定语言工作,因此您也需要做出决定。

尽我所能,类型可以像这样分层:

  • 类型很弱,例如JavaScript,其中定义了[] + {}
  • 像Python这样的弱类型,您无法在其中进行[] + {},但是直到尝试
  • 像C或Java这样的弱类型,您无法执行[] + {},但是在编译时会进行检查,但是您没有更高级的类型功能
  • 跨越弱类型和强类型之间的边界,例如C ++模板元编程和更简单的Haskell代码,其中类型仅用于强制属性。
  • 完全进入强类型化,例如更复杂的Haskell程序,其中类型执行操作,如上所示
  • 类型非常强的类型(例如Agda或Idris),其中类型和值相互作用并且可以相互约束。这与类型系统一样强大,并且在其中进行编程与编写有关程序功能的数学证明相同。注意:在Agda中编码不是按字面意义写数学证明,类型是数学理论,具有这些类型的函数是证明这些理论的构造性例子。

通常,此列表越靠后,各种类型就可以为您提供更多帮助,但是到最后,您正爬入平流层,空气变得越来越稀薄-包装生态系统要小得多,与找到相关的库相比,您必须自己编写更多的东西。随着您的深入,进入障碍也越来越高,因为您必须真正了解类型系统才能编写大型程序。


请注意,如果列表中包含多个元素,则无需在类型级别列表中加上撇号。单引号仅对于0或1元素类型级别的列表是必需的,因为它们是模棱两可的(它们可能是指普通列表类型的构造函数)
Gabriel Gonzalez 2015年

1
我知道,但是给人的印象是撇号通常只是提升数据类型的一种好形式。EG Proxy :: Proxy True有效,但是最好将其编写为Proxy :: Proxy 'True
史蒂文·阿姆斯特朗

1
最好不要根据可分类的类型系统折叠各个轴。Barendregt的lambda多维数据集已经包括三个,此外,沿着单个弱强轴,您还包括静态与动态,参数与临时多态性(后者是使仆人技术使类型能够完成大部分工作的方法) ')。
user2141650

1
公平的说法,但是答案已经很长了,在lambda多维数据集真正开始有意义之前,您必须很好地学习类型理论。此外,在非常特殊的语言之外(并且由于我已经包括Haskell甚至Agda,我的意思确实是相当特殊的语言),所以您不会真正找到一种语言,例如具有依赖类型但没有类型运算符。
史蒂文·阿姆斯特朗

您能否简要解释一下'[xs]语法?这显然不是一个Char,但我看不出任何TypeOperatorsDataKinds使一种语法。是某种准报价吗?
wchargin

10

我刚刚开始使用Scala编写的大型平台的核心团队。您可以查看成功的开源应用程序,例如Scalatra,Play或Slick,以了解它们如何处理有关与动态数据格式进行交互的一些更详细的问题。

我们发现,Scala的强类型的最大优势之一在于用户培训。核心团队可以在类型系统中制定决策并执行这些决策,因此,当其他对设计原理不太熟悉的团队不得不与系统进行交互时,编译器会对其进行更正,而核心团队将不会不断对其中的内容进行更正。拉取请求。在大型系统中,这是一个巨大的优势。

当然,不是所有的设计原则都可以在类型系统中强制执行,但是类型系统越强大,可以在编译器中强制执行的设计原则就越多。

我们还可以使用户更轻松。通常,他们只是使用常规集合或案例类,而我们正在将其转换为JSON或网络传输所需的任何自动形式。

强类型输入还有助于区分未经过消毒的输入和未经过消毒的输入,从而有助于提高安全性。

强类型化还可以帮助您的测试更加专注于实际行为,而不需要一堆只测试您的类型的测试。它使测试更加愉快,更加集中,因此更加有效。

主要缺点是对语言和语言范式不熟悉,并且可以随时间纠正。除此之外,我们发现值得付出努力。


8

虽然不是直接答案(因为我尚未在haskell中使用+30.000 LOC代码库:( ..),但请您查看https://www.fpcomplete.com/business/resources/case-studies /在实际行业环境中包含大量的haskell案例研究。

另一篇好文章是IMVU,它描述了他们改用haskell的经历-http: //engineering.imvu.com/2014/03/24/what-its-like-to-use-haskell/

在较大的应用程序的个人经验,类型的系统非常多的帮助你,特别是如果你尝试编码尽可能多的,你可以在类型。当涉及到事物重构时,真正的力量确实很明显-意味着维护等工作变得不那么令人担忧。

我将转储指向我推荐的资源的几个链接,因为您一次要问很多问题:

作为结束语,与外界打交道的方法有多种。有一些库可确保您端的内容是类型安全的,例如Aeson用于JSON,Esqueleto用于SQL等。


1
感谢您提供答案,但是fpcomplete.com/business/resources/case-studies上的案例研究不再可用。
Saurabh Nanda

案例研究似乎
Steven Shaw

3

我所看到的:

我已经工作了一些大型的Ruby Web应用程序(Rails),一个大型的Haskell Web应用程序和几个较小的Web应用程序。凭着这种经验,我不得不说,在维护和降低学习曲线等方面,Haskell应用程序的工作要比Rails轻松得多。我认为这些好处都归功于Haskell的类型系统和函数式编程风格。但是,与许多其他人不同,我认为类型系统的“静态”部分只是极大的便利,因为使用动态协定仍然有好处。

我相信

有一个不错的软件包,称为Contracts Ruby,它提供了一些主要功能,我认为这些功能可以帮助Haskell项目获得更好的维护特性。Contracts Ruby在运行时执行检查,因此最好与高测试收敛性配合使用,但它仍提供与在Haskell等语言中使用类型注释相同的内联文档以及意图和含义的表达。

回答问题

为了回答上面提出的问题,在许多地方可以使用高级类型系统来熟悉Haskell和其他语言。但是,老实说,虽然这些文档来源本身非常出色,但是与在Ruby,Python,Java和其他此类语言中找到的大量文档和实用建议相比,它们似乎都有些不足。无论如何,Real World Haskell会变老,但仍然是很好的资源。

范畴论

如果选择Haskell,您会遇到大量讨论类别理论的文献。恕我直言类别理论是有用的,但不是必需的。考虑到它在Haskell社区中的普遍性,很容易将类型的优缺点与对类别理论的实用性的感觉混为一谈。记住它们是两件事是有帮助的,也就是说,以类别理论为指导的实现既可以使用动态类型的语言也可以使用静态类型的语言(以类型系统提供的好处为模)来完成。一般而言,高级类型系统不受类别理论的约束,并且类别理论也不受类型系统的约束。

有关类型的更多信息

当您了解更多有关使用类型和其中的技术进行编程(之所以会很快发生,因为它很有趣)的知识时,您将要用类型系统表达更多。在这种情况下,我将研究以下一些资源,并与我一起使工具供应商意识到我们希望将具有这些功能的工业级质量工具仅打包到公开易于使用的界面中(例如Contracts Ruby):


2

首先,我觉得在弱类型与强类型之间以及静态与动态类型之间,答案存在混淆。链接提供的OP清楚地区分:

强类型系统是一种具有编译时限制或运行时功能的类型系统,您会发现它很吸引人。

弱类型系统是缺少该限制或功能的类型系统。

例如,C,C ++和Java是静态类型的,因为变量是在编译时键入的。但是,由于C和C ++允许使用void *指针和强制类型转换来绕过限制,因此可以将其视为弱类型。有关此主题的更多信息。

根据这种区别,强类型只能更好。失败越早越好。

但是,在编写大型程序时,我认为类型系统不起作用。Linux内核是用C和汇编语言编写的1000万个LOC,被认为是一个非常稳定的程序,它与我的200条可能充满安全漏洞的Java行相距甚远。同样,尽管动态类型的“脚本语言”在编写大型程序时声誉不佳,但偶尔有证据表明它不配(例如Python Django,LOC超过70k)

我认为,这全都与质量标准有关。大型应用程序的可伸缩性责任仅应由程序员和架构师及其将应用程序进行整洁,测试和记录良好的意愿。


-1

在哪里可以读到如何在大型应用程序中使用这些功能强大的类型系统和静态/编译时功能?

来自先前的答案https://www.fpcomplete.com/business/resources/case-studies/

如何超越标准的“ hello world”对这些强大功能的介绍?

就像其他任何语言一样可以增强实力

一个人如何使用富类型系统来建模业务域问题?

通过使用抽象数据类型或更普遍的多态

当您位于30,000 LOC +区域中时,类型系统会帮助还是阻碍?

它会一直提供帮助。类型系统可以帮助您编写代码,因为它告诉您所需结果的形状。实际上,Agda可以为您编写代码。

PS:不要犯一个拥有类型系统和自己写类型的错误,这是愚蠢的:计算机可以为您做到。

当您的系统与弱类型的外部环境交互时,这些类型的系统(和编译时检查)所提供的安全网会如何处理?通过JSON或XML API,各种数据存储,用户输入等。

太好了,因为通过类型知道值的结构意味着计算机可以推断出一种为您编写(反)序列化器的方法。

如果您真的想了解类型和抽象,那么最好的介绍是关于了解类型,数据抽象和多态性。

这是一篇论文,而不是带有漂亮图片的案例研究,但它很有启发性


-2

作为经常在Web应用程序上工作的.net程序员,我同时看到类型化的C#和未类型化的Javascript方面。

我不确定我是否看过任何有关您所提问题的文献。使用打字语言,您将所有这些事情视为理所当然。使用无类型的类型,您会看到将类型定义为不必要的开销。

就个人而言,我认为您不能否认强类型语言可以以非常低的成本(与编写等效的单元测试相比)提供您所描述的好处。与弱类型系统进行交互通常涉及通用类型,例如数组或对象字典,例如DataReader,或者创造性地使用字符串或新的动态类。从本质上讲,所有方法都有效,您只会遇到运行时错误而不是编译时错误。

如果您想编写一个很短的程序,也许要用几行代码定义一个函数以与较大的应用程序一起工作,那么您根本就没有空间声明类。难道这就是像JS这样的利基未类型化语言所占据的位置吗?


您是指输入/未输入还是静态/动态输入?JavaScript不是未键入的,而是动态键入的。
乔治

2
您不必定义类型。任何体面的语言都会为您推断类型:haskell,ocaml,sml,F#...
nicolas 2015年

1
当您习惯于强类型语言时,其他所有内容都不会被类型化,当我说定义类型时,我的意思是“创建类”,确保可以使用var或其他任何方法,但这只是编译器的trick俩
Ewan 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.