Sexp 什么都好,就是缩进太难受…尤其是多层嵌套的时候,或者一个很长的参数后面跟一个很短的参数,难受啊。
写代码有时候考验的是「排版」技术。
我觉得 threading 挺好的。楼主设计的占位符就可以解决上面的一些疑问吧。
一般情况下链式调用的情况,换上 threading 应该是无缝衔接。而对于前面的运算结果不是作为第一个参数的情况,确实有点难受,这时候占位符就挺好。我平常用的 Racket 里的 threading,也是有这么一个设计,不过是用 _
作为占位符,那么就可以写成这样:
(~> (make-foo ...)
(do-something-on-foo _ arg ...)
...
(cons _ some-tag))
这样下来还是很容易看清楚实际的参数位置的,而且还可以解决并不想在第一个参数位置插入前面的计算结果的情况。就我个人的习惯,(即使会自动把前面的计算结果隐式地作为第一个参数,)我还是会显式地标记一下参数的位置。
而且 threading 主要不是在于缩进的问题吧,这样的写法可能更贴近计算流程,在不少情况下也符合编码时候的思路。
Clojure 的一些小发明我觉得还挺好,比如这里的 threading,比如对匿名函数的 reader,#(+ % %2)
都可以解决一些特定情况下的痛点。尤其是后面一个 % 的设计,我很喜欢。
应该是不会影响运行时性能的吧,宏展开时就还原成普通的调用了。说影响应该会对编译时期产生影响吧,毕竟多用了一个宏,需要多进行一次/组宏展开了。
Clojure里面的threading macro好用,写elisp的时候就从来没用过threading。
感觉没有啥缩进问题。 一个函数别用两种threading,一个函数别有两个condition branching,一个函数别有两层let,就能搞的比较好看。
(defn fetch [{:keys [params] :as ctx}]
(-> {:url api-url, :params params}
(http/get)
(ensure-success!)
(:body)
(json/parse-string {:key-fn keyword})))
(defn parse [items]
(->> items
(map ->some-item)
(reduce summary (init-result))
(dump datasource)))
(def api-request (comp parse fetch))
(api-request {:params {:x 1}})
Emacs 默认缩进都会把这个变成
(fn1 a
(fn2
(fn3
(fn4 xx
(+ 1 2 3))))
b c)
啊~~ 你关闭了默认缩进? 另外如果 fn2 fn3 也有额外的参数可能不那么直观
我也考虑连 => 都不用 用大括号:
{ 'fn1
'fn2
(fn3 a ~ c)
(fn4 5 6 ~)
(+ 1 2 3)
...}
但好像也没有好看到哪里去。。。
我没有了解这方面的库,只是在去年初看JavaScript 熟悉函数式编程后觉得很有用,然后写了自己的 Common Lisp 版 curry、pipe…
《Ansi Common Lisp》 有简化版的 curry/compose/pipe,我写了加强版。。。
. cl-celwk/functional.lisp at master · Soul-Clinic/cl-celwk · GitHub
另外我也参考以前用的node.js 的 Koa/Express (他好像是参考Ruby on Rails 的??),写了更灵活的 Hunchentoot 版本(smart-server)~~,觉得有用的就拿去。。。
(smart=> "/$name/#id/%money/:type/$others*" fn1 fn2 @next)
;; @next is optional, means go on next route parser, otherwise return the last function value
(smart+ fn1 (id name type money others)
;; name the same with the route path, not necessary same order
"$name => string
#id => integer
:type => symbol
%money => float
$others* => rest (an optional list)
$others? => an optional string"
codes...)
自认为比 Hunchentoot 的 easy-handler 好用灵活…
即使会自动把前面的计算结果隐式地作为第一个参数
我是隐式地作为最后一个参数。。。
我也参考RamdaJS 写了个可以加任意的待输入参数的curry:
(call (call (curry~ 'fn1 ~ a b ~ ~) d) e f) => (fn1 d a b e f)
另外没想到 Clojure 也是用这样风格的符号,我是自己编的~~
另外我这个没涉及多线程, 暂时还没用到。。。
只写了个简单的 race 和时间限制函数,用来限制获取IP地理位置的最长时间(用到SBCL的 gate 函数,好用么?)
这不就是clojure中流行的东西吗?
而且emacs的dash.el库就有这个功能: https://github.com/magnars/dash.el.
dash中的介绍:
Threading macros:
-> (x &optional form &rest more)
->> (x &optional form &rest more)
--> (x &rest forms)
-as-> (value variable &rest forms)
-some-> (x &optional form &rest more)
-some->> (x &optional form &rest more)
-some--> (x &optional form &rest more)
但我在写程序的时候没怎么用过这个特性, 因为抽象层次太高, 不是很好调试…
我没用过 Clojure 啊,买了本书觉得不好看。。
最近准备使用Flutter,那些嵌套n层的代码感觉可读性和代码颜值都不好,一切都是Widge,但大部分Widge都会让你多一层,逻辑上也挺好,很多Widge就是CSS的一部分
Dart 能不能实现类似我这帖子提到的把代码从嵌套变成平行的风格? 例如把 Scaffold(Container(Margin(…))) 变成
Scaffold(
Container,
Margin,
…)
如果不行就反应出了 Lisp 的灵活度,主要是macro,很难被复制啊…
不过更高级点的是不是也可以做一个Lisp 代码转换为 Dart/Flutter 代码的编译器(当然就用Lisp来写)啊? 那样代码写起来会更爽吧?
就像Google也要让Flutter转换为JS实现网页端
记得有个 Lisp付费代码库写 iOS 的,估计是直接编译而不是转换为OC再编译吧?
这件事可以指望clojure,不过目前还没有人去实现。clojure社区还都在用 rn.
对Clojure 没有感觉,语法看起来更加有限制了,风格也有不小差别
我也不知道是因为初遇CL的时候(《黑客与画家》)对那风格有感觉就限制住了
但 Clojure 有成套的做宿主语言的机制,其它的 Lisp 这方面要差很多。
我对Clojure 也没有详细去了解(翻看了下书里的代码就没动力了),但是能不能像CL2 那样写个库把 CL 的语法改成 Clojure 那样?
(主要就是 set-dispatch-macro-character 和 set-macro-character 吧?)
CL 可以改成 Clojure 那样,但是反过来不行。Clojure 的语法扩展是有边界的。
你可以看看julia的源码 那里面有一个用C写的lisp 是用来解析julia
你可以hack一下 自定义自己的解析器!
觉得怎么样呢?
如果你不打算看C语言 那你的lisp也不会高到哪去
C语言早就会啦,十年前就用C语言写了一个VS对战平台DOTA的挤房器,大学舍友都说好用。。。
核心就是 pointer 吧? C语言默认是用 stack,而 Lisp 是默认用 heap (会像OC一样自动释放吧?前版本都是要手动 release,iOS6 以后才让人舒一口气不用手工[obj release])
那是我的表面理解~~
用scheme写了个简单的 Threading function:
(define (~>> arg list-of-funcs)
((foldl compose identity list-of-funcs) arg))
;; For example:
(~>> 0
(list add1
add2
product3
product4))