笔记, Cirru Table Redo 失败, 经验教训

题叶

缘起

前面两周花了一些精力断断续续去想这个项目:

Cirru Table Redo: https://github.com/Cirru/cirru-table-redo

本来的目标是做一个实时预览每个值的解释器的 Demo
从前做过两个版本, 也是在微博记录当中回忆才萌生念头的:

Cirru Live Editing: http://www.tudou.com/programs/view/SWKnhfmduos/
Cirru Table: http://www.tudou.com/programs/view/mHypUOnZSOI/

在 Live Editing 的例子里, 我试验了有一个全局 Mutable Scope,
然后代码一遍渲染一遍会修改 Scope, 于是代码同时会显示每个值.
当时的 commit 没去找是哪一个, 所以只能看不清晰的视频了

Cirru Table 当中呢, 则是把解释过程整个记录然后用树展示出来
效果对我自己来是是挺棒的, 只不过具体到程序, 不可能做到记录全部执行过程
Chrome Profiler 当中 Flame Graph 有类似, 但恐怕也不会记录全部数据
不过我觉得等硬件性能上去了倒是值得考虑下, 而且引入函数式编程的手法优化下
作为概念演示, 已经足够了, 我不管了, 只是想尝试点别的

失败的目标

那么我有新的想法了, 试试用新的办法来演示, 比如说有这样一段脚本:

cirrudefine (add x y) (+ x y)
define (main)
  print (add 1 2)

就像 Chris Granger 指出的, 抽象的变量是程序难以理解的原因
因为你根本不知道变量里边是什么, 只能一边脑补一边写代码, 错了也不知道
那我的方案首先是程序执行的过程当中应该把具体的数值显示出来
就算写 test case 也只是给某些特定数据吧, 不如直接在程序当中给:

cirrudefine:Builtin (add:Symbol x:1 y:2)
  +:Builtin x:1 y:2

为了整套东西能运行, 我需要引入 suppose 函数, 写上初始的变量
suppose 的作用, 是在开发时提供图形化的数据, 而在实际运行时消失
那么整个程序经过注释, 文字版就是这样的:

cirrusuppose:Built (add:Symbol 1:1 2:2)

define:Builtin (add:Symbol x:1 y:2)
  +:Builtin x:1 y:2

define:Builtin (main:Symbol)
  print:Builtin (add:Func 1:1 2:2)

基于这样的思路, 拆分出来子问题可以有这样一些:

  • 需要有基本的解释器, 比如 add 调用时直接能运行 (搞定 Demo)
  • 需要在渲染 DOM 时也能解释, 写另一份解释器 (思路理清, 没搞定特例)
  • 需要搞定读写文件接口的数据模拟 (思路理清, 没实现)
  • 需要考虑匿名函数的使用, 内部的数据怎样模拟 (思路没有理出来)

现在感觉这个坑已经太大了, 目前能力远远不够, 打算快点跳出来
当然我依然对此保持强烈的兴趣, 也是我为什么失败了还留笔记
如果有同学对此感兴趣继续探索就有意思了.. 当然很可能失败
前些天看到这句话, 可以共勉一下:
http://weibo.com/1651843872/Cl1Cl2hKI

If you don't fail at least 90 percent of the time, you're not aiming high enough.
-- Alan Kay

Live Coding

关于怎样让编程更加简单, 更加可靠, 社区已经投入了很多研究
特别是前面的 Chris Granger 的作品, 都是很发人深省的
他设计的 LightTable 当中就在多, 不过具体对应这里的是:

Inline Evaluation: http://docs.lighttable.com/tutorials/full/#inline-evaluation

实际上我希望能有对更大的项目使用的方案, 单行的有点不够
不管怎么说 LightTable 跟 Swift 出现在我面前时还是很正震惊的
大概因为我完全不知道怎样去实现吧.. Anyway, 以后会有更强大的

Bret Victor 在易学编程当中演示的就真的对图形开发者非常有必要了
因为能清楚理解每一行程序发挥怎样的作用的话, 对开发来说会清晰很多
具体看原文吧, 或者中文翻译可以的:

