clojure

    技术2024-03-18  102

    准备将您的脚趾浸入功能性编程池中吗? 保罗·格雷厄姆(Paul Graham)关于Lisp的经典文章也许说服了您尝试一下。 也许您想学习一种新的做事方式。 或者,也许您想查看可以提取多少代码和数据。

    理想情况下,您的Web应用程序需要在IBM Cloud服务器上运行所获得的性能和高可用性。 在本教程中,我将向您展示如何使用Node.js作为IBM Cloud Web应用程序的一部分运行Clojure程序。

    构建和部署Clojure应用程序所需的内容

    一个IBM Cloud帐户(注册您的免费试用帐户,或者如果已经拥有一个帐户,则登录到IBM Cloud )。 HTML和JavaScript的使用知识。 MEAN应用程序堆栈(至少Node.js和Express)的工作知识。 如果您不熟悉它,可以在我的developerWorks系列文章中学习MEAN堆栈的基础知识 。

    一个开发环境,支持将Node.js应用程序上载到IBM Cloud,例如Eclipse。

    在IBM Cloud中运行应用程序后,您可以快速添加Git存储库和持续交付管道以自动开发,测试和部署它。 要进行设置,请在您应用的“概述”页面上,选择添加Git Repo和管道 ,然后您就可以在浏览器中进行所有开发和部署。

    你好Clojure

    Clojure是Lisp的一种方言,可以在JVM或JavaScript中执行。 要从Node.js应用程序运行它,请使用clojurescript-nodejs包。

    编辑packages.json文件,将clojurescript-nodejs添加到软件包列表中: "dependencies": { "express": "4.13.x", "cfenv": "1.0.x", "clojurescript-nodejs": "*" }, 创建Clojure环境: // Get a Clojure environment var cljs = require('clojurescript-nodejs'); 定义一个Clojure函数( 有关代码的详细说明,请参见下一部分): cljs.eval('(ns app (:require clojure.string)) ' + '(defn h2 [str] (clojure.string/join ["<h2>" str "</h2>"]))'); 使用Clojure代码响应URL请求( 有关此代码的详细说明,请参见下一部分: app.get("/trivial", function(req, res) { res.send(cljs.eval('(ns app) (h2 "Hello, Clojure")')); }); 运行该应用程序并查看结果。

    Clojure函数如何工作?

    如果您已经了解Clojure,甚至是其他Lisp方言,那么您将对本材料很熟悉。

    在上面的步骤3中 ,当您定义Clojure函数时, cljs.eval函数将接收字符串并将其评估为Clojure代码。 在这种情况下,字符串之间用撇号( ' )分隔,因此也可以使用引号( " )。

    cljs.eval('

    下面的Clojure语句有两件事:

    将我们的命名空间定义为app 。 这很重要,因为如果不将变量,函数等放在名称空间中,就无法定义它们。 导入clojure.string库。 该库包含字符串处理函数,这些函数在生成HTML时很有用。 (ns app (:require clojure.string))

    下面的Clojure语句是实际的函数定义。 因为这是我们定义的第一个函数,所以让我们详细介绍一下。 第一部分定义了一个名为h2的函数。

    (defn h2

    下面的部分指定该函数将具有一个称为str参数。 请注意,该部分使用方括号( [ ] )代替了典型的方括号。 Clojure与传统的Lisp不同,它使用不同的括号类型来表示不同的内容。 方括号定义了一个向量,该向量用作列表(用正括号括起来的项目的集合),但是允许更快地访问中间元素并更快地在末尾插入项目。

    [str]

    接下来,从库中调用函数的语法是<library name>/<function> 。 这部分从clojure.string调用join 。 它是一个接收向量并返回包含所有项目的字符串的函数。 在这种情况下,它将字符串括在h2标签中:

    (clojure.string/join ["<h2>" str "</h2>"])

    整个函数定义是一个列表。 最后的圆括号结束了。

    )

    在上面的步骤4中 , app.get调用内的Clojure代码更加简单。 它首先将自身标识为app名称空间的一部分,然后调用之前定义的h2函数。 cljs.eval函数返回最后一个表达式的结果,在本例中为h2调用。 然后,这就是res.send发送给用户的值。

    res.send(cljs.eval('(ns app) (h2 "Hello, Clojure")'));

    用Clojure编写整个应用程序

    对于更复杂的事情,将整个应用程序放在Clojure中更容易,而在JavaScript中只包含一个小存根。

    JavaScript存根

    下面是残留的app.js。 第一行是编辑器指令,其目的是避免出现错误:

    /*eslint-env node*/

    接下来,创建一个Clojure环境:

    // Get a Clojure environment var cljs = require('clojurescript-nodejs');

    除了评估字符串,还评估文件:

    // Evaluate the application library cljs.evalfile("app.cljs");

    Clojure应用

    app.cljs的第一部分等效于模板随附的app.cljs 在这里,逐行解释。

    这些行定义名称空间( my-ns )并导入我们需要的两个库: clojure.string和cljs.nodejs 。 第一个只是导入,而第二个则包含在带有:as node的向量中,以指定其余名称空间中的代码将包称为node 。

    (ns my-ns (:require clojure.string [cljs.nodejs :as node] ) )

    Clojure注释以分号( ; )开头。

    ; Get the application environment

    此行显示了在Clojure中用于与基础JavaScript代码进行通信的两种机制。 js/语法用于访问JavaScript全局变量,例如require 。 该代码段(js/require "cfenv")等同于require("cfenv") 。 要在Clojure中调用对象的方法,语法为(.< method > < object > < parameters, as needed >) 。 片段(.getAppEnv (js/require "cfenv"))等效于require("cfenv").getAppEnv() 。

    最后, def定义了一个全局变量。 它计算第二个参数的值,并将其分配给第一个参数。 总的来说,这行等效于var appEnv = require("cfenv").getAppEnv() :

    (def appEnv (.getAppEnv (js/require "cfenv")))

    除了没有方法调用外,此行与appEnv行类似:

    ; Create an express application (def express (js/require "express"))

    此行显示了另一种访问JavaScript的方式: (js* < expression >)运行该JavaScript表达式并返回值。 这行等效于var app = require("express")() :

    (def app (js* "require('express')()"))

    此行显示了另一种使用JavaScript的机制。 当您有一个对象时,语法(aget < object > < attribute >)获取该对象的属性。 结合(.< method > < object > < parameters, as needed >)语法,此整行表示app.use(express.static("public")),这是用于指定在其中提供静态文件的代码公共目录。

    ; Static files (.use app ((aget express "static") "public"))

    这是一个代码示例,它指定对/ trivial路径的GET请求的响应。 创建匿名函数的语法为( fn [< parameters >] (< expression >) ) 。 将其与JavaScript方法的调用方式结合起来,等效于app.get( "/ trivial ", f unction(req, res) {res.send( " h ello")}) :

    ; Respond to a request (.get app "/trivial" (fn [req res] (.send res "Hello")))

    这等效于启动应用程序以侦听JavaScript中appEnv中指定的端口的代码:

    ; Start listening (.listen app (aget appEnv "port") "0.0.0.0" (fn [] (.log js/console (aget appEnv "url")) ) )

    回应用户

    无论用户输入如何,都返回固定响应的Web应用程序不是很有用。 实际的Web应用程序必须处理用户输入。 此输入通常以三种形式出现:在路径中,在查询中或在HTTP请求的正文中。

    路径参数

    在Express(Node.js HTTP服务器库)中,通过指定:< keyword >作为路径组件来表示路径参数。 然后在req.params提供结果。 以下是相关代码。

    首先,创建一个带有返回表单页面链接的字符串。 这仅仅是装饰性的,以使用户易于返回。

    ; Go back to the form (def goBackForm "<hr /><a href=\"/form.html\">Go back</a>");

    这是接收/process-path/< whatever >所有GET请求的实际调用:

    ; Process a part of the path (.get app "/process-path/:param"

    请求URL时调用的函数。 它使用两个字符串的串联来调用res.send :参数值和返回表单页面的链接HTML(先前定义为goBackForm )。

    (fn [req res] (.send res (clojure.string/join [

    请记住, (*js < expression >)将表达式评估为JavaScript,然后将值返回给Clojure。 这使得读取JavaScript变量(例如req.params.param变得容易。

    (js* "req.params.param") goBackForm ]) ) ) )

    查询参数

    查询参数甚至更易于处理。 不需要用冒号指定参数名称; 它们已经由浏览器从HTML创建。

    ; Respond to GET requests with a query (.get app "/process-form" (fn [req res] (.send res (clojure.string/join [ (js* "req.query.get") goBackForm ]) ) ) )

    身体参数

    主体中的参数(由POST和PUT方法使用)需要更多处理。 首先,您需要将process-body添加到packages.json文件。 然后,您需要将其用作中间件,以便在对主体条目进行编码时对其进行处理。 在该步骤之后,实际参数的访问方式与其他方法一样。

    ; Parse the body (.post app "*"

    JavaScript表达式返回一个函数。 在JavaScript和Clojure中,函数均可用作返回值和函数参数。

    (js* "require('body-parser').urlencoded({extended: true})") ) ; Respond to POST requests (.post app "/process-form" (fn [req res] (.send res (clojure.string/join [ (js* "req.body.post") goBackForm ]) ) ) )

    将所有参数放在一起

    通常,您不在乎参数来自何处; 您只关心他们的价值观。 因为参数具有键和值, 所以要使用的适当数据结构是map 。

    下面是执行此操作的代码示例。 第一部分是函数定义。 因为这是一个命名函数,可以从其他地方调用它,所以它是用defn而不是fn定义的。

    ; Given a request object, return a map with all the parameters (defn getParams [req]

    let调用定义了可以在表达式的末尾使用的多个局部变量 (大致来说,它们在功能编程中实际上称为符号)。 在这种情况下,它定义了三个: queryMap , bodyMap和paramMap 。

    (let [ ; Define local variables

    这三个定义都使用js->clj函数将JavaScript对象转换为Clojure映射。

    queryMap (js->clj (js* "req.query")) bodyMap (js->clj (js* "req.body")) paramMap (js->clj (js* "req.params")) ]

    merge功能将三个不同的映射合并到一个映射中(如果键相同,则后面的映射中的值会覆盖前面的映射中的值)。 该表达式是let的结果,因此也是整个函数的结果。

    (merge queryMap bodyMap paramMap) ) ; end of let ) ; end of defn

    这实际上是对请求的响应:

    ; Respond requests with parameters from all over (.all app "/merge/:pathparam" (fn [req res]

    这是let的另一种用法。 来自命令式编程的背景,我发现在大多数处理过程中,通过定义然后使用的符号,可以更轻松地编写和理解函数。

    (let [ ; local variables

    调用我们之前定义的函数以获取参数。

    params (getParams req) ] (.send res (clojure.string/join [

    现在,我们要使用JSON.stringify将地图转换为JSON。 为此,请使用clj->js将地图转换回JavaScript对象:

    (.stringify js/JSON (clj->js params)) goBackForm ]) ; end of clojure.string/join ) ; end of res.send ) ; end of let ) ; end of fn [req res] ) ; end of app.all

    其他API速查表

    用于Node.js和Express软件包的相同工具可以用于任何其他API或软件包。

    要使用软件包,请使用(def <var name > (js/require "< package name >")) 。 要调用方法,请使用(.< method > < object > < parameters, if any >) 。 要使用JavaScript中的全局变量,请使用js/< variable > 。 如果所有其他方法均失败,请使用以下语法评估JavaScript表达式: (js* "< JavaScript expression >") 。

    结论

    本教程为您提供了用Clojure编写Web应用程序的基础知识。 下一步是学习如何实际使用功能编程功能来使您的应用程序更好。 关于这个主题有很多书籍。 我最喜欢的是迈克尔·福格斯(Michael Fogus)和克里斯·豪斯(Chris Houser)创作的《克洛瑞尔的欢乐》 。


    翻译自: https://www.ibm.com/developerworks/cloud/library/cl-clojure-node-bluemix-app/index.html

    Processed: 0.011, SQL: 9