使用函数式编程语言 ELM 开发游戏 已翻译 100%

oschina 投递于 2015/02/12 17:50 (共 8 段, 翻译完成于 02-15)
阅读 5424
收藏 38
Elm
2
加载中

这是我首次准备就有关用 elm 进行游戏开发的内容撰写一个系列的文章. 这是一种能编译成 html 和  javascript,以便你可以将其直接部署到web服务器上,或者打包到 nw.js 中以创建一个独立的应用或者游戏,这样的编程语言. 没有多少教程可以参考,但是随着我的慢慢进步之中我已经了解到了越来越多的东西. 因为 elm 正处在积极的发展过程中,如果或者当我的文章有点过时的时候,我将会对它们进行更新. 也会慢慢纠正我在里面留下的错误 …

函数式编程 (FP) 是一种令人惊异的边界不清的范式. 不少人对它都有很多不同的认识,而它所为我呈现的,则是一个希望能编写更加简单的可组合式代码的群体, 以避免强 耦合 并创建出易于调试的软件.

LeoXu
LeoXu
翻译于 2015/02/13 00:59
2

Games

从很多编程的领域看来,游戏开发是最适合面向对象的而且游戏总是带很多状态。从表面上看,面向对象这种方法很适合。当我使用 Functional Programming的时候,我发现这种方法也很适合游戏编程,而且我也对如何解决问题很感兴趣。

为什么我选择用Functional Programming开发游戏呢? 简单的说,我对传统的软件设计方法感到厌倦。命令式的面向对象代码对导致一下过度设计的问题,而且很不美观。

这仅仅是我的个人观点,所以请放松,但是如果你渴望一些不同的事物,为什么不来functional的路上看看呢。

21paradox-
21paradox-
翻译于 2015/02/15 08:32
1

Iteration

看看下面最简单的js例子,对于一个数组取平方。使用"命令式"的代码你会描述一些将要发生的事情。而用functional代码(声明式编程的子集),你去描述你想要做的事情。所以"命令式"的风格中我们会定义一个临时的index变量然后创建一个循环,遍历一遍数组,然后每个value取平方。

var numbers = [1,2,3,4,5,6,7,8,9],
    i;
for (i = 0; i < numbers.length; i++) {
    numbers[i] *= numbers[i];
}

相比起来,functional的方法路线呢,如果你写js可以考虑下lodash这个库。然而使用一个专门是functional programming的语言,会比的很容易,所以我们使用elm来做这件事。

import List (..)
square : Int -> Int
square n =
    n * n
numbers : List Int
numbers =
    map square [1..9]

使用elm我们不需要定义临时变量,我们也会创建一个数组更容易,而且定义一个function复用。通过map我们将每个list的数字取平方,然后返回一个新的list。

正如你看到的,functions是对于传进来的每个变量有类型提示的。import List (..)这一行简单引入了核心list function,elm自带的这些function 提供了 map和filter的方法。

译者注: loadash的functional 路线

var _ = require('lodash');
var square = _.curryRight(_.map, 2)(function(n){
  return n *n;
});
square([1,2,3,4,5,6,7,8,9]);
21paradox-
21paradox-
翻译于 2015/02/15 09:40
1

Filtering

现在设想一下我们想从数组中移除奇数,然后只平方哪些过滤后的数组。通常,“命令式”的js是这样写的:

var numbers = [1,2,3,4,5,6,7,8,9],
    squaredNumbers = [],
    i;
for (i = 0; i < numbers.length; i++) {
    if (numbers[i] % 2 == 0) {
        squaredNumbers.push(numbers[i] * numbers[i]);
    }
}
numbers = squaredNumbers;

当然,这样写可以用,但是定义另外一个数组看起来很乱,而且我们还是得写循环。这就是"命令式"代码最不具有新意的地方。你可能会在你的代码的1000个地方重复写上面的代码。

现在我们试试functional 的写法:

isEven : Int -> Bool
isEven n =
    n % 2 == 0
