头图

想要知道什么是多范式,就不得不提,编程语言是干什么的。

范式与抽象

在我看来,编程语言便是对我们所要描述的问题的一种抽象。抛开最底层、完全模拟计算机运算的汇编语言不谈,每一种语言都根据所要解决的问题进行了抽象。C语言作为贴近底层的系统开发语言,提供了结构化的流程控制以及过程/函数,来简化指令序列的定义;Go语言目标是服务器语言,提供了轻量的协程以及通道,来提供更好的并发支持;Haskell语言是一个函数式语言,因此没有什么是可以变的,执行运算不过是简化表达式;Java语言将万物视为对象,命令也不过是让对象进行动作;更加针对特定领域的如将法律转化为程序的Catala或是用于定义逻辑的Prolog等……在每个语言提出一些特性之后,后续的语言都会有所选择地学习借鉴一些特性,或者是在抽象所及范围内进行模拟。例如,Java便通过将函数看作对象的方式兼容了函数式编程。

范式,就是一种体系,既包括了我们如何看待世界(表达式?对象?或是断言逻辑?),又包括了在这些世界观下解决问题的常见的模式。例如,Haskell所探索出的通过Monad来表示副作用的计算,便成为了函数式编程范式的一部分,被后续的Scala等多种语言吸收借鉴;而GoF提出的设计模式也广为流传,被大量应用,是面向对象开发时的必备经典。多范式编程语言,顾名思义,就是支持其中的多种范式,方便我们在面对不同问题时能采用最恰当的视角,来解决问题。

让我们以月兔MoonBit这一国产编程语言为例。假如说我们想模拟猫狗狭路相逢,坐标分别为-1和1,狗向猫进一步,猫退一步,如此重复五个回合。

函数式编程

● 如果我们视这个“世界”为表达式,我们可以用函数式编程。

我们视这个“世界”为表达式。我们首先定义模拟环境的数据结构,包含一猫一狗,各有坐标,则有:

struct Cat { location: Int }
struct Dog { location: Int }
struct World { cat: Cat; dog: Dog }

之后,我们定义状态改变的运算函数,如:

fn dog_forward(world: World) -> World {
  { .. world, dog: { .. world.dog, location : world.dog.location - 1 }}
}

fn cat_backward(world: World) -> World {
  { .. world, cat: { .. world.cat, location : world.cat.location - 1 }}
}

最后,我们则可以将它们组合起来,构成我们的模拟:

let simulation: (World) -> World = repeat(compose(dog_forward, cat_backward), 5)

可以看到,我们定义的都是表达式的组合。完整代码在这里:https://try.moonbitlang.com/#9e994c2c

命令式编程

● 如果我们视这个“世界”为冯诺依曼机,我们可以用命令式编程。

我们视这个“世界”为一个内存,而我们则通过指令来修改这个世界。我们同样先定义我们的模拟环境的数据结构,包含一猫一狗,各有坐标,则有:

struct Cat { mut location: Int }
struct Dog { mut location: Int }
struct World { mut cat: Cat; mut dog: Dog}

之后,我们定义改变状态的指令序列,或者说“过程”,如:

fn dog_forward(world: World) -> Unit {
  world.dog.location = world.dog.location - 1
}

fn cat_backward(world: World) -> Unit {
  world.cat.location = world.cat.location - 1
}

最后,我们同样组合起来:

fn simulation(world: World) {
  var i = 0
  while i <= 5 {
    dog_forward(world)
    cat_backward(world)
    i = i + 1
  }
}

可以看到,我们所做的都是在操纵内存中存储的数据,修改“世界”的状态。完整代码在这里:https://try.moonbitlang.com/#b56d6ac7

面向对象编程

● 如果我们视这个“世界”为对象的世界,我们可以用面向对象编程。

我们将猫和狗视为对象。作为对象,它们都具有状态和行为;而它们都是动物,部分行为也是类似的。我们可以先定义动物的状态和行为,例如,它们具有位置;它们可以移动:

interface Animal {
  location(Self) -> Int
  move(Self) -> Unit
}

之后,我们可以定义猫和狗,以及它们的行为。在月兔中,我们采用structural typing,于是有:

struct Cat { mut loc: Int }
struct Dog { mut loc: Int }

fn location(self: Cat) -> Int {
  self.loc
}
fn move(self: Cat, distance: Int) -> Unit {
  self.loc = self.loc + distance
}

fn location(self: Dog) -> Int {
  self.loc
}
fn move(self: Dog, distance: Int) -> Unit {
  self.loc = self.loc + distance
}

最后,我们可以定义我们的模拟环境的状态,以及行为:

struct World { mut c: Cat; mut d: Dog}
fn World::new() -> World { { c : { loc: -1 }, d : { loc: 1 } } }
fn cat(self: World) -> Cat { self.c }
fn dog(self: World) -> Dog { self.d }
fn simulate(self: World) -> Unit {
  var i = 0
  while i <= 5 {
    self.dog().move(-1)
    self.cat().move(-1)
    i = i + 1
  }
}

完整代码在这里:https://try.moonbitlang.com/#2d60fce4

总结

范式是一个体系,用来描述我们所面对的问题,并提供了一套方法论,来配套解决方法。但是我们也需要意识到,在软件工程中,不存在所谓的“银弹”,没有什么范式是可以一套打天下的。每一种范式都会有自己的优缺点。例如,想要用纯函数式编程来做输入输出,就可能会遇到Monad这个棘手的概念;而用面向对象编程开发,则不免陷入GoF提出的各种设计模式的纠缠,而究其原因则来源于面向对象的一些缺陷。

月兔编程语言,以及众多现代编程语言,支持多范式编程,就是希望能提供一把瑞士军刀,以最合适的角度切入问题,解决问题。


Moonbit
1 声望3 粉丝

IDEA基础软件中心打造的下一代智能开发平台