Scala类型推导,弱类型编程语言

翻译自:

之剑

注:以下相对个人意见。

2016.5.1 00:38:12

设若本人做三个简练的次序,作者不关注该选用强类型语言照旧弱类型语言。不过只要本人的程序很复杂,何况须求由四人举办爱抚,那么作者决然会选用强类型语言。

怎么是静态类型?为何它们很有用?

依照Picrce的说法:“类型系统是贰个得以依赖代码段总计出来的值对它们进行分类,然后经过语法的手段来自动物检疫查测量试验程序不当的种类。”

花色能够让您意味着函数的域和值域。举例,在数学里,我们平日见到上边包车型大巴函数:

f: R -> N

本条概念告诉大家函数”f”的功用是把实数集里的数映射到自然集里。

抽象地说,这才是具体意思上的品种。类型系统给了大家有的表示这个聚集的更加强硬的点子。

有了那些项目的志,编写翻译器现在能够 静态地认清这几个顺序是不利的。

换句话说,倘使三个值不能知足程序里的限制条件的话,那么在编译期就能够出错。

万般来讲,类型检验器(typechecker)只可以够保险不精确的的次第不能够经过编写翻译。然而,它不可见保障具备科学的程序都能透过编写翻译。

是因为品种系统的表明技艺不断充实,使得大家能够生成出特别可信的代码,因为它使得大家能够决定程序上的不可变,就是是前后相继还平素不运转的地方下(在类型上限制bug的出现)。学术界一直在力图突破项目系统的表明技巧的顶峰,包涵值相关的档案的次序。

潜心,全数的类型音讯都会在编写翻译期擦除。前面不再必要。那个被堪当类型擦除。举个例子,Java里面包车型客车泛型的实现.

几时用强类型语言当今,多数今世的编制程序语言都帮助项目推导(type
inference),由此强类型语言代码看起来和弱类型语言的代码同样轻易。诸如Scala/Haskell/OCaml/Kotlin/ELM等强类型语言既简便易行又不行文雅。

Scala中的类型

Scala庞大的品类系统让我们得以采纳更兼具展现力的表达式。一些注重的性状如下:

  • 支撑参数多态,泛型编制程序

  • 支撑项目推导,那正是你干吗无需写val i: Int = 12: Int

  • 支撑存在向量(existential
    quantification),给一部分从未有过名称的类型定义一些操作

  • 支撑视图。 给定的值从三个品种到其余品种的“可调换性”

多态能够用来编排泛型代码(用于拍卖不一样类型的值),并且不会减价扣静态类型的表明才干。

譬喻说,未有参数多态的话,一个泛型的列表数据结构常常会是下边那样的写法(在Java还未曾泛型的时候,确实是这么的):

scala> 2 :: 1 :: "bar" :: "foo" :: Nilres5: List[Any] = List(2, 1, bar, foo)

这样的话,大家就无法苏醒各样成分的类型音信。

scala> res5.headres6: Any = 2

那样一来,大家的选取里会蕴藏一种种的类型调换(“asInstanceOf[]“),代码会缺点和失误类型安全(因为她俩都以动态类型的)。

多态是透过点名项目变量来达成的。

