• Haskeller 的 PureScript 试水

    |

    – 讲道理虽然自称 Haskeller ,但是其实我根本不会 Haskell(

    为什么我要尝试 PureScript ?

    因为它是 Haskell 和 JavaScript 生的娃啊。

    Facebook 的 React.js 可以说是给 JavaScript 社区带来了一次 functional programming 的热潮。然而 JavaScript 毕竟不是真正的函数式编程语言——虽然有着 closure 等特性让它可以支持这种独特的范式,但是整体而言它还是基于顺序指令和事件回调的语言。

    PureScript 是一门年轻的语言——现在在 github 上可以查到的最早提交来自于 30 Sep 2013 。它致力于创造一种“类似 haskell 的编程体验”和“生成可读的 JavaScript 代码”。于是,有了它,我们终于可以在项目前端部分中愉快的写“ monoid in the category of endofunctors of X, with product × replaced by composition of endofunctors and unit set by the identity endofunctor ”了(

  • 又是一篇lambda演算介绍:过程与数据结构(一)

    |

    λ-calculus

    自从了解了lambda演算,我就觉得,每个写代码的人都应该学学它。——沃兹·基硕德

    λ演算的详细介绍可以从 Wikipedia 找到。

    简单来说,这是一个比图灵机早提出几个月的可计算性模型,表达优美形式简洁反正比图灵机高到不知道哪里去了,但是愚蠢的人类没办法在机器上直接实现它所以只能照着图灵机来造计算机,导致图灵那家伙小人得志真正的大神却退居二线(相对图灵来说的二线)

    (为了方便表示,以下将 λV.E 记为 V -> E

    数据

    在原始的 lambda 演算中,没有直接的“数字”概念。

    lambda 演算的全部规则仅有两条(其实也可以说是三条),在这种简洁的体系中没有一个容纳原生“数字”概念的空间。

    但是神奇的是,仅仅只有两条“运算方法”,却能够构造出整个直觉上的“数字”体系。这种通过“过程”构造的“数字”,我们叫它“Church Numerals”,因为它正是由引入 lambda 演算的那位神人提出的。

  • 重构半衰期

    所谓“重构半衰期”,就是“写一段代码,在时间T之后你会觉得这一段代码写得像坨 shit ”中 T 的最小值。

    之所以突然想到这个概念,是因为我发现,现在的我看几个月之前的文章简直就是羞耻 play :

    *大综合楼,一跃解千愁

    Haskell 练习

    记一次离散作业

    第一、二篇,就算不说每次都写一遍的 replicate,那两级 where 就足够让人迷醉,更不用说当时完全不知道用 monad , 甚至 lambda

    第三篇简直令人发指,双手颤抖的想要毁尸灭迹——当然最后被理智所制止。

    根据马克思主义“自我否定,螺旋上升”的事物发展理论,这个 min(T) 可以代表你的学习进展。之所以会觉得之前的东西是 shit 正是因为现在的水平和当初已经不是同一层面。

    想通了这点,我便心安理得的继续写着 shit 一样的代码了。

  • OOM Killer 大爆炸

    |

    讲道理把 OOM killer 设成 2 居然会把整个系统搞挂……

    enter image description here

    于是整个 OS 的内存管理全部挂掉,能用的命令只有 echo

    还好是可以以文件的形式强行修改服务进程的。

    echo 0 > /proc/sys/vm/overcommit_memory
    

    然后试了试 ls ,终于能够运行。

    一激动之下多按了几个↑,然后……

    enter image description here

    ……………………………………

  • 做了一点微小的贡献

    |

    这次重写 Blog ,主要做了三件事:

    1. 试用了 Redux
    2. 实现了服务端渲染
    3. 重写了 yue ——现在叫 eliter

    只做了点微小的贡献,很惭愧。

  • React Redux Isomorphic 踩坑记——数据同步

    |

    同构渲染的好处大家都有目共睹,SEO 优化、首屏速度之类的。这次重写 blog,基于试水的考虑使用了 react + redux 实现同构渲染,然后果不其然踩到了点小坑。

    state 带来的难题

    起初我只是想感受一下 react 的服务端渲染,并没有考虑把 redux 这种应对高复杂度应用的框架弄来。然而很快便发现,同构渲染要解决的首要问题——数据同步问题,在传统 react 的 props + state 模式下很难做到。

    一般纯客户端使用 react 时,习惯将数据保存在 state 中,然而在服务端渲染时很难控制组件的 state (至少我在文档中根本没找到解决办法),于是难以将客户端的组件数据与服务端渲染时的数据保持一致。当时想到的一个办法就是将所有的状态全部抽出来由最顶层组件的state提供,然后以props的形式分发到下级组件。然后开始写之后发现,这™不就是 Redux ?于是便高高兴兴的 npm install redux --save

    在服务端分发数据

    Readux 除了能解决数据同步的问题外,还顺带解决了服务端对不同组件数据分发的问题。比如之前对于加载文章的解决方式是:当组件挂载后,ajax拉下组件对应的文章数据保存在state中,然后渲染。

    export default class SingleArticle extends Component {
        constructor(props) {
            super(props);
            this.state = {/* Init */}
        }
        componentDidMount() {
            ajax.get(/*load*/)
                .then(/*handle*/)
        }
        render() {
            // ....
        }
    }
    

    然而在服务端是不会触发组件生命周期更不能ajax的。

    那么应该如何在前后端公用一套代码的情况下为每个组件加载不同的数据呢?Redux 的另一个好处就体现出来了——它的 action -> reducer 逻辑是独立的,不与组件相关的。于是便可以单独为每种情况抽取“加载”的逻辑,对应不同的 action ,然后 dispatch ,世界和平。

    顺着这种思路下去,又一个问题出现了——如何实现对于不同的 URL 请求,在服务端分发不同的“加载”逻辑,这一套“加载”逻辑还要™和客户端公用一套代码。幸好 react-router 已经给我们提供了用于服务端渲染的 API ,可以方便的获取当前应该渲染的组件。于是解决方法也浮出水面——将数据加载作为组件类的 static 方法(如 static fetchData() ,然后利用 router 的 api 获取当前组件类,调用 fetchData() 触发 action

    这样加载数据的上层逻辑就统一了,但是服务端和客户端对于 fetchData() 的实现肯定不能一样——客户端使用ajax,服务端从model读取数据。

    为了保证调用形式的统一,我们可以使用 redux 提供的 middleware 方案抹平差异——使用 action 触发 middleware ,而服务端和客户端分别各自实现。

    最后的服务端形式便化为了这样

    // Component.jsx
    @connect(/** Connect functions **/)
    export default class Page extends Component {
        // ...other methods
        render() {
            const { data } = this.props
            return (
                <div>
                    <ContentView>{ data }</ContentView>
                    <Comment />
                </div>
            )
        }
        static fetchData(store, props) {
            const id = props.params.id
            return store.dispatch(loadAction(id))
        }
    }
    
    // server render
    
    const matchRouter = (location, routes) => {
        return new Promise((res, rej) => {
            match({
                location,
                routes
            }, (error, redirectLocation, renderProps) =>
                res({error, redirectLocation, renderProps}))
        })
    }
    export default function* () {
        /** react-router 匹配 **/
        const {
            error,
            redirectLocation,
            renderProps
        } = yield matchRouter(this.url.path, routes)
    
        if (error) {
            /** 错误处理 **/
        } else if (redirectLocation) {
            /** 重定向 **/
        } else {
            const
                midd = applyMiddleware(apiFactory(makeRequest)),
                store = createStore(reducer, midd)
            // 获取将要渲染的组件
            const components = renderProps.components.filter(c => c && c.fetchData)
            // 分别触发各组件的加载方法
            yield Promise.all(components.map(c =>
                                c.fetchData(store, renderProps)))
            /** 渲染,发送response **/
        }
    }
    

    其中 apiFactory 的实现为

    export const CALL_API = Symbol('CALL_API')
    export const apiFactory = makeRequest => store => next => action => {
        if (!action[CALL_API]) {
            return next(action)
        }
        const req = action[CALL_API]
        const { method, url, success, fail, extra } = req
        return makeRequest(url, { method })
            .then(/** handle data **/)
    }
    

    服务端环境和客户端环境各自传入不同的 makeRequest 实现,实现抹平上层差异。

    为了偷懒,我在服务端是用 supertest 向自身发送 http 请求调用 api 实现。

    将数据同步到客户端

    解决服务端获取数据的问题后,终于到了最后一步——将数据同步到客户端。

    解决办法非常的简单粗暴——渲染一个模板,把初始状态转为 json 字面量挂载到客户端的 window 对象属性下,客户端脚本读取初始状态生成store。

    /** 服务端 **/
    export default function* () {
        const {
            error,
            redirectLocation,
            renderProps
        } = yield matchRouter(this.url.path, routes)
        if (error) {
        } else if (redirectLocation) {
        } else {
            const
                rendered = getRendered(store, renderProps),
                initial_state = JSON.stringify(store.getState())
            /** 渲染模板,发送数据 **/
            this.render('client', { rendered, initial_state })
        }
    }
    
    /** 客户端 **/
    const
        initial_state = window.__INITIAL_STATE__,
        middleware = applyMiddleware(apiFactory(makeRequest)),
        store = createStore(reducer, initial_state, middleware),
        history = syncHistoryWithStore(browserHistory, store)
    

    模板文件:

    doctype html
    html(lang="zh-cn")
        head
    
        body
            div#client!= rendered
            script!= 'window.__INITIAL_STATE__ = ' + initial_state
            script(src = static_path + "client.js")
    

    于是,终于可以舒心的写真正的“客户端与服务端公用”的代码了……

  • 近期的坑

    以下按坑大小升序排列

    • 博客 Dashboard 的前端

      顺便和 new.hcyue.me 一起上线

      新版博客已经上线了还没做完,药丸。

    • Structure and Interpretation of Computer Programs

      坑开的太久,是时候把最后一章看完了

    • 造MVVM的轮子

      大概类似 vue 那样?

    • 看看Elm

      号称最牛逼的 functional 界面开发呢

    • 《计算机科学中的现代逻辑学》

      看了两章感觉还不错

    • 天坑《抽象代数基础》

      我一个程序猿,怎么就去学数学了呢?还是要考虑历史的进程。(都是被fp带的)

  • CPS变换与JavaScript流程控制

    |

    正如上一篇文章所说,利用CPS原理可以进行流程控制。那么在JavaScript编程中究竟如何消灭callback hell呢?

    co 正是一个解决JavaScript异步流程难题的库。它利用ES2015标准中的 yield,实现了与(本来被期望加入ES2016标准却因为大厂跳票没赶上deadline所以只能明年再说的)async/await 相似的功能而红透半边天。其原理就是利用 yield 的状态机特性,将(看起来是)同步的代码转换为CPS的形式。

    co的源文件有两百多行,而其核心思想其实非常简洁——不断的将 yieldnext 值追加到 promise 链中,达到“一步接一步”执行的效果。

    这个核心思想用coffeescript写出来不到十行:

    fuck = (gen) ->
        it = do gen
        go = (res) =>
            p = it.next(res)
            if p.done
                return
            p.value.then go
        do go
    
  • 学习笔记:Continuation-passing Style

    |

    说来惭愧,虽然对于 CPS transform 这个概念略有耳闻(当时的理解是一种将普通递归调用变化为尾递归的方法),但是对于 CPS 这个概念还是在看轮子哥的文章 时才想起去详细了解。终究还是CS基础不牢。

    Definition

    In functional programming, continuation-passing style (CPS) is a style of programming in which control is passed explicitly in the form of a continuation. This is contrasted with direct style, which is the usual style of programming. Gerald Jay Sussman and Guy L. Steele, Jr. coined the phrase in AI Memo 349 (1975), which sets out the first version of the Scheme programming language.[1][2] John C. Reynolds gives a detailed account of the numerous discoveries of continuations.[3]

    简单来说,就是一种程序调用方式,在每次函数调用时通过传参决定下一步执行的内容。