coffee-js / languages

编程语言学习论坛
https://github.com/coffee-js/languages/issues
112 stars 11 forks source link

作用域的索引, 一个想法 #36

Open tiye opened 11 years ago

tiye commented 11 years ago

一个没有多少依据的想法, 我觉得还是值得记录一下, 因为对我而言够新奇 估计描述上会乱, 我尽量多加例子, 介绍新的概念时往往容易出错的 代码我用 CoffeeScript 写例子, 因为讨论的概念好多都是 JS 里来的

默认的定义:

log = console.log

来源

编程语言作用域, 从局部作用域能索引到上一层的作用域, 这非常有意思 这带来了变量索引的方便. 虽然, 在局部变量还全局变量的语法上会有分歧

a = 0
outer = ->
  b = 1
  inner = ->
    c = 2
    log a, b, c
outer()()

# prints
#0 1 2

__proto__ 是给对象增加一个原型, 当前对象不能访问到 key 时, 从原型索引 this 则能一直指向所运行的对象, 两者搭配

a =
  name: "a"
  read: -> log @name

b = __proto__: a

b.read()

b.name = "yes"
b.read()

# prints
# a
# yes

这样, 就存在两种变量的索引方案, 同时两者都是链式的, 意味着可以嵌套或者级联 前者方便 JS 里使用闭包, 并且很自然 后者满足简单的 OOP 里共用方法, 共用属性, 这样的需求 OOP 中, 同一个类里的属性和方法相互调用非常方便, 这是我体会到的

我想索引是编程里最关键的操作之一

我理解的编程(废话)是怎样 设计和存储数据 , 然后就是怎样 访问和调用数据 数据组合的方式我按 JS 理解, 有 List 和 Object 足够, 后者我用 Dict 区分一下

Object 我在 JS 中有点像制造组合数据类型的方式 Object 帮助数据完成归类, 避免了散乱. 当然, 这是自然而然的一种结构 并且对于 HTML 标签, 如果不用 Object 组织属性和方法那将难以想象

另一个例子在深度的递归当中, 闭包的层层嵌套有时需要技巧才能正确地索引 我现在写过的代码里体会深的解释器里创建的函数:

fn = (item1, scope) -> (item2) ->
  # log "define fn:", item1
  if item1[0]?
    do ret = (scope, item1) ->
      (arg, out_scope) ->
        child = spawn scope
        value = get arg, out_scope
        key = item1[0]
        child[key] = value
        # log ":::", key, value, child, arg
        if item1[1]? then ret child, item1[1..]
        else
          # log "doing function", child
          get item2, child
  else
    child = spawn scope
    get item2, child

因为要在 scope child out_scope 三个作用域中找到数据, 复杂度更高 当然在递归时, 也只有通过参数传递到闭包进行更多数据的处理了

增加更多的索引方式

体会到索引的作用自然会想到设计一些新的索引方案来增强 我理解中面向对象是近似于作用域一样的设计, 并在基础上做拓展 但怎样设计的索引很难说会给编程带来多方便, 因为没有从实际应用中找到

演示的代码, 注意我用了 &a, 代码不能作为正常的 CoffeeScript 运行

a = 0
f1 = -> log &a
f1()

do wrapper = ->
  a = 1
  f1()

# want it print
#0
#1

我定义 &a 索引执行时作用域里改变量的值, 至少有点用途

比如下面的代码中 ^name.child2.name 就是先索引上层 Object

a =
  name: "level 1"
  child:
    name: "level 2"
    read: -> log ^name.child2.name
  child2:
    name: "level 2 of child2"

a.child.read()

# want it print
# level 2 of child2

代码中我写了比较难看的定义 ~(-1).name 表示数组的上一个元素

a = [
  {name: "one"}
  {name: "two"}
  {
    name: "three",
    read: -> log ~(-1).name
  }
]

a[2].read()

# want it print
# two

类似的做法, 具体看编程中用到怎样的索引. 我只能做一些概念的猜想 或者会有设计语言的麻烦, 或者性能, 或是否实用, 都是我不熟悉的

模仿 Lisp 的代码即数据

看到 List 和 Map 能组合出数据, 我回想到从前一个问题: Lisp 里, code 和 List 都是 sequence 的形式表示, 很奇怪, 是线性的序列 那么有没有对应更高维度或者更灵活的结构呢?

因为到了 Dict, 就想到键值对的形式好像是有趣的另一种描述代码即数据的方式.. 又会想到 Object 中 keys 没有单纯顺序的说法, 就像是上面找的古怪结构 于是我在想, "code is data", "function is data" 是否能在这里表现出来 同时其中, 顺序的代码依然作为 List 存在, Object 本身的 keys 却是各自分开的

JS 中, function 必属于一个对象, 其中的 this 指向那个对象, 因而可以视作 method 赋值的概念, 也无非是改写 Object 里的一个 key 而已 作为类比, 我上一段落想象的语言看起来真的存在, 只是看不清具体的样子

我能想到的一些特征是, 每一行的数据都有返回值, 每一行都能被索引到 另外代码可以作为数据传输, 同时其中包含着 Dict, List, Function 以及当然 Value 代码 Dict 的键允许以多线程的方式处理, 而数据通过相互间索引来进行沟通

加上对象与函数的统一

补充些别的想法. 暑假思考 Cirru 时我想对象接受 key 的方式与 Curry 函数接受参数类似 在接收完一个参数之后返回一个值, 或者能继续接收参数的内容. 函数与数据结构类似 结合在一起, 就像 Haskell 中的模式匹配, 能对预定数据直接返回结果

factorial :: (Integral a) => a -> a   
factorial 0 = 1   
factorial n = n * factorial (n - 1) 

借鉴 Lua 的 __index, 那样一个步骤步骤应该是先去找到 key, 否则查找函数来求值

而在我的理解当中就出现很古怪的一套语法了, 定义 : init 开始执行的话

: a 1
: b 2
: f
  : "string" 3
  : x : y
    + x y

: scope-a
  : a 1
  : b 2
  : f
    log (@ a) (@ b)
  : f2 : nil
    @ f

: scope-b
   : --index scope-a

: init
  log (f a b)

  scope-b f
  run (scope-b f2)
tiye commented 11 years ago

今天写到这个突然发现以前想的不好, this 如果是作用域, 一切皆是对象的方便就没有了 比如下面:

Number.prototype.__defineGetter__ 'prev', -> @ - 1
Number.prototype.__defineGetter__ 'next', -> @ + 1

(1).next # 结果是 2

还有在 Ruby 里夸张的 4.times{|x| p x}:

Number::times = (f) -> [1..@].map f
(4).times -> console.log 'print' # 打印 4 次

这么写出来真的好嚣张的感脚..