scala> def drop1[A](l: List[A]) = l.taildrop1: [A](l: List[A])List[A] scala> drop1(Listres1: List[Int] = List

曾几何时用弱语言类型我们依然亟待编写翻译代码,并且那也亟需耗费时间,可是本身感到编写翻译专门的学业是值得的。因为代码的维护和掌握也须求耗时。相比较于消除bug和读书代码所急需消耗的数小时时间,作者感觉开支数秒去开展编写翻译代码是值得的。

多态是scala里的一等国民

总结的话,那意味着有部分您想在Scala里发挥的档期的顺序概念会显得“太过度泛型”,进而导致编写翻译器不能了解。所有的连串变量在运行期必需是明显的。

对此静态类型的三个相比遍布的症结正是有太多的花色语法。Scala提供了种类推导来消除那几个难点。

函数式语言里相比美貌的品类推导的点子是
Hindlry-Milner,况兼它是在ML里首先应用的。

Scala的花色推导有一小点见仁见智,不过思想上是一致的:推导全数的自律标准,然后统一到多个品种上。

在Scala里,比方,你无法这么写:

scala> { x => x }:7: error: missing parameter type { x => x }

但是在OCaml里,你可以:

# fun x -> x;;- : 'a -> 'a =

在Scala里,全部的花色推导都以某些的。Scala三回只思量贰个表达式。例如:

scala> def id[T] = xid: [T]T scala> val x = idx: Int = 322 scala> val x = idx: java.lang.String = hey scala> val x = id(Arrayx: Array[Int] = Array(1, 2, 3, 4)

在这里,类型都被埋伏了。Scala编写翻译器自动推导参数的门类。注意大家也尚未要求展现钦命重返值的项目了。

品种推导+强类型=>简练,可读性强的代码!

型变

Scala的类型系统须要把类的接续关系和多态结合起来。类的承继使得类之间存在老爹和儿子的关联。当把面向对象和多态结合在一块时,两个基本的主题材料就出去了:假若T’是T的子类,那么Container[T’]是不是Container[T]的子类呢?Variance注释允许你在类承接和多态类型之间表达上边的这个涉及:

<table><tr><td></td><td>含义</td><td>Scala中的标识</td></tr><tr><td>covariant</td><td>
C[T’]是C[T]的子类</td><td>[+T]</td></tr><tr><td>contravariant</td><td>C[T]是C[T’]子类</td><td>[-T]</td></tr><tr><td>invariant</td><td>
C[T]和C[T’]不相关</td><td>[T]</td></tr></table>

子类关系的真的意思是:对于贰个加以的类型T,若是T’是它的子类,那么T’可以代替T吗?

scala> class Contravariant[-A]defined class Contravariant scala> val cv: Contravariant[String] = new Contravariant[AnyRef]cv: Contravariant[AnyRef] = Contravariant@49fa7ba scala> val fail: Contravariant[AnyRef] = new Contravariant[String]:6: error: type mismatch; found : Contravariant[String] required: Contravariant[AnyRef] val fail: Contravariant[AnyRef] = new Contravariant[String] 

项目推导的质量瓶颈咱俩为编写翻译代码付出耗费时间的代价。如在此之前涉嫌的平等,小编赞成于在编写翻译时开销少些年华并非在阅读与领会代码时开销越来越多的年华。

量化(Quantification)

不经常你不供给给二个品种变量以名称,举例

scala> def count[A](l: List[A]) = l.sizecount: [A]Int

你能够用“通配符”来顶替:

scala> def count(l: List[_]) = l.sizecount: Int

先看个代码:

Map<Integer, Map<String, String>> m = new HashMap<Integer, Map<String, String>>(); 

是呀,
那简直太长了,大家不禁惊叹,那编写翻译器也太古板了.差相当少一半字符都以双重的!

本着泛型定义和实例太过繁琐的主题素材,在java 7 中引进了金刚石运算符.
奇妙的Coin项目,满意了您的心愿.

于是,你在java 7之后能够如此写了:

Map<Integer, Map<String, String>> m = new HashMap(); 

钻石运算符平时用于简化创立带有泛型对象的代码,能够制止运转时
的要命,而且它不再要求程序员在编码时呈现书写冗余的体系参数。实际上,编写翻译器在进展词法剖判时会自动推导类型,自动为代码实行补全,况且编写翻译的字节码与
在此之前一样。

立时在提案中,那一个难题叫”Improved Type Inference for Generic Instance
Creation”,缩写ITIGIX听上去怪怪的,不过为何叫钻石算法? 世界上,
哪有那么多为何.

Scala就是因为做了等级次序推导, 让Coders认为好像在写动态语言的代码.

在Scala中,高阶函数日常传递无名氏函数.举个栗子:

一段定义泛型函数的代码

def dropWhile[A](list: List[A], f: A => Boolean): List[A]

当大家传入二个无名氏函数f来调用它,

val mylist: List[Int] = List(1,2,3,4,5) val listDropped = dropWhile( mylist,  => x < 4 )

listDropped的值是List

我们用大脑可以随性所欲判别, 当list: List[A]
中的类型A在mylist评释的时候已经钦命了Int, 那么很令人瞩目,
在其次个参数中,大家的x也必是Int.

很幸运Scala设计者们曾经思虑到那点,Scala编写翻译器能够推导这种情形.然则你得服从Scala的标准范围来写你的dropWhile函数的具名:
dropWhile

def dropWhile[A] ( list: List[A] ) ( f: A => Boolean ) : List[A] = list match {case Cons if f => dropWhilecase _ => list}

这么而来,我们就足以一直像上面那样使用那些函数了:

val mylist: List[Int] = List(1,2,3,4,5)val droppedList = dropWhile ( x => x < 4 )

小心, x参数没有一点点名Int类型,
因为编写翻译器直接通过mylist的泛型音讯Int推导出x的连串也是Int.

品种推导是一门博大的知识,背后有繁冗的反驳,
那在编译器设计开荒的时候需求消除的难题.

|Scala|Haskell,ML|

|———|——–|

|局地的、基于流的(flow-based)类型估算|全局化的Hindley-Milner类型臆想|

在《Programming in
Scala》一书中关系基于流的门类测度有它的局限性,可是对于面向对象的分层类型管理比Hindley-Mlner特别名贵。

基于流的系列推导在偏应用函数场景下,不能对参数类型大致

品种推导(Type
Inference)是当代高端语言中三个更加的广泛的天性。其实,这些特点在函数式语言

中早有了普遍应用。而HindleyMilner推导器是兼具类型推导器的基础。

Scala实现的三个简单的HindleyMilner推导器:

 /* * http://dysphoria.net/code/hindley-milner/HindleyMilner.scala * Andrew Forrest * * Implementation of basic polymorphic type-checking for a simple language. * Based heavily on Nikita Borisov’s Perl implementation at * http://web.archive.org/web/20050420002559/www.cs.berkeley.edu/~nikitab/courses/cs263/hm.html * which in turn is based on the paper by Luca Cardelli at * http://lucacardelli.name/Papers/BasicTypechecking.pdf * * If you run it with "scala HindleyMilner.scala" it will attempt to report the types * for a few example expressions. (It uses UTF-8 for output, so you may need to set your * terminal accordingly.) * * Changes * June 30, 2011 by Liang Kun(liangkunbaidu.com) * 1. Modify to enhance readability * 2. Extend to Support if expression in syntax * * * * Do with it what you will. :) */ /** Syntax definition. This is a simple lambda calculous syntax. * Expression ::= Identifier * | Constant * | "if" Expression "then" Expression "else" Expression * | "lambda(" Identifier ") " Expression * | Expression "(" Expression ")" * | "let" Identifier "=" Expression "in" Expression * | "letrec" Identifier "=" Expression "in" Expression * | "(" Expression ")" * See the examples below in main function. */ sealed abstract class Expression case class Identifier(name: String) extends Expression { override def toString = name } case class Constant(value: String) extends Expression { override def toString = value } case class If(condition: Expression, then: Expression, other: Expression) extends Expression { override def toString = "(if " + condition + " then " + then + " else " + other + ")" } case class Lambda(argument: Identifier, body: Expression) extends Expression { override def toString = "(lambda " + argument + " → " + body + ")" } case class Apply(function: Expression, argument: Expression) extends Expression { override def toString = "(" + function + " " + argument + ")" } case class Let(binding: Identifier, definition: Expression, body: Expression) extends Expression { override def toString = "(let " + binding + " = " + definition + " in " + body + ")" } case class Letrec(binding: Identifier, definition: Expression, body: Expression) extends Expression { override def toString = "(letrec " + binding + " = " + definition + " in " + body + ")" } /** Exceptions may happened */ class TypeError(msg: String) extends Exception class ParseError(msg: String) extends Exception /** Type inference system */ object TypeSystem { type Env = Map[Identifier, Type] val EmptyEnv: Map[Identifier, Type] = Map.empty // type variable and type operator sealed abstract class Type case class Variable extends Type { var instance: Option[Type] = None lazy val name = nextUniqueName() override def toString = instance match { case Some => t.toString case None => name } } case class Operator(name: String, args: Seq[Type]) extends Type { override def toString = { if (args.length == 0) name else if (args.length == 2) "[" + args + " " + name + " " + args + "]" else args.mkString(name + "[", ", ", "]") } } // builtin types, types can be extended by environment def Function(from: Type, to: Type) = Operator("→", Array) val Integer = Operator("Integer", Array[Type] val Boolean = Operator("Boolean", Array[Type] protected var _nextVariableName = 'α'; protected def nextUniqueName() = { val result = _nextVariableName _nextVariableName = (_nextVariableName.toInt + 1).toChar result.toString } protected var _nextVariableId = 0 def newVariable(): Variable = { val result = _nextVariableId _nextVariableId += 1 Variable } // main entry point def analyze(expr: Expression, env: Env): Type = analyze(expr, env, Set.empty) def analyze(expr: Expression, env: Env, nongeneric: Set[Variable]): Type = expr match { case i: Identifier => getIdentifierType(i, env, nongeneric) case Constant => getConstantType case If(cond, then, other) => { val condType = analyze(cond, env, nongeneric) val thenType = analyze(then, env, nongeneric) val otherType = analyze(other, env, nongeneric) unify(condType, Boolean) unify(thenType, otherType) thenType } case Apply(func, arg) => { val funcType = analyze(func, env, nongeneric) val argType = analyze(arg, env, nongeneric) val resultType = newVariable() unify(Function(argType, resultType), funcType) resultType } case Lambda(arg, body) => { val argType = newVariable() val resultType = analyze(body, env + (arg -> argType), nongeneric + argType) Function(argType, resultType) } case Let(binding, definition, body) => { val definitionType = analyze(definition, env, nongeneric) val newEnv = env + (binding -> definitionType) analyze(body, newEnv, nongeneric) } case Letrec(binding, definition, body) => { val newType = newVariable() val newEnv = env + (binding -> newType) val definitionType = analyze(definition, newEnv, nongeneric + newType) unify(newType, definitionType) analyze(body, newEnv, nongeneric) } } protected def getIdentifierType(id: Identifier, env: Env, nongeneric: Set[Variable]): Type = { if (env.contains fresh, nongeneric) else throw new ParseError("Undefined symbol: " + id) } protected def getConstantType(value: String): Type = { if(isIntegerLiteral Integer else throw new ParseError("Undefined symbol: " + value) } protected def fresh(t: Type, nongeneric: Set[Variable]) = { import scala.collection.mutable val mappings = new mutable.HashMap[Variable, Variable] def freshrec: Type = { prune match { case v: Variable => if (isgeneric(v, nongeneric)) mappings.getOrElseUpdate(v, newVariable else v case Operator(name, args) => Operator(name, args.map(freshrec } } freshrec } protected def unify(t1: Type, t2: Type) { val type1 = prune val type2 = prune (type1, type2) match { case (a: Variable, b) => if  { if (occursintype throw new TypeError("Recursive unification") a.instance = Some } case (a: Operator, b: Variable) => unify case (a: Operator, b: Operator) => { if (a.name != b.name || a.args.length != b.args.length) throw new TypeError("Type mismatch: " + a + " ≠ " + b) for(i <- 0 until a.args.length) unify, b.args } } } // Returns the currently defining instance of t. // As a side effect, collapses the list of type instances. protected def prune: Type = t match { case v: Variable if v.instance.isDefined => { val inst = prune(v.instance.get) v.instance = Some inst } case _ => t } // Note: must be called with v 'pre-pruned' protected def isgeneric(v: Variable, nongeneric: Set[Variable]) = !(occursin(v, nongeneric)) // Note: must be called with v 'pre-pruned' protected def occursintype(v: Variable, type2: Type): Boolean = { prune match { case `v` => true case Operator(name, args) => occursin case _ => false } } protected def occursin(t: Variable, list: Iterable[Type]) = list exists (t2 => occursintype protected val checkDigits = "^$".r protected def isIntegerLiteral(name: String) = checkDigits.findFirstIn.isDefined } /** Demo program */ object HindleyMilner { def main(args: Array[String]){ Console.setOut(new java.io.PrintStream(Console.out, true, "utf-8")) // extends the system with a new type[pair] and some builtin functions val left = TypeSystem.newVariable() val right = TypeSystem.newVariable() val pairType = TypeSystem.Operator("×", Array(left, right)) val myenv: TypeSystem.Env = TypeSystem.EmptyEnv ++ Array( Identifier -> TypeSystem.Function(left, TypeSystem.Function(right, pairType)), Identifier -> TypeSystem.Boolean, Identifier-> TypeSystem.Boolean, Identifier -> TypeSystem.Function(TypeSystem.Integer, TypeSystem.Boolean), Identifier -> TypeSystem.Function(TypeSystem.Integer, TypeSystem.Integer), Identifier-> TypeSystem.Function(TypeSystem.Integer, TypeSystem.Function(TypeSystem.Integer, TypeSystem.Integer)) ) // example expressions val pair = Apply( Apply( Identifier, Apply(Identifier, Constant, Apply(Identifier, Identifier ) val examples = Array[Expression]( // factorial Letrec(Identifier("factorial"), // letrec factorial = Lambda(Identifier, // lambda n => If( Apply(Identifier, Identifier, Constant, Apply( Apply(Identifier, Identifier, Apply( Identifier("factorial"), Apply(Identifier, Identifier ) ), // in Apply(Identifier("factorial"), Constant, // Should fail: // fn x =>  ) Lambda(Identifier, Apply( Apply(Identifier, Apply(Identifier, Constant, Apply(Identifier, Identifier ) ), // pair, f Apply( Apply(Identifier, Apply(Identifier, Constant, Apply(Identifier, Identifier ), // letrec f = (fn x => x) in ((pair   Let(Identifier, Lambda(Identifier, Identifier, pair), // Should fail: // fn f => f f Lambda(Identifier, Apply(Identifier, Identifier, // let g = fn f => 5 in g g Let( Identifier, Lambda(Identifier, Constant, Apply(Identifier, Identifier, // example that demonstrates generic and non-generic variables: // fn g => let f = fn x => g in pair (f 3, f true) Lambda(Identifier, Let(Identifier, Lambda(Identifier, Identifier, Apply( Apply(Identifier, Apply(Identifier, Constant, Apply(Identifier, Identifier ) ) ), // Function composition // fn f (fn g (fn arg ) Lambda( Identifier, Lambda( Identifier, Lambda( Identifier, Apply(Identifier, Apply(Identifier, Identifier ) ) ) ) for(eg <- examples){ tryexp(myenv, eg) } } def tryexp(env: TypeSystem.Env, expr: Expression) { try { val t = TypeSystem.analyze(expr, env) print }catch{ case t: ParseError => print(t.getMessage) case t: TypeError => print(t.getMessage) } println(":t" + expr) } } HindleyMilner.main

Haskell写的贰个 合一算法的简易完毕:

module Main where import Data.List (intersperse) import Control.Monad -- utils -- mapFst :: (a -> b) ->  ->  mapFst f  =  -- types -- type Name = String data Term = Var Name | App Name [Term] -- 表示一个替换关系 type Sub = (Term, Name) -- implementation -- -- 检查变量 Name 是否出现在 Term 中 occurs :: Name -> Term -> Bool occurs x t = case t of  -> x==y  -> and . map  $ ts -- 使用 Sub 对 Term 进行替换 sub :: Sub -> Term -> Term sub  t@ | a==y = t1 | otherwise = t sub s  = App f $ map  ts -- 使用 Sub 列表对 Term 进行替换 subs :: [Sub] -> Term -> Term subs ss t = foldl  t ss -- 把两个替换列表组合起来,同时用新加入的替换对其中所有 Term 进行替换 compose :: [Sub] -> [Sub] -> [Sub] compose [] s1 = s1 compose  s1 = compose ss $ s : iter s s1 where iter :: Sub -> [Sub] -> [Sub] iter s ss = map (mapFst  ss -- 合一函数 unify :: Term -> Term -> Maybe [Sub] unify t1 t2 = case  of (Var x, Var y) -> if x==y then Just [] else Just [] (Var x, App _ _) -> if occurs x t2 then Nothing else Just [] (App _ _, Var x) -> if occurs x t1 then Nothing else Just [] (App n1 ts1, App n2 ts2) -> if n1/=n2 then Nothing else unify_args ts1 ts2 where unify_args [] [] = Just [] unify_args _ [] = Nothing unify_args [] _ = Nothing unify_args   = do u <- unify t1 t2 let update = map  u1 <- unify_args (update ts1) (update ts2) return (u1 `compose` u) -- display -- instance Show Term where show  = s show (App name ts) = name++"("++(concat . intersperse "," $ (map show ts))++")" showSub  = s ++ " -> " ++ show t -- test cases -- a = Var "a" b = Var "b" c = Var "c" d = Var "d" x = Var "x" y = Var "y" z = Var "z" f = App "f" g = App "g" j = App "j" test t1 t2 = do putStrLn $ show t1 ++ " <==> " ++ show t2 case unify t1 t2 of Nothing -> putStrLn "unify fail" Just u -> putStrLn $ concat . intersperse "n" $ map showSub u testcases = [(j [x,y,z], j [f [y,y], f [z,z], f [a,a]]) , , ,(f [a, f [b, c], g [b, a, c]], f [a, a, x]) ,(f [d, d, x], f [a, f [b, c], f [b, a, c]]) ] main = forM testcases (uncurry test) 

困难掌握的次序编写翻译也会相当慢。

之所以,假设您的代码可读性强,编译也能块相当多。

参数化多态

参数化多态:定义相同子类型的器皿。译者注:参数化多态是指编码能够不点名别的特定项目,在被实例化作为参数时才去明确项目。在面向对象编制程序中,称之为generic
programming,而在函数式编制程序中,则被简称为polymorphise。

定义Array[A],大家得以有本人的兑现方式并对数组进行操作和利用。

子类型多态由此继承类来实现多样达成,可是那会导致大目的的产生。作者认为那会招致代码泥团。译者注:代码泥团是指三个随便化的繁杂的结构化系统,只是代码的堆砌和拼接,往往会导致众多荒谬只怕缺欠。

发表评论

电子邮件地址不会被公开。 必填项已用*标注