Learnable Programming: http://worrydream.com/LearnableProgramming/
(翻译)易学编程: http://chengyichao.info/learnable-programming/

总之就是, 让程序脱离抽象, 能让开发者非常直观地理解, 这很重要

脚本和静态类型

中间有个思考, 就是: 程序越大, 越会有很多抽象机制, 也越发难懂
首先数据的抽象, 过程的抽象, 在程序开发当中是至关重要的
其次才是我们要让程序更好懂, 有不高的维护成本, 然而两者常常背道而驰
比如函数式编程 Monad 抽象程度很高, 理解起来极难

脚本语言计划因为短小, 抽象不高, 但也因此经常出错, 只能从 Log 调试
对于脚本语言我又这样的一种理解, 就是脚本实际写的是不出错的情况
就是说假设程序不会遇到奇怪的数据, 那么先这样, 再那么, 应该就搞定了
然后写着写着发现有没考虑到的, 加入判断去处理一下. 强调下名字, 脚本.

在静态类型的语句当中, 或者说需要显示声明类型的语言, 就不能这样散漫
每一个函数, 到需要考虑好类型, 怎样处理各种数据的可能, 再连接到一起
程序经过了类型检查, 相比脚本语言来说出问题的情况少得多
当然, 每个函数都认真考虑, 提升质量的同时, 编写的工作量也多了

这几乎就是在便利和可靠两者之间做抉择, 而且是完全不同的目的
所以我想两者都可以当作是完全不同的编程语言
有一天要设计程序的可视化的时候, 考虑下是否需要两者区分开来?

类型系统

这一段是后边补上的, 我想起来还有一些关于类型系统的思绪

静态类型系统语言, 我接触的 Go, Haskell, 类型在开发当中都起到巨大的作用
在编程时, 类型是程序理解代码的一步环节. 不需要执行就能发现错误
有些语言的 IDE 还能根据静态类型给出方法和属性提示,
这种提示, 有一点点像是 Live Coding 当中所期望的即时查看执行过程

当然, 将类型类比程序的执行, 类型是抽象的, 变量的值才是具体的
很多具体场景, 只有弄明白具体的值, 以及运行过程, 才能理解程序
整体上我想说, 类型系统尽管抽象, 但也许也算一种将执行过程可视化的思路
又或者, 我们想要的可视化, 也行并不总是通过以往常有的方式重现?

不可变数据的 name

中间试着玩的时候, 未来考虑渲染时状态不被破破坏, 我尝试考虑不可变数据
比如这样一个解释器, 会修改作用域当中两个变量的数据,
如果渲染过程中后边的代码修改了数据, 其他用了变量的未位置是否收到影响?

cirrudefine (guess x y)
  set size 10
  set width 20
  + (* x size) (* y weight)

可是去掉修改作用域的函数 set, 问题来了, 然后如何定义变量呢?
后来想起了 Do Notation 当中手法, 通过函数作用域引入数据的 name,
具体就是个山寨(不全)的 bind 函数, 通过调用匿名函数来实现:

cirrudefine (guess x y)
  bind 10 $ \ (size)
    bind 20 $ \ (weight)
      + (* x size) (* y weight)

也是因为匿名函数的存在, 这个项目难度增加了, 匿名函数内部数据如何展示?
文本的代码执行是非常抽象的, 虽然看起来只是传入一个值, 实际却不能那么写,
具体没有考虑清楚. 不过 bind 的首发确实有意思, 以后还能用到

总结

可视化的编程方案对于开发者来说会很重要, 特别是界面越来越多的趋势下
另外要吸引新人加入程序员的领域, 真的是要有足够的诚意才行啊
我期待看国外牛人发布新的方案去做执行过程的可视化, 自己做只是学习而已
至少理解了 bind 函数这一节, 帮我理解了 Haskell 当中有些地方

阅读 2k

题叶
ClojureScript 爱好者.

ClojureScript 爱好者.

17.2k 声望
2.6k 粉丝
0 条评论
你知道吗?

ClojureScript 爱好者.

17.2k 声望
2.6k 粉丝
宣传栏