探索Clojure将协议用作扩展机制的方法

“没有继承性的扩展,第 1 部分” 主要讨论了 Goovy、">Scala 和 Clojure 中为现有类添加新方法的机制,这也是 Java 下一代语言实现无继承扩展的方法之一。本文将探讨 Clojure 的协议如何以创新的方法拓展 Java 扩展功能,为表达式问题提供出色的解决方案。

尽管这期文章主要关注可扩展性,但也会略为涉及一些允许 Clojure 和 Java 代码无缝互操作的 Clojure 特性。这两种语言有着根本性的差别(Java 是命令式、面向对象的;而 Clojure 是函数式的),但 Clojure 实现了一些便捷的特性,使 Clojure 能够在确保最小摩擦的前提下处理 Java 结构。

Clojure 协议回顾

协议是 Clojure 生态系统的重要组成部分。上一期文章 展示了如何使用协议向现有类添加方法。协议也能帮助 Clojure 模拟面向对象的语言的为人熟知的许多特性。例如,Clojure 可模拟面向对象的类 — 数据与方法的组合,方法是通过协议将记录 与函数 绑定在一起的。为了理解协议与记录之间的交互,首先必须介绍映射,这是作为 Clojure 中记录基础的核心数据结构。

映射与记录

在 Clojure 中,映射就是一组名称-值对的集合(其他语言中常见的概念)。例如,清单 1 中的 “读取-求值-打印” 循环 (REPL) 的第一步就是创建一个包含有关 Clojure 编程语言信息的映射:

清单 1. 与 Clojure 映射交互

user=> (def language {:name "Clojure" :designer "Hickey" })#'user/languageuser=> (get language :name)"Clojure"user=> (:name language)"Clojure"user=> (:designer language)"Hickey"

Clojure 广泛使用映射,因此其中包含特殊的语法糖,可简化与映射的交互。为检索与某个键有关的值,您可以使用熟悉的 (get ) 函数。但 Clojure 会尽可能地简化此类常用操作。

在 Java 环境中,语言的源代码并非原生数据结构,必须对它进行分析和转换。在 Clojure(和其他 Lisp 变体)中,源代码表示属于 原生数据结构,比如列表,列表有助于解释语言中的奇怪语法。在 Lisp 解释器将列表作为源代码读取时,它会尝试着将列表的第一个元素解释为某些可调用 的元素,比如函数。因此在 清单 1 中,(:name language) 表达式将返回与 (get language :name) 表达式相同的结果。Clojure 之所以提供这种语法糖,是因为从映射中检索项目属于常用操作。

此外,在 Clojure 中,某些结构可放在函数调用插槽中,这扩展了可调用性(像调用函数一样调用这些结构的能力)。Java 程序只可以调用方法和内置语言语句。清单 1 展示了映射键(如 (:name language))在 Clojure 中可作为函数加以调用。映射本身也是可调用的;如果您认为替代语法 (language :name) 更容易阅读,也可以使用这种替代语法。Clojure 丰富的可调用图表使得这种语言更易于使用,从而减少了重复的语法(例如 Java 程序中常见的 get 和 set )。

然而,映射并不能完全模拟 JVM 类。Clojure 提供了其他方法来帮助您建模包括数据和行为在内的问题,更加无缝地集成底层 JVM。您可以创建对应于类似的底层 JVM 类且完整性各有不同的多种结构,包括类型 和记录 在内。您可以使用 (deftype ) 创建一个类型,通常用该类型来建模机械 结构。例如,如果您需要一个数据类型来持有 XML,那么很有可能会使用 (deftype MyXMLStructure) 表示 XML 内嵌的数据提取结构。在 Clojure 中,习惯于使用记录获得数据,信息记录 是应用程序的核心。为支持这种用法,Clojure 将在包含可调用性等特性的底层记录定义中自动包含大量接口。清单 2 中的 REPL 交互演示了记录的底层类和超类:

清单 2. 记录的底层类和超类