numbers : List Int
numbers =
    map square (filter isEven [1..9])

我们在numbers里增加一个filter,然后这块方法就变得可以复用了,我们不需要对于有点不同的需求写重复代码。

这里就是functional programming闪耀的地方了,你花了更少的时间在编写想要的做的东西上,而且代码阅读起来也很方便。还有就是这证明了方法是可以链式调用的。

21paradox-
21paradox-
翻译于 2015/02/15 11:15
1

Chaining

如果你现在觉得方法的nest调用会变得失控,你是对的。在elm语言中我们可以使用 |>操作符去帮助我们链式调用方法。

|> 操作符是 functional 程序的别名,它取得左边所有的参数然后传递this当作最后一个参数,this是最右边的参数。仍有<|是反向作用上述过程的。

-- this
1 |> add 2
-- is equivalent to this
add 2 (1)

当有多个function被调用,我们很容易看到这个的好处。

-- this
1 |> add 2
  |> add 3
  |> add 4

-- is equivalent to this
add 4 (add 3 (add 2 (1)))

这样做减少了需要写的括号,而且使得代码更易读,变得更像一句话:

numbers : List Int
numbers =
[1..9] |> filter isEven
   |> map square
21paradox-
21paradox-
翻译于 2015/02/15 11:31
1

Composition

一个更好的解释composition的地方,将简单的functoin组合起来编写成复杂的

在elm理我们可以将function compose起来,通过>>操作符。这样做的好处是我们不需要指定input就可以提前把function compose起来。

-- this
(isEven >> not)
-- is equivalent to
(\n -> not (isEven n))

从逻辑上来考虑,如果我们知道 g : A -> B 和 f : B -> C(译者: 方法g是从状态A -> B, 方法f是状态B -> C),我们可以将f和g compose起来通过创建一个g >> f ,就是 A ->B ->C,(而且这个顺序可以是反向的 f << g : A -> C)。

在这个例子中中我们检测数字是否是奇数:

squareIsOdd =
square >> isEven >> not -- `not` 是一个built-in的方法用作布尔值反转
squareIsOdd 3 == True
squareIsOdd 7 == False

这个inputs给了squareIsOdd一个 composed的方法,每一个方法调用时都返回了一个结果,用作下一个方法的参数。

21paradox-
21paradox-
翻译于 2015/02/15 11:44
1

State

State是程序储存的状态,通过对象中的变量来表示。问题在于state是这样存储的,它能够允许开发者不在当前的scope下修改变量的值,这么做是存在隐患的,比如:

var foo, bar;
foo = {
"baz": 1
"setBaz": function(value) {
this.baz = value;
bar.qux = value * 2; // 讨厌!
}
};
bar = {
"qux": 2
};
foo.setBaz(2);

可能有一些修改bar.qux的原因,比如bar.qux应当永远是foo.baz的两倍。但是直到开发者看了setBaz的源码之后他们才会知道bar.qux改变了。对象的api骗人了,这个例子是一个你可以明确的,容易的,识别出的糟糕代码。但是这样写"相当有效",所以不可避免的导致程序员这写。我自己看到过和这么写过太多太多这种代码。

所以,如何解决问题呢?不为开发者提供这些功能就行。elm没有全局变量,没有变量,只有input和output。

21paradox-
21paradox-
翻译于 2015/02/15 11:58
1

然而如果function没有任何update操作,仅仅只是返回了input,output和input是同一内容,这样避免了不必要的拷贝。

noop input =
    input
sameAsInput = noop { a = "b" }

所以取setBaz这个例子:

type alias Foo = { baz : Int }
type alias Bar = { qux : Int }

foo : Foo
foo = 
      { baz = 1 }
bar : Bar
bar = 
      { qux = 2 }

