lisp方言_使用此Lisp方言为OpenWhisk编写清晰简洁的代码

对函数式编程感兴趣? 作为服务的功能(FaaS)如何? 在本教程中,您将通过在Clojure中编写OpenWhisk操作来学习将这两者结合在一起。 这样的动作比用JavaScript编写的动作更清晰,更简洁。 同样,函数式编程是FaaS的更好范例,因为它鼓励编程而不依赖副作用。

这是三个教程系列中的第一篇,这些教程通过开发库存控制系统来说明Clojure和OpenWhisk。 在第1部分中,您将学习如何使用Clojure通过Node.js运行时和ClojureScript包来编写OpenWhisk操作。 第2部分将教您如何使用OpenWhisk序列将动作组合为有用的块,这些块可以完成应用程序的工作。 第3部分将向您展示如何与外部数据库交互,以及如何使用日志记录在OpenWhisk应用程序中调试自己的Clojure。

构建应用程序所需的条件

  • OpenWhisk和JavaScript的基本知识(Clojure是可选的,本文介绍了您在需要时需要的内容)
  • 一个Bluemix帐户( 在此处注册 )。

为什么这样

实际上,这实际上是两个独立的问题:

  1. 为什么要用Clojure编写OpenWhisk操作,而不是使用本机JavaScript?
  2. 如果要用Clojure编写,为什么要使用OpenWhisk?

让我们依次看一看...

为什么要用Clojure编写OpenWhisk操作,而不是使用本机JavaScript?

Clojure是Lisp的方言,并提供该语言的所有编程优势(例如不变性和宏)。 这需要一些时间来习惯,但是一旦完成,您就可以编写清晰,简洁的代码。 例如,此行用了450多个单词来在本文后面进行解释,任何了解Clojure的人都可以一眼理解它:

