zoukankan      html  css  js  c++  java
  • R语言高级编程系列之面向对象的类型系统--S3对象

    导论

    R语言的类型系统相对于一般语言而言要复杂很多,一般来说,官方制定的类型系统有四种:基础类型、S3类型、S4类型和RC类型。在本文中主要给大家介绍一下R3类型。

    为什么需要S3类型

    在正式介绍S3类型之前,有个问题本人认为最需要想清楚,那就是为什么需要有S3类型。我相信对于许多有面向对象编程经验而言,应该多多少少能感到R3对象的设计有些“反直觉”,很难理解。原因在于,S3类型于大多数面向对象的语言(C#、JAVA、C++等)都不一样。

    存在即合理

    R语言当中有不少和其他常用语言不一致的设定,最常被人吐槽的就是赋值符号不是等号而是<-。但是我觉得比起吐槽更重要的是,要去理解为什么设计这个语言的人要这样设计。对于S3系统的设计而言,一个很重要的目的就是,设计者希望打造一种“可拓展的函数”。

    对初学者友好的编程方式

    这里指的“初学者”并不代表其水平低,相反,其中不乏许多统计、金融等领域的大牛,只是术业有专攻,他们在计算机领域可以算是初学者。而R语言的很大的一批用户是这一群人。那么什么样的编程方式更容易让初学者理解呢?自然是面向过程的编程方式。

    对于初学者而言:

    result <- mean(v1)
    

    要比

    result = v1.mean()
    

    更加容易理解,虽然对于面向对象的开发者而言后者可读性更高,原因在于,在面向过程的编程语言(如C语言)里,很多时候写起来其实是这样的……

    result = vector_mean(v1)
    

    甚至是这样的……

    result = vector_double_mean(v1)
    

    但是不管怎么样吧,面向过程依旧是对初学者而言比较容易理解的方式。因此,R语言的常用功能往往是以一个个函数的形式提供给用户,如plot,summary,mean等等。

    “可拓展”的函数

    一般的实现思路

    试想一下这种情况,如果说要编写一个concat的函数,对有序容器进行连接。我们希望对于这个的函数而言,不仅可以连接数组,还可以连接列表,那我们可以写出这样的代码:

    array_concat <- function(x,y) {
      array(c(x, y))
    }
    
    list_concat <- function(x,y) {
      list(c(unlist(x), unlist(y)))
    }
    
    concat <- function(x, y) {
      if (is.array(x) && is.array(y)){
        return(array_concat(x, y))
      } else if (is.list(x) && is.list(y)) {
        return(list_concat(x, y))
      } else {
        stop("not supported type")
      }
    }
    

    在concat函数中,利用条件语句对输入类型进行判断,然后调用相应的函数。这个方法在这里的可行的,问题在于,假如用户自己添加了一种新类型呢?那就玩不转了,只能对concat函数进行修改。设想一下,全世界那么对R语言开发者,假如每个人在添加新的类型时,对于其所需要的内置函数(如summary,mean等)都需要进行修改,那样容易造成混乱,可行度非常低。假如不能进行修改的话,那就会出现一堆诸如array_concat, list_concat之类的函数,对于初学者而言显然不够友好。

    泛型函数(Generic Function)

    现在就轮到泛型函数登场了。首先要指出的是,这里的泛型跟C#里的泛型完全不是一个概念,请不要混淆。

    创建的方法其实非常简单,需要用到UseMethod函数。

    concat.array <- function(x, y) {
      if (is.array(y)) {
        return(array(c(x, y)))
      } else {
        stop("not supported type")
      }
    }
    
    concat.list <- function(x, y) {
      if (is.list(y)) {
        return(list(c(unlist(x), unlist(y))))
      } else {
        stop("not supported type")
      }
    }
    
    concat.default <- function(x, y) {
      stop("not supported type")
    }
    
    concat <- function(x, y) {
      UseMethod("concat")
    }
    

    此时concat函数的功能与前文中concat的功能是一样的。

    从代码中可见,函数的可拓展性大大提升了,用户可以在不修改内置函数的情况下使得内置函数支持新类型。

    创建S3对象

    S3对象神奇的地方在于,它的类是没有显示声明的,也就是说没有“类作用域”这么一个玩意。更坑的是,并没有一个简单通用的办法检查一个对象是不是S3对象(我当时在书上看到这里的时候简直想摔书)。但是不管怎么样,S3对象依旧是R语言里面最常见的对象,所以还是有它的价值的。

    创建S3对象的语法

    有两种创建方式,见代码:

    myClass <- structure(list(), class = "myClass")
    
    myClass <- list()
    class(myClass) <- "myClass"
    

    两种创建方式并没有什么不同。

    编写构造函数

    如果有个构造函数的话,代码看起来会清晰很多,使用起来也更加方便。
    下面的代码演示了如何利用构造函数来创建复数类。

    Complex <- function(real, imaginary) {
      structure(list(real,imaginary), class = "Complex")
    }
    print.Complex <- function(x) {
      print(paste(x[1],"+",x[2],"i",sep = ""))
    }
    c1 = Complex(10,20)
    

    方法分派

    如果前文的内容读者能够完全理解的话,这快内容的理解其实是顺理成章的。S3对象可以多重继承,即一个对象可以继承多个类。在使用UseMethod调用时,会依次从对象的各个类中寻找相应的函数,如果都没有找到,则会调用default方法。

    f <- function(x){
      UseMethod("f")
    }
    
    f.a <- function(x) {
      return("Class a")
    }
    
    f.default <- function(x) {
      return("Unknown class")
    }
    
    x <- structure(list(), class = "a")
    f(x)
    
    y <- structure(list(), class = c("b", "a"))
    f(y)
    
    z <- structure(list(), class = "c")
    f(z)
    

    进一步的探讨

    关于S3对象的本质,我个人认为其实是每个对象都遵循着一个接口约束,然后由一个方法来调用这些遵守接口约束的对象的方法。其实这种编程方式在强类型的编程语言里也能很好地实现,以下面的代码为例,由于参数遵守(ICollection<T>)接口,因此对于任何符合该接口的对象都有CopyTo方法和Count字段,都可以作为该CopyToArray方法的参数。

    public static T[] CopyToArray<T>(ICollection<T> collection)
    {
        var arr = new T[collection.Count];
        collection.CopyTo(arr, 0);
        return arr;
    }
    

    转载声明

    文章为本人原创,转载请注明作者名称,谢谢!

    参考文献

    1.Hadley, Wickham. 高级R语言编程指南(Advanced R)[M]. 北京:机械工业出版社, 2016. 66-70

  • 相关阅读:
    【ccf 2017/12/4】行车路线(dijkstra变形)
    【ccf2017-12-2】游戏(模拟)
    解决让浏览器兼容ES6特性
    富文本编辑器ckeditor的使用
    JavaScript中,让一个div在固定的父div中任意拖动
    父组件如何向子组件方法(对话框的封装)
    Vue2.0 Transition常见用法全解惑
    JavaScript事件冒泡简介及应用
    为什么axios请求接口会发起两次请求
    修改input type=file 标签默认样式的简单方法
  • 原文地址:https://www.cnblogs.com/HeYanjie/p/6292536.html
Copyright © 2011-2022 走看看