我是怎样学习新编程语言的

栏目: 编程语言 · 发布时间: 6年前

内容简介:学习新的编程语言的最终目的是解决实际问题。掌握编程语言的过程,在某种程度上近似学习一种新的工程实践。不仅解决问题固然可乐,学习的过程也同样充满了新鲜感,不过需要谨防的是新鲜感带来的胜任力错觉。胜任力错觉指的是反复接触新东西,发现不用花费什么气力就理解了其中所有的内容。说的简单点,就是自以为是。这种胜任力错觉导致最常见的后果是以为掌握了某种技能,真正开始解决问题时,要么是半天摸不着头绪,要么就是处处掣肘。所以我始终相信,阅读是一码事,理解是一码事,掌握还是另一码事,所谓一码归一码,大抵就是这么回事。

我是怎样学习新编程语言的

学习新的编程语言的最终目的是解决实际问题。掌握编程语言的过程,在某种程度上近似学习一种新的工程实践。不仅解决问题固然可乐,学习的过程也同样充满了新鲜感,不过需要谨防的是新鲜感带来的胜任力错觉。

胜任力错觉指的是反复接触新东西,发现不用花费什么气力就理解了其中所有的内容。说的简单点,就是自以为是。这种胜任力错觉导致最常见的后果是以为掌握了某种技能,真正开始解决问题时,要么是半天摸不着头绪,要么就是处处掣肘。所以我始终相信,阅读是一码事,理解是一码事,掌握还是另一码事,所谓一码归一码,大抵就是这么回事。

以终为始,方得始终。老子(真·老子,非我)也说,慎终如始,则无败事。这里的“终”就是目标,在软件工程中,有一种实践很好得反映了这种做事方式——测试驱动开发。借我司的一位牛人的原话:看一个人会不会测试驱动开发,不是看他的测试写得好不好,而是要看他是不是始终从测试出发去解决问题。脑子里条件反射的就是测试该怎么测?这种才是测试驱动开发的实质。

学习,说白了就是一个不会到会的过程,这里头最难的是学会了什么?在学习方法上,我们很多时候喜欢遵循前人的套路,美其名曰知识体系化。我承认体系是前人经验和群体智慧的积累,但是学习体系不代表你具备形成体系的能力,就像你学习了著名开发框架(Spring or Rails)也不会说你能开发这套框架一样。学习的关键还是发散、收敛和再发散、再收敛的渐进过程,感性的定性分析到理性的定量分析,在不断丰富和修正认知,处处用实践检验认知。这种过程坚持下来,得到就不单单是知识,可能是元知识(方法论)或者智慧。

看书抄代码是个学习的好方法,不过书中的例子一般都被加工(简化)过,我们很容易陷入套路中,谨记胜任力陷阱。比较推荐的方式,自己认准一段有用的程序,反复练习(也可以每次增加些体系化的功能)直到娴熟。在接触新语言时,不去看一套完整的语言体系,而是事先把这段程序可能用到的基本类型、数据结构、流程控制结构、模块化和功能组件列出来,然后去找它们在这门语言中对应的实现。

有目的地试错

我常用的练手程序叫 tree ,功能是 list contents of directories in a tree-like format. 这个程序需要用到的基本构件有:

基本类型(basic type)
1. str
 
数据结构(data structure)
1. list
2. map

流程控制结构(control flow structure)
1. if, else
2. recursion

模块化(modulize)
1. function
2. module/namspace/package

功能组件(function components)
1. IO
2. File
3. Path

分类清晰之后,对应找起来很方便,有的基本不用找,经验足矣。现在的编程语言基本都有 repl ,多尝试几遍就有了感性认识。我说的很轻松,但是如果不去尝试,一样会难住。Elixir中有 iex 命令作为 repl ,而且这门语言深受Clojure的影响,尤其是文档和例子方面很充足,对于初学者再友好不过。

换种思维

在编写 tree 的过程中,我会时不时停下来思考Elixir在某个功能点上应该怎么用才好?因为历史上,把 Java 的代码写成C风格的人不在少数,这足以让人警惕。再说,学会用新语言的思维方式编程是我初始的目的之一。

这里举个例子,map的key使用哪种基本类型会比较合适?Clojure中有keyword,如 {:name "clojure"} ,而 Python 中并没有这样的数据类型,我只好使用 {'name': "python"} ,那么Elixir呢?它推荐的是atom/symbol, %{:name => "elixir"} #or %{name: "elixir"}

遇到需要join path的时候,凭借原来的经验,我会去寻找 Path 模块。具体可以去问谷歌,也可以问 repl

iex<1> h Path.join
or
iex<1> Path.join <TAB> #用tab键
join/1    join/2

看到 join/1 join/2 的时候,我有些许迷茫,但是很快就变成了欣喜。我们知道,在动态类型语言中,arity指的是方法参数的个数,这里的 1和2 其实表明的就是join有两个重载的方法,分别接受一个参数和两个参数。更进一步,arity是方法(函数)实现静态多态的依据之一。再进一步,多态是函数的特性,而非OO中固化下来的概念——类的特性。

组织代码

上面的验证只需要 repl 就足够了。但是,真正编写还是得有组织和结构。软件工程中,控制复杂度(复杂度从来不会被消除)的基本法则就是模块化。这就引出了module和function,还有对模块可见性(private, public etc.)的修饰。

defmodule Tree do
  defp tree_format(parent_dir, dir_name) do
    %{:name => dir_name, :children => []}
  end
end

