6 个新奇的编程方式,改变你对编码的认知

我时不时会发现一种编程语言的不同用法它有时候会改变我对编程的看法啊。这篇文章中,我想分享一下让我惊讶的发现。这不是类似于高呼“函数式编程会改变世界!”博客文章。我敢打赌,大多数读者都没有听说过下面的大多数语言和范例,所以你应该也会被这些新概念吸引。

注意:我对以下大多数语言的使用经验都很少,但是我发现他们背后的想法非常吸引人,但对其没有专业知识,所以有任何错误请指出并指导更正。如果您也有新的范例和想法,欢迎分享。

默认并发

示例语言: ANI ,  Plaid

让我们用一个哲学家的思想来解决问题吧:有些编程语言是默认情况下并发的,也就是说,每行代码都是并行执行的。

例如,假设你写了三行代码,A,B和C:

A;
B;
C;

在大多数编程语言中,A先执行,然后执行B,最后执行C。在像 ANI 这样的语言中,A,B和C都将同时执行。

ANI 中代码行之间的控制流或排序,仅仅是代码行之间显式依赖关系的副作用。例如,如果B引用了A中定义的变量,则A和C将同时执行,而B只会在A完成后执行。

以下是ANI中的“Hello World”示例:

"Hello, World!" ->std.out

在ANI术语中,我们将 "Hello, World!" 对象(字符串)发送到 std.out 流。如果我们发送另一个字符串,会发生  std.out 什么

"Hello, World!" ->std.out
"Goodbye, World!" ->std.out

这两行代码并行执行,因此它们可以在控制台中以任何顺序结束。现在,看看当我们在一行上引入一个变量并参考它会发生什么:

s = [string\];
"Hello, World!" ->s;
\s ->std.out;

第一行声明一个“锁存(latch)”(锁存器有点像变量),调用 s 它包含一个字符串; 第二行将文本赋值  "Hello, World!" 给 s ; 第三行“解锁”  s 并将内容发送给 std.out 。在这里,您可以看到ANI的隐式程序排序:由于每行都依赖于前一行,因此此代码将按写入的顺序执行。

Plaid 语言还声称通过默认支持并发性,但使用权限模型,如在本文中,设置控制流程。多核技术正在兴起,并发性仍然是大多数语言中难点。ANI 和 Plaid 提供了一个新的解决方案,可以带来惊人的性能提升;,问题在于“默认并行”是否会改变开发的状态。有关更多信息,请参阅 并发性并行性 。

相关类型

示例语言: Idris ,  Agda ,  Coq

你可能习惯使用C和Java等语言来键入系统,编译器可以检查变量是整数,列表还是字符串。但是如果你的编译器能检查一个变量是“一个正整数”,“一个长度为2的列表”还是“一个回文字符串”呢?

下面是如何声明一个 Vector 包含无形库1,2,3的值:

val l1 = 1 :#: 2 :#: 3 :#: VNil

这将创建一个变量 l1 ,它的类型签名不仅指定它 Vector 是包含的 Ints ,而且指定它的 Vector 长度为3.编译器可以使用此信息来捕获错误。让我们使用该 vAdd 方法在 Vector 两个之间执行成对加法 Vectors :

val l1 = 1 :#: 2 :#: 3 :#: VNil
val l2 = 1 :#: 2 :#: 3 :#: VNil
 
val l3 = l1 vAdd l2
 
// Result: l3 = 2 :#: 4 :#: 6 :#: VNil

上面的例子工作正常,因为类型系统知道两者 Vectors 都有长度3.但是,如果我们尝试了 vAdd 两种 Vectors 不同的长度,我们没等到运行时就会在编译时得到一个错误。

val l1 = 1 :#: 2 :#: 3 :#: VNil
val l2 = 1 :#: 2 :#: VNil
 
val l3 = l1 vAdd l2
 
// Result: a *compile* error because you can't pairwise add vectors 
// of different lengths!

Shapeless 是一个仍然有点粗糙的库,只支持依赖类型的一个子集,并有相当冗长的代码和类型签名。相反, Idris 使得类型成为编程语言的第一类成员,因此依赖类型系统似乎更加强大和干净。为了进行比较,请查看 Scala vs Idris:相关类型,现在以及未来的 讨论。

连贯语言

示例语言: Forth ,  cat , joy

有没有想过不用变量和函数应用程序编程会是什么样子?至少我没有,但显然有些人这么想了,他们提出了 连续编程 。这个想法是,语言中的所有内容都是将数据推送到堆栈或从堆栈中弹出数据的函数; 程序几乎完全通过功能组合(串联组合)来构建。

这听起来很抽象,所以我们来看看 cat 中的一个简单例子 :

2 3 +

在这里,我们将两个数字推入堆栈,然后调用该 + 函数,将两个数字从堆栈中弹出,并将其添加到堆栈中的结果:代码的输出为5。下面是一个稍微有趣的示例:

def foo {
  10 <
  [ 0 ]
  [ 42 ]
  if
}
 
20
foo

让我们一行一行地浏览一下:

  1. 首先,我们声明一个函数 foo 。请注意,cat中的函数没有指定输入参数:所有参数都从堆栈中隐式读取。

  2. foo 调用 < 函数,该函数弹出的第一项在堆栈中,将它与10,并且推动任一 True 或  False 背面压入堆栈。

  3. 接下来,我们将值0和42输入堆栈:我们将它们包括在括号中以确保它们未被执行就推入堆栈。这是因为它们将分别用于调用 if 下一行函数的“then”和“else”分支。

  4. 该 if 函数从堆栈中弹出3个项目:布尔条件,“then”分支和“else”分支。根据布尔条件的值,它会将“then”或“else”分支的结果输回堆栈。

  5. 最后,我们将20输入堆栈并调用 foo 函数。

  6. 当所有的事情都做完后,会得到一个42的结果