user=> (defrecord Person [name age postal])user.Personuser=> (def bob (Person."Bob" 42 60601))#'user/bobuser=> (:name bob)"Bob"user=> (class bob)user.Personuser=> (supers (class bob))#{java.io.Serializable clojure.lang.Counted java.lang.Object clojure.lang.IKeywordLookup clojure.lang.IPersistentMap clojure.lang.Associative clojure.lang.IMeta clojure.lang.IPersistentCollection java.util.Map clojure.lang.IRecord clojure.lang.IObj java.lang.Iterable clojure.lang.Seqable clojure.lang.ILookup}

在 清单 2 中,我创建了一个名为 Person 的新记录,它包含用于 name、age 和 postal 代码的字段。我可以使用 Clojure 针对构造函数调用的语法糖来构造此类新记录(使用类名称加一个句点作为函数调用)。返回值为带有名称空间的实例。(默认情况下,所有 REPL 交互都发生在 user 名称空间内。)可调用性规则仍然存在,因此我可以使用 清单 1 展示的语法糖来访问记录的成员。

调用 (class ) 函数时,它将返回 Clojure 创建的名称空间和类名(可与 Java 代码交互)。我还可以使用 (supers ) 来访问 Person 的超 class。在 清单 2 的最后四行中,Clojure 实现了几个接口,包括 IPersistentMap 等可伸缩性接口,该接口允许使用 Clojure 的原生映射语法来处理类和对象。自动包含的一组接口是记录与类型之间的一个重要差别,类型不包含任何自动接口实现。

使用记录实现协议

Clojure 协议就是指定函数及其签名的指定集合。清单 3 中的定义将创建一个协议对象和一组多态协议函数:

清单 3. Clojure 协议

(defprotocol AProtocol "A doc string for AProtocol abstraction" (bar [this a] "optional doc string for aar function") (baz [this a] [this a b] "optional doc string for multiple-arity baz function"))

清单 3 中的函数对一个参数的类型进行分派,这使得它在该类型上具有多态性(此类型通常被命名为 this,以模拟 Java 上下文占位符)。因此,所有协议函数至少必须有一个参数。通常,协议使用驼峰式大小写混合格式命名;因为它们将在 JVM 级别上具体化 Java 接口,因此与 Java 命名规范保持一致能够简化互操作性。

记录可以实现协议,就像是在 Java 语言中实现接口一样。记录必须(将在运行时检查)实现与协议签名匹配的函数。在清单 4 中,我创建了一个实现 AProtocol 的记录:

清单 4. 实现协议

(defrecord Foo [x y] AProtocol (bar [this a] (min a x y)) (baz [this a] (max a x y)) (baz [this a b] (max a b x y)));exercising the record(def f (Foo.1 200))(println (bar f 4))(println (baz f 12))(println (baz f 10 2000))

在 清单 4 中,我创建了一个名为 Foo 的记录,它带有两个字段:x 和 y。为了实现协议,我必须包含匹配其签名的函数。实现协议后,我可以为对象的实例调用函数,就像调用普通函数一样。在函数定义中,我可以访问该记录的两个内部字段(x 和 y)以及函数参数。

时间: 2024-10-21 21:39:19

探索Clojure将协议用作扩展机制的方法的相关文章

Java 下一代: 没有继承性的扩展(二)探索 Clojure 协议