defp 定义了一个私有的方法 tree_format ,它是用来格式化目录的。目录结构是树形结构,所以很容易递归实现。

defp children(path) do
  if (path |> File.dir?) do
    File.ls!(path) |> Enum.map(fn f -> tree_format(path, f) end)
  else
    []
  end
end

defp tree_format(parent_dir \\ ".", dir_name) do
  %{:name => dir_name, :children => Path.join(parent_dir, dir_name) |> children}
end

在利用递归的过程中,我使用 File.ls! (查文档,注意!号)列出子目录,然后递归地格式化。这些都比较好理解,不过这里其实出现了两个新的玩意(当然也不是一蹴而就的,认识之后才重构成这样)。一个是 \\ "." ,还有一个是 |> 。第一个比较容易猜,叫做默认参数(default arguments);第二个有Clojure基础的也手到擒来,叫做管道操作符(pipe operator),用来将左边表达式的结果传入右边方法的首个参数。这里就是 children(path)path .

结构,解构

完成目录结构的格式化,接下来需要做的是渲染这组树状的数据。

defp decorate(is_last?, [parent | children]) do
  prefix_first = (if (is_last?), do: "└── ", else: "├── ")
  prefix_rest = (if (is_last?), do: " ", else: "│ ")
  [prefix_first <> parent | children |> Enum.map(fn child -> prefix_rest <> child end)]
end

defp render_tree(%{name: dir_name, children: children}) do
  [dir_name 
   | children 
   |> Enum.with_index(1)
   |> Enum.map(fn {child, index} -> decorate(length(children) == index, render_tree(child)) end) 
   |> Enum.flat_map(fn x -> x end)]
end

到这里,我学到的是参数解构(arguments destructing), map-indexed 的新实现,字符串的拼接(string concatenation)还有列表元素的前置操作。

Elixir和所有函数式编程语言一样,具备强大的模式匹配(Pattern matching)的功能,参数解构其实就是其中的一个应用场景。

%{name: dir_name, children: children}
matching
%{:name=>".",:children=> ["tree.exs"]}
# ->
dir_name == "."
children == ["tree.exs"]

渲染的过程也是递归的。最终返回的是一个加上分支标识前缀的列表

[dir_name | children]

这是一种将 dir_name 前置到 children 列表头部,形成新列表的做法。和Clojure(绝大数Lisp)中的 (cons dir_name children) 类似。

操作符 | 除了可以前置列表元素,递归解构也是一把好手。

defp decorate(is_last?, [parent | children]) do
  ...
end

参数列表中的 [parent | children] ,解构出了列表的head和rest,这对于递归简直就是福音。

在添加前缀的步骤 [prefix_first <> parent...] 中,经验里字符串的拼接常用符号 + 不起作用了,换成了 <> ,这个是靠试错得出来的。

除了说到的这部分内容,我还运用了 Enum.map, Enum.with_index, Enum.flat_map 等函数式语言的标配。这些零散的知识点,可以添加到基本构件中,以便持续改进。

入口

程序要执行,就需要一个入口。每次我都会猜猜 argv 会在哪里出现呢?是 sys (Python), os (Go),还是 process (Node.js),这回又猜错了,Elixir管这个叫做 System .

  def main([dir | _]) do
    dir |> tree_format |> render_tree |> Enum.join("\n") |> IO.puts
  end
# ---
Tree.main(System.argv)
# ---
$ elixir tree.exs .

重构

这里重构的目的是让程序更加贴近Elixir的表达习惯,那么哪里不是很符合Elixir风格呢?我注意到了 if...else ,可以考虑模式匹配实现多态。

defp children(path) do
  if (path |> File.dir?) do
    File.ls!(path) |> Enum.map(fn f -> tree_format(path, f) end)
  else
    []
  end
end

File.ls! 中的 ! 表示如果指定目录有问题,函数会抛出error或者异常。然而,Elixir还给出了一个 File.ls 方法,即便出错,也不会有抛出的动作,而是返回 {:error, ...} 的元组,至于正常结果,则是 {:ok, ...} . 这恰恰可以使用模式匹配做动态分派了。

defp children(parent) do
  children(parent |> File.ls, parent)
end

defp children({:error, _}, parent) do
  []
end

defp children({:ok, sub_dir}, parent) do
    sub_dir |> Enum.map(fn child -> tree_format(parent, child) end)
end

一旦 children(parent |> File.ls, parent) 中的 parent 不是目录, File.ls 返回的就会是 {:error, ...} 元组,它会被分派到对应的方法上,这里直接返回一个空的列表。反之,我们就可以拿到解构之后的子目录 sub_dir 进行交互递归,实现全部子目录的格式化。

小结

在学习Elixir的过程中我收获了很多乐趣,不过,这离掌握Elixir还有很远的距离。我曾经看过一部科幻电影“降临”,剧情受到了萨丕尔-沃夫假说(语言相对性原理)的影响,这个假说提到:人类的思考模式受到其使用语言的影响,因而对同一事物时可能会有不同的看法。既然如此,那么自然语言也好,编程语言也罢,如果能换种思维方式解决同一种问题,说不定能收获些奇奇怪怪的东西,编程之路,道阻且长,开心就好。 – 2018-06-08


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Designing Web Navigation

Designing Web Navigation

James Kalbach / O'Reilly Media / 2007-8-15 / USD 49.99

Thoroughly rewritten for today's web environment, this bestselling book offers a fresh look at a fundamental topic of web site development: navigation design. Amid all the changes to the Web in the pa......一起来看看 《Designing Web Navigation》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具