这种编程风格颇有趣味:程序可以以无数种方式拆分和连接以创建新程序; 非常简单的语法(甚至比LISP更简单),导致非常简洁的程序; 也具有强大的元编程支持。看起来你必须记住或想象堆栈的当前状态,而不是能够从代码中的变量名称中读取它,这可能使得很难推断代码。

声明式编程

示例语言: Prolog ,  SQL

声明式编程 已经存在了很多年,但大多数程序员仍然不知道这个概念。这里的要点是:在大多数主流语言中,你需要描述如何解决一个特定的问题; 在声明性语言中,你只需描述你想要的结果,而语言本身就能找出到达那里的方法。

例如,如果您在C中从头开始编写排序算法,例如编写合并排序的指令,该指令逐步描述如何递归地将数据集分成一半并按排序顺序合并到一起。如果您使用像 Prolog 这样的声明性语言对数字进行排序 ,则应该描述所需的输出:“我需要相同的值列表,但索引中的每个项目  i 应小于或等于索引处的项目 i + 1 ”。将以前的C解决方案与此Prolog代码进行比较:

sort_list(Input, Output) :-
  permutation(Input, Output),
  check_order(Output).
  
check_order([]).
check_order([Head]).
check_order([First, Second | Tail]) :-
  First =< Second,
  check_order([Second | Tail]).

如果你使用过SQL,那么你已经完成了一种声明式编程,你可能没有意识到这一点:当你发出查询时 select X from Y where Z ,你正在描述你想要返回的数据集;它是真正计算出如何执行查询的数据库引擎。

声明性语言的美妙之处在于它允许你在更高层次的抽象中工作:你只需要描述所需输出的规范。例如, prolog中 简单 数独求解器 的代码,只是列出了解决的数独谜题的每行,每列和对角线应该是什么样的:

sudoku(Puzzle, Solution) :-
  Solution = Puzzle,
  
  Puzzle = [S11, S12, S13, S14,
            S21, S22, S23, S24,
            S31, S32, S33, S34,
            S41, S42, S43, S44],
  
  fd_domain(Solution, 1, 4),
  
  Row1 = [S11, S12, S13, S14],
  Row2 = [S21, S22, S23, S24],
  Row3 = [S31, S32, S33, S34],
  Row4 = [S41, S42, S43, S44],      
  
  Col1 = [S11, S21, S31, S41],
  Col2 = [S12, S22, S32, S42],
  Col3 = [S13, S23, S33, S43],
  Col4 = [S14, S24, S34, S44],      
  
  Square1 = [S11, S12, S21, S22],
  Square2 = [S13, S14, S23, S24],
  Square3 = [S31, S32, S41, S42],
  Square4 = [S33, S34, S43, S44],      
  
  valid([Row1, Row2, Row3, Row4,
         Col1, Col2, Col3, Col4,
         Square1, Square2, Square3, Square4]).
 
valid([]).
valid([Head | Tail]) :- fd_all_different(Head), valid(Tail).

以下是数独解算器的运行结果:

| ?- sudoku([_, _, 2, 3,
             _, _, _, _,
             _, _, _, _,
             3, 4, _, _],
             Solution).
 
 
S = [4,1,2,3,2,3,4,1,1,2,3,4,3,4,1,2]

不幸的是,声明式编程语言很容易造成性能瓶颈。上面的排序算法很可能 O(n!) 让数独解算器进行了一次强力搜索; 而且大多数开发人员必须提供数据库提示和额外索引,避免执行SQL查询时出现代价高昂且效率低下的情况。

符号编程

示例语言: Aurora

Aurora 语言是一个典型的 符号编程 的例子:它不仅包括纯文本编程,而且还包括图像,数学方程,图形,图表等。这能够用该数据的原始格式操作和描述各种数据,而不是用文本描述所有数据。Aurora也是完全互动的,可以立即显示每行代码的结果,例如 REPL。

Aurora语言由Chris Granger创建,他也创建了Light Table IDE。Chris在他的文章中概述了Aurora的动机:实现更好的编程。目标是使编程更加具有可观察性,直接并减少偶然的复杂性。欲了解更多信息,请务必查看Bret Victor令人难以置信的会谈: Inventing on Principle ,  Media for Thinking the Unthinkable , and  Learnable Programming 。

基于知识的编程

示例: Wolfram语言

像上面提到的 Aurora 语言一样,Wolfram语言也是基于符号编程的。但是,符号层仅仅是为Wolfram语言的核心提供一致的接口的一种方式,这是基于知识的编程:内置于大量的库,算法和数据。这使得从绘制Facebook连接到操纵图像,查看天气,处理自然语言查询,绘制地图上的方向,求解数学方程式等等都可以轻松地完成。

我怀疑 Wolfram 语言是否有最大的“标准库”和有任何语言的数据集。互联网的连接是编写代码的内在组成部分的想法让我感到兴奋:它几乎就像一个自动完成功能进行谷歌搜索的IDE。符号编程模型是否像Wolfram声称的那样灵活,而且可以真正利用所有这些数据?如果是的话,会很有趣。

更新:虽然Wolfram声称Wolfram语言支持“符号编程”和“知识编程”,但这些术语的定义略有不同。更多有关信息,请参阅 知识编程 和 符号编程 wiki。

来自: reddit

 

回到

顶部