R中的泛型函数是什么?

泛型函数(Generic function)。"一次编写,多种类型通用,还不会搞混类型" 的函数,就像超市的购物袋,它本身不规定装什么(可以装水果、零食、日用品),但你装进去苹果,拿出来还是苹果;装进去牛奶,拿出来还是牛奶。这个购物袋就类似泛型的作用 —— 它不限制具体装什么,但保证你取出来的和放进去的是同一种东西。使用java中的泛型来举例解释。
例如:在java中创建一个打印集合中的元素的方法,如果集合在创建的时候就指定了List中的元素类型,如果我们在不使用泛型的情况下,可以对每一种类型的集合创建一个方法(方法的重载)这种说法其实也不完全准确,为了方便讲解。

package src.Generics_;

import org.w3c.dom.ls.LSInput;

import java.util.Arrays;
import java.util.List;

public class GenericTest2 {
    public static void main(String[] args) {
        List<String> stringList = Arrays.asList("a", "b", "c");
        List<Integer> integerList = Arrays.asList(1, 2, 3);
        // Sting集合打印元素
        printStringList(stringList);
        // Integer集合打印元素
        printIntegerList(integerList);

    }

    // 这里创建一个打印String List中元素的函数
    public static void printStringList(List<String> lst) {
        for (String l1 : lst) {
            System.out.println(l1);
        }
    }

    // 这里再创建一个打印Integer List中的元素的函数
    public static void printIntegerList(List<Integer> lst) {
        for (Integer l2 : lst) {
            System.out.println(l2);

        }
    }
}

在上面我们在主函数中创建了两个元素类似的集合。我们在这里使用了一个比较笨的方式,对集合元素打印的函数进行重写来满足集合中不同元素类型的打印。当然,如何我们使用泛型来实现呢?

package src.Generics_;

import java.util.Arrays;
import java.util.List;

public class GenericTest3 {
    public static void main(String[] args) {
        List<String> lst1 = Arrays.asList("A", "B", "C");
        List<Integer> lst2 = Arrays.asList(1, 2, 3, 4);
        // 这里我们使用泛型函数
        printList(lst1); // 打印String 集合
        printList(lst2); // 打印Integer 集合
        // 如果我们再创建一个新类型的集合
        List<Double> lst3 = Arrays.asList(1.1, 1.2, 1.2);
        printList(lst3);
    }

    // 这里咱们创建一个泛型函数
    public static <T> void printList(List<T> lst) {
        for (T item : lst) {
            System.out.println(item);
        }
    }
}

通过这个简单的java中方法泛型的小例子,可以对泛型有一点了解。那么R中的泛型如何实现呢?R中在面向对象的时候有多种对象类型,不同的对象类型的泛型方法是否也不同呢?
是的,R 中的泛型方法以 S3 泛型 最为基础和常用,覆盖了大部分内置功能;S4 泛型用于更严格的面向对象场景;第三方包(如 tidyverse)会根据需求定义领域特定的泛型。泛型的核心作用是:对不同类型的对象,用统一的函数名实现不同的具体操作。


下面我们先介绍一下R中的S3泛型。

# 这里我们创建一个泛型函数
genericFun <- function(x,...){UseMethod("genericFun")}

# 指定泛型函数作用的类型
genericFun.numeric <- function(x){
  print("this is a number")
}
genericFun(10)
# 当我们运行这个泛型函数的时候,打印:this is a number
# 如果我传入的参数不是整型呢?
genericFun("S")
# 发生了报错
# Error in UseMethod("genericFun") : 
#   no applicable method for 'genericFun' applied to an object of class "character"

S3 泛型的方法分派只看第一个参数(通常是 x)的类,其他参数的类型不影响方法匹配。S3 对方法的参数要求很灵活:方法可以只定义它需要的参数,无需与泛型函数的参数完全匹配(只要能处理传入的参数即可)。 R中的S3泛型函数好灵活啊。


S4泛型函数相较S3泛型函数更加的严格,那如何简单使用呢?
S4 泛型通常与 S4 类配合使用,需先通过 setClass() 定义类(包含 slots 和继承关系)
● 方法(Method):与特定类绑定的函数,实现该类的具体逻辑,必须通过泛型函数调用。
● 类(Class):S4 类需显式定义(包含 slots 结构,类似 “属性”),具有严格的继承关系。
● 分派机制:S4 支持多参数分派(根据多个参数的类匹配方法),而 S3 仅基于第一个参数。
这里我们举个例子吧。

# S4泛型函数是依赖S4类的,这里我们先定义一个S4类
# 定义一个"S4Person"类,包含name(字符型)和age(数值型)两个slots
setClass(
  Class = "S4Person",  # 类名
  slots = list(
    name = "character",  # slot名称及类型约束
    age = "numeric"
  )
)

# 定义继承类"S4Student"(继承自"S4Person")
setClass(
  Class = "S4Student",
  slots = list(school = "character"),  # 新增school slot
  contains = "S4Person"  # 继承自"S4Person"
)
# setGeneric() 定义泛型函数,需指定函数名和参数,核心是用 standardGeneric() 声明泛型接口
# 定义泛型函数"greet",功能是向对象打招呼
setGeneric(
  name = "greet",  # 泛型函数名
  def = function(x) {  # 定义参数(至少包含要分派的对象)
    standardGeneric("greet")  # 声明为S4泛型
  }
)

# 为"S4Person"类注册greet方法
setMethod(
  f = "greet",  # 绑定到的泛型函数名
  signature = "S4Person",  # 方法对应的类(单类)
  definition = function(x) {  # 方法逻辑
    paste0("Hello, I'm ", x@name, ", ", x@age, " years old.")
  }
)

# 为"S4Student"类注册greet方法(继承类可重写方法)
setMethod(
  f = "greet",
  signature = "S4Student",
  definition = function(x) {
    paste0("Hello, I'm ", x@name, " from ", x@school, ".")
  }
)

# 多参数分派示例:为x="S4Person"、y="S4Student"注册greetTwo方法
setMethod(
  f = "greetTwo",
  signature = signature(x = "S4Person", y = "S4Student"),  # 多个参数的类
  definition = function(x, y) {
    paste0(x@name, " says hi to ", y@name, " (student at ", y@school, ").")
  }
)

#### 测试greet泛型函数
# 创建S4Person对象
p <- new("S4Person", name = "Alice", age = 30)
greet(p)  # 调用S4Person的greet方法
#> [1] "Hello, I'm Alice, 30 years old."

# 创建S4Student对象
s <- new("S4Student", name = "Bob", age = 20, school = "MIT")
greet(s)  # 调用S4Student的greet方法
#> [1] "Hello, I'm Bob from MIT."

这里简单总结一下两者的区别。


image.png
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 要了解一个函数的运行过程,一是查看帮助文档,二是查看其代码 普通函数 对于普通函数,查看其源代码是简单的事情,如d...
    昨夜朱楼梦阅读 291评论 0 0
  • R语言有四大类型系统:基础类型、S3类型、S4类型和RC类型。 R虽然被认为是一种函数式语言,但是同样支持面向对象...
    LeoinUSA阅读 9,879评论 1 19
  • 接R-面向对象编程 下面演示如何基于TimeSeries类实现一个WeightHistory类以记录个人的历史体重...
    王诗翔阅读 968评论 0 1
  • 来自R大神著作《Advanced R 》练习题,来一起检验一下R语言知识吧!🤓本文参考资料:《Advanced R...
    caokai001阅读 961评论 0 1
  • 本文转载自http://blog.csdn.net/youshaoduo/article/details/5486...
    desunire阅读 1,960评论 0 0