"getAvailable" {"data" (into {} (filter #(> (nth % 1) 0) dbase))}

如果要用Clojure编写,为什么要使用OpenWhisk?

FaaS平台(例如OpenWhisk)使构建高度模块化的系统变得很容易,该系统仅通过定义明确的接口进行通信。 这使得开发模块化的应用程序变得容易,而没有副作用。 同样,FaaS需要更少的资源,因此比拥有不断运行的应用程序便宜。

开发工具链

IBM并未正式推荐Clojure,并且OpenWhisk没有Clojure运行时。 我们将在OpenWhisk上运行Clojure的方式是使用ClojureScript包,该包将Clojure代码编译为JavaScript。 然后可以由Node.js运行时执行JavaScript。

使用Node.js运行时对动作进行编码的最常见方法是将所有内容放入一个文件中,并且具有一个主要函数来接收参数并返回结果。 这种方法很简单,但是您的代码仅限于使用OpenWhisk已经拥有的任何npm库。

另外,您可以使用package.json文件编写一个更完整的Node.js程序,将其放入zip文件中,然后上传。 这使您可以使用其他库,例如clojurescript-nodejs 。 有关更多详细信息,请阅读Raymond Camden 撰写的在OpenWhisk中创建压缩的动作 ”。

Windows应用商店包含一个Linux子系统,您可以直接从Windows运行它。 就个人而言,我更喜欢在Linux上安装工具链-这样,我就可以直接从Windows笔记本电脑上进行安装。 以下命令是在该环境中发出的。

  1. 安装npm(这可能是一个耗时的过程,因为它需要很多其他软件包):
    sudo apt-get update
    sudo apt-get install npm
  2. 创建具有以下内容的package.json文件(可在GitHub上找到):
    {
      "name": "openwhisk-clojure",
      "version": "1.0.0",
      "main": "main.js",
      "dependencies": {
        "clojurescript-nodejs": "0.0.8"
      }
    }
    注意:当前软件包版本是0.0.8。 通过在package.json文件中指定版本,可以确保如果将来发布的版本不向后兼容,应用程序也不会损坏。
  3. 创建一个main.js文件(在GitHub可用 ):
    // Get a Clojure environment
    var cljs = require('clojurescript-nodejs');
    
    
    // Evaluate the action code
    cljs.evalfile(__dirname + "/action.cljs");
    
    
    // The main function, the one called when the action is invoked
    var main = function(params) {
    
      var clojure = "(ns action.core)\n ";
    
      var paramsString = JSON.stringify(params);
      paramsString = paramsString.replace(/"/g, '\\"');
    
      clojure += '(clj->js (cljsMain (js* "' + paramsString + '")))';
    
      var retVal = cljs.eval(clojure);
      return retVal;
    };
    
    exports.main = main;
  4. 创建一个action.cljs文件(在GitHub可用 ):
    (ns action.core)
    
    
    (defn cljsMain [params]
      {:a 2 :b 3 :params params}
    )
  5. 运行以下命令以安装依赖项:
    npm install
  6. 安装zip程序。
    sudo apt-get install zip
  7. 压缩操作所需的文件。
    zip -r action.zip package.json main.js action.cljs node_modules
  8. 下载适用于Linuxwsk可执行文件 (此链接适用于64位版本)。 将其放在路径中的目录中,例如clojurescrip/usr/local/bin
    sudo mv wsk /usr/local/bin
  9. 获取您的身份验证密钥,然后运行wsk命令登录。
    wsk property set --apihost openwhisk.ng.bluemix.net --auth <your key here>
  10. 上载操作(在这种情况下,将其命名为test )。
    wsk action create test action.zip --kind nodejs:6
  11. 转到Bluemix OpenWhisk UI,单击左侧栏上的Develop ,然后运行操作test 。 响应应类似于以下屏幕截图:
    Bluemix OpenWhisk测试响应

注意:如果您在日志中查找该操作,它将表明您使用的是未声明的变量。 您可以放心地忽略该警告消息。

它是如何工作的?

我之前已经写过关于如何集成Clojure和Node.js的文章 ,因此这里的解释将略为简化。 如果您需要更多详细信息,可以随时在此处找到它们。

查看存根main.js,它从创建ClojureScript(转换为JavaScript而不是Java的Clojure)环境的代码开始,然后评估action.cljs文件。

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

// Evaluate the action code
cljs.evalfile(__dirname + "/action.cljs");

这种方法很简单,虽然每次重新启动操作都需要重新编译Clojure,但这并不像听起来那样糟糕。 初始化代码(即不在代码main或通过在代码中称为main )被执行一次,然后将结果由OpenWhisk缓存。 因此,只有长时间不调用该操作时,Clojure才会重新编译。

接下来是main功能。 使用JavaScript哈希表中的参数调用它。

// The main function, the one called when the action is invoked
var main = function(params) {

我们通过声明自己是action.core命名空间的一部分来开始创建Clojure代码。

var clojure = "(ns action.core)\n ";

将参数导入Clojure有点复杂。 当更简单的解决方案失败时,我转向了这一解决方案,它将参数编码为JavaScript Object Notation(JSON)字符串。 JSON可以作为JavaScript表达式进行评估,可以使用ClojsreScript中的语法(js* <JavaScript expression>)对其进行评估。 但是,JavaScript表达式是一个字符串, Clojure中的字符串用双引号(“)括起来,双引号(”)JSON.stringify使用的相同。因此,下一行确保参数字符串中的双引号被转义。请注意当参数值包含双引号时,这种简单的解决方案将失败;我计划在本系列的第三篇文章中展示一种更好的解决方案。

var paramsString = JSON.stringify(params);
paramsString = paramsString.replace(/"/g, '\\"');

此行添加了实际调用Clojure中的动作的代码。 在Clojure(及其祖先Lisp)中,函数调用不表示为常用function(param1, param2, ...) ,而是表示为(function param1 param2 ...) 。 从最里面的括号到最外面的括号,此代码首先获取参数字符串并将其解释为JavaScript表达式。 然后,它将使用该值调用函数cljsMain 。 的输出cljsMain ,一个Clojure的哈希表,然后被转换到使用JavaScript哈希表clj->js

clojure += '(clj->js (cljsMain (js* "' + paramsString + '")))';

最后,调用Clojure代码并返回返回值:

var retVal = cljs.eval(clojure);
  return retVal;
};

该行将导出main功能,因此它将对运行时可用。

exports.main = main;

action.cljs中的动作本身甚至更简单。 第一行声明名称空间action.core 。 Clojure起源于Java虚拟机语言,名称空间具有Java中类名的某些功能。

(ns action.core)

这段代码定义了cljsMain函数。 通常,Clojure函数是使用(defn <function name> [<parameters>] <expression>) 。 该表达式通常是一个函数调用,但不一定必须如此。 在这里,它是一个文字表达 。 Clojure中的哈希表用大括号( {} )括起来。 语法为{<key1> <value1> <key2> <value2> ...} 。 在这种情况下,关键是关键字,词以冒号开始( : ),这在Clojure的手段,他们不能为别的符号。 该哈希表中的值是两个数字,并将参数传递给操作。

(defn cljsMain [params]
  {:a 2 :b 3 :params params}
)

库存控制系统

本文的示例应用程序是一个库存控制系统。 它有两个前端-一个是可以减少库存的销售点,另一个是可以让经理购买替换物品或更正库存编号的重新订购系统。

“数据库”操作

要抽象数据库,请创建一个处理所有数据库交互的操作。 根据参数,此操作需要执行以下操作之一:

  • getAvailable-获取可用物品(您有库存的物品)的列表,以及每个物品有多少。
  • getAll-获取所有物品的列表,包括缺货的物品,以便重新排序。
  • processPurchase —获取项目清单以及每个项目的购买数量,然后从库存中扣除。
  • processReorder-获取重新排序的项目和金额的列表,并将其添加到库存中。
  • processCorrection-获取物料清单和正确的数量(在实际盘点库存之后)。 此数量可能大于或小于数据库中当前的数量。

目前,该数据库将是一个哈希表,其中项目名称为键,而库存量为值。 请注意,每次重新启动操作过程时,都将重置此值。

  1. 在新目录(例如,…/ inventory / dbase_mockup)中,创建与测试操作相同的三个文件: package.jsonmain.jsaction.cljs 。 前两个具有与测试操作相同的内容。 您可以在GitHub中找到第三个action.cljs。
  2. 运行以下命令以安装依赖项:
    npm install
  3. 压缩动作:
    zip -r action.zip package.json main.js action.cljs node_modules
  4. 上传动作:
    wsk action create inventory_dbase action.zip --kind nodejs:6
  5. 使用测试输入运行操作以查看会发生什么:
    输入项 预期结果
    {
        "action": "getAll"
    }
    {
      "data": {
        "T-shirt L": 50,
        "T-shirt XS": 0,
        "T-shirt M": 0,
        "T-shirt S": 12,
        "T-shirt XL": 10
      }
    }
    {
        "action": "getAvailable"
    }
    {
      "data": {
        "T-shirt L": 50,
        "T-shirt S": 12,
        "T-shirt XL": 10
      }
    }
    {
        "action": "processCorrection",
        "data": {"T-shirt L": 10, "Hat": 15}
    }
    {
      "data": {
        "T-shirt L": 10,
        "Hat": 15,
        "T-shirt XS": 0,
        "T-shirt M": 0,
        "T-shirt S": 12,
        "T-shirt XL": 10
      }
    {
        "action": "processPurchase",
        "data": {
            "T-shirt L": 5,
            "T-shirt S": 2 
        }
    }
    {
      "data": {
        "T-shirt L": 5,
        "Hat": 15,
        "T-shirt XS": 0,
        "T-shirt M": 0,
        "T-shirt S": 10,
        "T-shirt XL": 10
      }
    }
    {
        "action": "processReorder",
        "data": {
            "T-shirt L": 20,
            "T-shirt M": 30
        }
    }
    {
      "data": {
        "T-shirt L": 25,
        "Hat": 15,
        "T-shirt XS": 0,
        "T-shirt M": 30,
        "T-shirt S": 10,
        "T-shirt XL": 10
      }
    }

它是如何工作的?

本节介绍了一些Clojure概念。 建议您通过在Clojure命令行 (称为REPL,用于“读取,评估和打印循环”)中打开的浏览器选项卡进行阅读,以边做边学。

action.cljs的第一行定义名称空间:

(ns action.core)

接下来,我们使用def命令将dbase定义为哈希表。 该语法与JavaScript中的语法有些相似,但是有几个重要的区别:

  • 没有冒号( :键和值之间)。
  • 您可以使用逗号( , )作为不同键值对( {"a" 1, "b" 2, "c" 3} )之间的分隔符。 但是,您也可以在不更改表达式值的情况下省略分隔符(因此{"a" 1 "b" 2}{"a" 1, "b" 2} )。
  • 您在这里看不到它,但是键不必是字符串。 它可以是任何合法值:
    (def dbase {
      "T-shirt XL" 10
      "T-shirt L" 50
      "T-shirt M" 0
      "T-shirt S" 12
      "T-shirt XS" 0
      }
    )

然后,使用defn定义函数cljsMain 。 它需要一个参数,即带有参数的哈希表。 由于main.js的编写方式,这是一个JavaScript哈希表,而不是Clojure表。

(defn cljsMain [params] (

下一行使用let函数。 此函数获得一个向量-本质上是一个用方括号( [] )包围的列表-以及一个表达式。 向量具有标识符,后跟要在let表达式持续时间内分配给它们的值。 使用let允许您以接近命令式编程的格式进行编程。 向量内的代码可以用JavaScript编写为:

var cljParams = js→clj(params);
	var action = get(cljParams, "action");
	var data = get(cljParams, "data");

这行代码开始在Clojure中运行:

let [

如上所述, params的值是JavaScript哈希表。 js->cljs函数将其转换为Clojure哈希表(与cljs->js使用的cljs->js相反)。

cljParams (js->clj params)

其他两个符号actiondata获得特定参数的值。 在哈希表中获取值的一种方法是函数(get <hash table> <value>) 。 并非所有动作都具有data参数,但是没关系-在这种情况下,我们只会得到nil ,而不是错误情况。

要查看实际效果,请在REPL网站上运行以下代码(get {:a 1 :b 2 :c 3} :b) 。 请记住,以冒号开头的单词是不能用作符号的关键字,因此无需将它们视为字符串。

action (get cljParams "action")
      data (get cljParams "data")
    ]

函数case行为与JavaScript(从C,C ++和Java继承它们)的switch...case语句相同。

(case action

"getAll"操作是最简单的,只需在参数"data"下返回数据库即可。

"getAll" {"data" dbase}

下一步操作将寻找可用的物品,即您有库存的物品。 实现此功能的表达式并不特别复杂,但是它使用了几种特定于函数式编程的技术。

在命令式编程中,您告诉计算机该怎么做。 在函数式编程中,您告诉计算机您想要什么,然后让计算机找出如何执行此操作。 在这种情况下,您希望计算机为您提供项目数大于0的所有项目。

为此,您可以使用filter功能。 此函数接收一个函数和一个列表,并仅返回参数函数为其返回真值的那些项(大多数值为真)。 当给filter一个哈希表时,它就像是一个有序对的列表,每个对由一个键及其值组成。

形式#(<function>)定义了一个函数(没有给出名称,因此它是一个匿名函数)。 在该函数定义中,您以百分比( %%1 )的形式引用函数的唯一参数或第一个参数(如果有多个参数)。 您可以将其他参数称为%2%3等。要从列表或向量中获取值,可以使用第nth函数。 此函数从0开始计数,因此列表中的第一个值为(nth [<list>] 0) ,第二个为(nth [<list>] 1)等。

REPL网站 (nth [:a :b :c :d] 2)以查看nth工作方式。 要查看运行中的匿名函数,请运行(#(+ 3 %) 3) 。 匿名函数将其获得的任何值加三,因此结果为3 + 3或6。

函数#(> (nth % 1) 0)在参数中找到第二个值,并检查它是否大于0。由于filter与哈希表一起工作的方式,该值始终是值,即项目数。 为了您的目的,您只关心该数字为正的情况。

此时,结果是向量列表,每个向量都有两个值:产品名称和库存数量。 但是,所需的输出是哈希表。 要将以这种方式格式化的值添加到哈希表,请使用into函数。 该函数的第一个参数是您向其添加值的初始哈希表,在本例中为空表。

要在REPL网站上关注 ,请运行(filter #(= (nth % 1) 1) {:a 1 :b 0 :c 1 :d 2})以查看列表。 然后,运行(into {} (filter #(= (nth % 1) 1) {:a 1 :b 0 :c 1 :d 2}))以查看哈希表中的列表。

"getAvailable" {"data" (into {} (filter #(> (nth % 1) 0) dbase))}

其他三个操作将修改数据库。 但是,我希望他们返回新数据库。 为此,请使用do函数。 此函数获取多个表达式,对其求值,然后返回最后一个表达式。 这允许具有副作用的表达式,例如为dbase符号赋予新的含义。

处理校正很容易。 由于校正后的值将替换现有的值,因此可以使用into函数。 它的作用与您期望的一样,在键相同时替换值。

"processCorrection" (do
          (def dbase (into dbase data))
          {"data" dbase}
      )

处理购买和重新订购更加困难,因为它取决于dbase旧值和data新值的值。 幸运的是,Clojure为您提供了一个称为merge-with的函数,该函数接收一个函数和两个哈希映射。 如果一个键仅出现在一个哈希中,则使用该值。 如果一个键出现在两个地图中,则它将运行该函数并使用该值。

要继续使用REPL网站 ,请运行(merge-with #(- %1 %2) {:a 1 :b 2 :c 3} {:b 3 :c 2 :d 4})

"processPurchase" (do
          (def dbase (merge-with #(- %1 %2) dbase data))
          {"data" dbase}
      )
      "processReorder" (do
          (def dbase (merge-with #(+ %1 %2) dbase data))
          {"data" dbase}
      )

在所有的值和表达式对之后,可以放置一个默认值。 在这种情况下,这是一条错误消息。

{"error" "Unknown action"}
    )
  )
)

结论

在本教程中,您学习了如何在模拟数据库Clojure中编写单个动作。 如果您正在编写单页应用程序,那可能就足够了。 但是,要在OpenWhisk上的Clojure中编写整个应用程序,需要执行其他操作,这些操作会将作为操作正常输出的JSON转换为HTML,并将带有新信息的HTTP POST请求转换为JSON。 这是本系列下一个教程的主题。


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值