编程语言中的6种有趣功能

Java是我专业学习和使用的第一门语言。 大约十年半以来,这一直是我的生计。 但是,这并不是我多年来学习过的唯一语言,例如,很久以前,我不得不开发JavaScript代码来实现动态用户界面。 当时,它被称为DHTML ....几年前,我还自学了Kotlin,从未停止使用它。 去年,在一家新公司工作时,我尝试了Clojure-尽管效果不佳。

在所有情况下,Java仍然代表着我学习和判断其他语言的基准。 这是语言的一些有趣特征,我发现这些特征来自Java背景,颇具挑战性。

JavaScript:原型

JavaScript是我必须与Java一起使用的第一语言。 尽管JavaScript在过去的这些年里一直在发展,但其中一个共同的功能却非常奇怪地实现了:新对象的实例化。

在Java中,首先创建一个

public class Person {

  private final String name ;
  private final LocalDate birthdate ;

  public Person ( String name , LocalDate birthdate ) {
    this . name = name ;
    this . birthdate = birthdate ;
  }

  public String getName () {
    return name ;
  }

  public LocalDate getBirthdate () {
    return birthdate ;
  }
}

然后,可以继续创建该类的实例

var person1 = new Person ( "John Doe" , LocalDate . now ());
var person2 = new Person ( "Jane Doe" , LocalDate . now ());

JavaScript与Java语法非常相似:

class Person {
  constructor ( name , birthdate ) {
    this . name = name ;
    this . birthdate = birthdate ;
  }
}

let person1 = new Person ( " John Doe " , Date . now ());
let person2 = new Person ( " Jane Doe " , Date . now ());

平行线在那里停止。 得益于JavaScript动态的特性,可以向现有实例添加属性和函数。

person1 . debug = function () {
  console . debug ( this );
}

person1 . debug ();

但是,这些仅添加到实例。 其他实例缺少这些补充:

person2 . debug ();  // Throws TypeError: person2.debug is not a function

为了向现在和将来的所有实例添加函数(或属性),需要利用原型的概念:

Person . prototype . debug = function () {
  console . debug ( this );
}

person1 . debug ();
person2 . debug ();

let person3 = new Person ( " Nicolas " , Date . now ());

person3 . debug ();

Kotlin:扩展功能/属性

几年前,我尝试自学Android。 我发现这种体验并不十分适合开发人员:当然,我了解目标之一是尽可能减小占用空间,但这是以非常简洁的API为代价的。

我记得必须调用带有很多参数的方法,其中大多数为null 。 我试图找到一种解决该问题的方法...并找到Kotlin的扩展属性-具有默认参数。 我停止了学习Android的道路,但继续使用Kotlin。

我爱Kotlin。 许多人赞扬Kotlin的零安全方法。 我喜欢它,但对我来说,最高的价值在于其他地方。

假设我们经常需要大写字符串。 在Java中实现该目标的方法是使用静态方法创建一个类:

public class StringUtils {

  public static String capitalize ( String string ) {
    var character = string . substring ( 0 , 1 ). toUpperCase ();
    var rest = string . substring ( 1 , string . length () - 1 ). toLowerCase ();
    return character + rest ;
  }
}

在早期,没有一个没有StringUtilsDateUtils类的项目。 幸运的是,现在,现有的库提供了最需要的功能, 例如 Apache Commons LangGuava 。 但是,它们遵循基于静态方法的相同设计原理。 令人遗憾的是,因为Java被认为是一种OOP语言。 不幸的是,静态方法不是面向对象的。

Kotlin允许在扩展函数和属性的帮助下向现有类分别添加行为。 语法非常简单,并且与面向对象的方法完全兼容:

fun String . capitalize (): String {
  val character = substring ( 0 , 1 ). toUpperCase ()
  val rest = substring ( 1 , length - 1 ). toLowerCase ()
  return character + rest
}

在编写Kotlin代码时,我经常使用它。

在后台,Kotlin编译器生成的字节码类似于Java代码中的字节码。 它只是“唯一的”语法糖,但是从设计的角度来看,与Java代码相比,这是一个巨大的改进!

转到:隐式接口实现

在大多数OOP语言(Java,Scala,Kotlin等)中,类可以遵守合同,也称为接口 。 这样,客户端代码可以引用该接口,而不关心任何特定的实现。