"没有继承性的扩展,第 1 部分" 主要讨论了 Goovy.Scala 和 Clojure 中为现有类添加新方法的机制,这也是 Java 下一代语言实现无继承扩展的方法之一.本文将探讨 Clojure 的协议如何以创新的方法拓展 Java 扩展功能,为表达式问题提供出色的解决方案. 尽管这期文章主要关注可扩展性,但也会略为涉及一些允许 Clojure 和 Java 代码无缝互操作的 Clojure 特性.这两种语言有着根本性的差别(Java 是命令式.面向对象的:而 Clojure 是

Remoting基本原理及扩展机制(中)

在上一篇文章我们已经介绍到通过在配置文件中指定自定义的ChannelSinkProvider,我们可以在Pipeline中加入自己的ChannelSink,此时我们就可以加入自己的信息处理模块,但是这里我们所能操作的对象是已经经过格式化的消息(即数据流),我们看不到原始的消息对象,这也势必影响了我们所能实现的扩展功能.而在上文的图1中,我们看到除了ChannelSink可以扩展之外,我们还可以加入自定义的MessageSink,而它是位于格式器之前的,也就是说在MessageSink中我们可以直

[精通Objective-C]类,接口,协议与扩展

[精通Objective-C]类,接口,协议与扩展 参考书籍:<精通Objective-C>[美] Keith Lee 目录 精通Objective-C类接口协议与扩展 目录 类 类的接口 类的实现 实例变量 属性 方法 协议 分类 扩展 类 创建一个类名为Atom,继承于NSObject的类.Atom类由两个文件组成,Atom.h和Atom.m,分别为类的接口和实现. 类的接口 Atom类的接口是在头文件Atom.h中设置的,用于声明类的属性和方法. #import <Foundati

Swift教程_零基础学习Swift完整实例(三)_swift基础(对象和类、枚举和结构、协议和扩展、泛型)

4.对象和类(Objects and Classes) 1.同Java一致,使用class和类名来创建一个类. 2.使用init创建一个构造方法,使用deinit创建一个析构方法,通过构造方法来初始化类实例.创建类实例同java一致,在类名后面加上()(实际是调用无参数的构造方法init(),构造方法也可以带参数).使用.来访问实例的属性和方法. [objc] view plain copy class NamedShape {       var numberOfSides: Int = 0/

《Swift开发实战》——第2章,第2.7节协议和扩展

2.7 协议和扩展 在Swift语言中,使用关键字protocol来声明一个协议.例如,如下所示的演示代码. protocol ExampleProtocol { var simpleDescription: String { get } mutating func adjust() } 在Swift语言中,类.枚举和结构体都可以实现协议.例如,如下所示的演示代码. class SimpleClass: ExampleProtocol { var simpleDescription: Strin

dubbo源码分析系列(1)扩展机制的实现

1 系列目录 dubbo源码分析系列(1)扩展机制的实现 dubbo源码分析系列(2)服务的发布 dubbo源码分析系列(3)服务的引用 dubbo源码分析系列(4)dubbo通信设计 2 SPI扩展机制 站在一个框架作者的角度来说,定义一个接口,自己默认给出几个接口的实现类,同时允许框架的使用者也能够自定义接口的实现.现在一个简单的问题就是:如何优雅的根据一个接口来获取该接口的所有实现类呢? 这就需要引出java的SPI机制了 2.1 SPI介绍与demo 这些内容就不再多说了,网上搜一下,一

设计-java 字段扩展机制 类扩展机制

问题描述 java 字段扩展机制 类扩展机制 当要求数据库中的一个表里需要进行字段扩展时,如何能让po层中的各个实体也能一起达到扩展的效果呢,数据库里面的表又该如何设计 解决方案 本文转载自:http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/ 简介:? 本文通过分析 Java 动态代理的机制和特点,解读动态代理类的源代码,并且模拟推演了动态代理类的可能实现,向读者阐述了一个完整的 Java 动态代理运作过程,希望能帮助读者加深对 Java

《Clojure程序设计》——第1章,第1.3节探索Clojure的程序库

1.3 探索Clojure的程序库 Clojure程序设计 Clojure代码通常都被打包在程序库中.每个Clojure库都属于某个命名空间,这与Java的包非常类似.你可以通过require来加载一个Clojure库. (require quoted-namespace-symbol) 当你使用require加载了一个名为clojure.java.io的库时,Clojure会在CLASSPATH中查找名为clojure/java/io.clj的文件.试试看. user=> (require '

腾讯CMEM的PHP扩展编译安装方法_php技巧

本文实例讲述了腾讯CMEM的PHP扩展编译安装方法.分享给大家供大家参考.具体如下: CMEM是什么? CMEM全称为Cloud Memory,是腾讯提供的高性能内存级持久化存储服务,适用于数据量小.访问量高.key-value存储的场景. CMEM基于一个存储键/值对的hashmap,数据使用内存存储,并保证数据的持久性. CMEM PHP Extension是什么? CMEM基于标准的Memcached协议以及接口,只是将数据获取接口增加返回值设定. Memcached的Get协议没有设计返