setFooBaz : Int -> Foo -> Foo
setFooBaz baz' foo 
                  = { foo | baz <- baz' }

foo1 = foo |> setFooBaz 2

我们看到setFooBaz是没有办法修改bar.quz的。这个方法是没有办法修改scope外面的值的,所以只能返回一个新的foo.

澄清一下,你可能这么想foo: Foo是一个type为 Foo的变量,但是它不是。它只是一个function不需要input然后output一个对象。我们可以很容易改变一个东西成Foo: Int -> Foo,去让baz被实例化成一种value。

如果我们仍想要确保bar.qux被及时更新成为foo.baz的两倍,我们可以创建一个方法,这个方法是被两个方法compose成的,被增加了2次人后返回原来的对象。

type alias FooBar =
    { foo : Foo
    , bar : Bar
    }
fooBar : FooBar
fooBar =
    { foo = foo -- our previously created `foo` function
    , bar = bar
    }
update : Int -> FooBar -> FooBar
update baz fooBar =
    { fooBar
        | foo <- fooBar.foo |> setFooBaz baz
        , bar <- fooBar.bar |> setBarQux baz * 2
    }
fooBar = fooBar |> update 2

我们能够update这个值,向我们想的那样,但是没有副作用。update操作的output值包含了各种操作的影响。

Elm

在我看来,functional语言有很多优势,通过上面的例子。阅读、debug、复用都很方便无副作用。所以为什么不去试着在写一个游戏呢。

21paradox-
21paradox-
翻译于 2015/02/15 15:36
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(9)

firebroo
firebroo

引用来自“firebroo”的评论

我能说你是标题党吗?只介绍了语言的语法,我开始以为是写开发游戏的

引用来自“Morler”的评论

楼上连第一句都没看完就在这里吐槽真的大丈夫么? 人家说的很清楚,这是系列文章的第一篇。
恩,是我的错。。
店长强力推荐
店长强力推荐
oschina的评论怎么越来越,感觉快成为下一个csdn了。
当当

引用来自“firebroo”的评论

我能说你是标题党吗?只介绍了语言的语法,我开始以为是写开发游戏的

引用来自“Morler”的评论

楼上连第一句都没看完就在这里吐槽真的大丈夫么? 人家说的很清楚,这是系列文章的第一篇。

引用来自“工头叫我去搬砖”的评论

日杂滚粗,好好说话
扣帽子砖家
MikeManilone
MikeManilone

引用来自“firebroo”的评论

我能说你是标题党吗?只介绍了语言的语法,我开始以为是写开发游戏的

引用来自“Morler”的评论

楼上连第一句都没看完就在这里吐槽真的大丈夫么? 人家说的很清楚,这是系列文章的第一篇。

引用来自“工头叫我去搬砖”的评论

日杂滚粗,好好说话
呵呵
MikeManilone
MikeManilone

引用来自“firebroo”的评论

我能说你是标题党吗?只介绍了语言的语法,我开始以为是写开发游戏的

引用来自“Morler”的评论

楼上连第一句都没看完就在这里吐槽真的大丈夫么? 人家说的很清楚,这是系列文章的第一篇。

引用来自“工头叫我去搬砖”的评论

日杂滚粗,好好说话
呵呵
工头叫我去搬砖
工头叫我去搬砖

引用来自“firebroo”的评论

我能说你是标题党吗?只介绍了语言的语法,我开始以为是写开发游戏的

引用来自“Morler”的评论

楼上连第一句都没看完就在这里吐槽真的大丈夫么? 人家说的很清楚,这是系列文章的第一篇。
日杂滚粗,好好说话
vmbncm
vmbncm
最好先上例子,显然原作者没做好
Morler
Morler

引用来自“firebroo”的评论

我能说你是标题党吗?只介绍了语言的语法,我开始以为是写开发游戏的
楼上连第一句都没看完就在这里吐槽真的大丈夫么? 人家说的很清楚,这是系列文章的第一篇。
firebroo
firebroo
我能说你是标题党吗?只介绍了语言的语法,我开始以为是写开发游戏的
返回顶部
顶部