public interface Shape {

  float area ();
  float perimeter ();

  default void display () {
    System . out . println ( this );
    System . out . println ( perimeter ());
    System . out . println ( area ());
  }
}

public class Rectangle implements Shape {

  public final float width ;
  public final float height ;

  public Rectangle ( float width , float height ) {
    this . width = width ;
    this . height = height ;
  }

  @Override
  public float area () {
    return width * height ;            (1)
  }

  @Override
  public float perimeter () {
    return 2 * width + 2 * height ;    (1)
  }

  public static void main ( String ... args ) {
    var rect = new Rectangle ( 2.0f , 3.0f );
    rect . display ();
  }
}
  1. 一个人应该使用BigDecimal来达到精确的目的-但这不是重点

重要的一点是:由于Rectangle实现Shape ,因此可以在Rectangle任何实例上调用Shape上定义的display()方法。

Go不是一种OOP语言:它没有类的概念。 它提供结构 ,并且功能可以与这种结构相关联。 它还提供了结构可以实现的接口

但是,Java实现接口的方法是明确的Rectangle类声明其实现Shape 。 相反,Go的方法是隐式的 。 实现接口所有功能的结构隐式实现此接口。

这将转换为以下代码:

package main

import (
 "fmt"
)

type shape interface {                        (1)
 area () float32
 perimeter () float32
}

type rectangle struct {                       (2)
 width float32
 height float32
}

func ( rect rectangle ) area () float32 {         (3)
 return rect . width * rect . height
}

func ( rect rectangle ) perimeter () float32 {    (3)
 return 2 * rect . width + 2 * rect . height
}

func display ( shape shape )  {                  (4)
 fmt . Println ( shape )
 fmt . Println ( shape . perimeter ())
 fmt . Println ( shape . area ())
}

func main () {
 rect := rectangle { width : 2 , height : 3 }
 display ( rect )                                (5)
}
  1. 定义shape界面
  2. 定义rectangle结构
  3. 将两个shape函数都添加到rectangle
  4. display()函数仅接受shape
  5. 因为rectangle实现了shape所有功能,并且由于隐式实现,所以rect也是shape 。 因此,调用display()函数并将rect作为参数传递是完全合法的

Clojure:“依赖类型”

我以前的公司在Clojure投入了很多资金。 因此,我尝试学习该语言,甚至写了几篇文章来总结我对语言的理解。

Clojure在很大程度上受到LISP的启发。 因此,表达式用括号括起来,要执行的功能首先位于它们的内部。 另外,Clojure是一种动态类型化的语言:虽然有类型,但它们并未声明。

一方面,该语言提供了基于合同的编程。 可以指定前置条件和后置条件:它们在运行时进行评估。 这样的条件可以进行类型检查, 例如,参数是字符串,布尔值等吗? -甚至可以更进一步,类似于_dependent类型

在计算机科学和逻辑中,从属类型是其定义取决于值的类型。 “整数对”是一种类型。 由于对值的依赖性,“第二对大于第一对的整数对”是从属类型。

—维基百科
https://zh.wikipedia.org/wiki/Dependent_type

它是在运行时强制执行的,因此不能真正称为依赖类型。 但是,这是我所涉猎的语言中最接近的一种。

之前,我曾详细地写过一个依赖于岗位的类型和基于契约的程序。

长生不老药:模式匹配

一些语言自称为提供模式匹配功能。 通常,模式匹配用于评估变量, 例如在Kotlin中:

var statusCode : Int
val errorMessage = when ( statusCode ) {
  401 -> "Unauthorized"
  403 -> "Forbidden"
  500 -> "Internal Server Error"
  else -> "Unrecognized Status Code"
}

此用法是类固醇的转换语句。 但是,通常,模式匹配的应用范围更加广泛。 在以下代码段中,将检查第一个常规的HTTP状态错误代码,如果未找到,则默认为更通用的错误消息:

val errorMessage = when {
  statusCode == 401 -> "Unauthorized"
  statusCode == 403 -> "Forbidden"
  statusCode - 400 < 100 -> "Client Error"
  statusCode == 500 -> "Internal Server Error"
  statusCode - 500 < 100 -> "Server Error"
  else -> "Unrecognized Status Code"
}

不过,它是有限的。

Elixir是一种在Erlang OTP上运行的动态类型化语言,将模式匹配提升到了一个全新的水平。 Elixir的模式匹配可用于简单的变量解构:

{ a , b , c } = { :hello , "world" , 42 }

将为a分配:hello ,为b “ world”分配c

它也可以用于集合的更高级解构:

[ head | tail ] = [ 1 , 2 , 3 ]

head将被分配为1,而tail将被分配为[2, 3]

然而,对于函数重载甚至更是如此。 作为一种功能语言,Elixir没有用于循环的关键字( forwhile ):循环需要使用递归来实现。

例如,让我们使用递归来计算List的大小。 在Java中,这很容易,因为有一个size()方法,但是Elixir API没有提供这种功能。 让我们以Elixir方式伪代码实现它-使用递归。

public int lengthOf ( List <?> item ) {
  return lengthOf ( 0 , items );
}

private int lengthOf ( int size , List <?> items ) {
  if ( items . isEmpty ()) {
    return size ;
  } else {
    return lengthOf ( size + 1 , items . remove ( 0 ));
  }
}

这可以几乎逐行转换为Elixir:

def length_of ( list ), do : length_of ( 0 , list )

defp length_of ( size , list ) do
  if [] == list do
    size
  else
    [ _ | tail ] = list            (1)
    length_of ( size + 1 , tail )
  end
end
  1. 模式匹配与可变解构。 头值分配给_变量,这意味着以后无法引用它-因为它没有用。

但是,如前所述,Elixir模式匹配也适用于函数重载。 因此,写Elixir的名义方法是:

def list_len ( list ), do : list_len ( 0 , list )

defp list_len ( size , []), do : size         (1)
defp list_len ( size , list ) do              (2)
  [ _ | tail ] = list
  list_len ( size + 1 , tail )
end
  1. 如果列表为空,则调用此函数
  2. 否则调用此函数

请注意,模式是按照声明的顺序进行评估的:在上面的代码段中,Elixir首先评估具有空列表的函数,并且仅在不匹配的情况下评估第二个函数, 列表不为空。 如果要以相反的顺序声明函数,则每次都会在非空列表上进行匹配。

Python:理解

Python是一种动态类型的趋势语言。 与Java中一样,Python通过for关键字提供循环。 以下片段循环遍历集合中的所有项目,并逐一打印它们。

for n in [ 1 , 2 , 3 , 4 , 5 ]:
  print ( n )

要收集新集合中的所有项目,可以创建一个空集合,然后将每个项目添加到循环中:

numbers = []
for n in [ 1 , 2 , 3 , 4 , 5 ]:
  numbers . append ( n )
print ( numbers )

但是,可以使用漂亮的Python功能: 进行理解 。 尽管它与标准循环使用相同的for关键字,但是for comprehension是实现相同结果的功能构造。

numbers = [ n for n in [ 1 , 2 , 3 , 4 , 5 ]]
print ( numbers )

上一个代码段的输出为[1, 2, 3, 4, 5]

也可以变换每个项目。 例如,以下代码片段将计算每个项目的平方:

numbers = [ n ** 2 for n in [ 1 , 2 , 3 , 4 , 5 ]]
print ( numbers )

输出[1, 4, 9, 16, 25]

理解的好处是可以使用条件句。 例如,以下代码段将仅过滤偶数项,然后对其平方:

numbers = [ n ** 2 for n in [ 1 , 2 , 3 , 4 , 5 ] if n % 2 == 0 ]
print ( numbers )

输出为[4, 16]

最后,为了理解,允许使用笛卡尔积。

numbers = [ a : n for n in [ 1 , 2 , 3 ] for a in [ 'a' , 'b' ]]
print ( numbers )

将会输出[('a', 1), ('b', 1), ('a', 2), ('b', 2), ('a', 3), ('b', 3)]

上面的理解也称为列表理解,因为它们旨在创建新列表。 地图理解非常相似,旨在创建...地图。

结论

尽管Java是一门不断发展的语言,但这是一件好事。 但是,其他语言中发现的方法也值得研究。 请记住,一种语言构成了人们对问题的思考方式以及解决方案的设计方式。 学习或至少熟悉其他语言是考虑其他观点的好方法。

翻译自: https://blog.frankel.ch/six-interesting-features-programming-languages/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值