【问题标题】:Functional programming in OOP language面向对象语言的函数式编程
【发布时间】:2018-08-02 17:36:08
【问题描述】:

不可变对象可以,但是,非最终本地引用可以吗?

也就是说,下一个代码 sn-p 可以表示为函数式编写的?

Employee e = new Employee("Lex", 24, 250);
e = Employee.setName(e, "Vasili");
e = Employee.setAge(e, 12);
e = Employee.setSalary(e, 2500);
Employee.log(e);

附:这里所有的Employee 方法都是静态的,setter 是返回新实例的工厂方法。

【问题讨论】:

  • e0, e1, e2 都是未定义的。在这里使用setXsetY 作为方法名称会产生误导。也许将它们命名为 createWithXcreateWithY
  • OK 是什么意思?
  • 不可变样式可能是new Employee("Lex", 24, 250).withName("Vasili").withAge(12).withSalary(2500)with 方法每个都返回一个新的Employee 对象。
  • @LexUshakov 在纯函数式语言中,没有“变量”。有些名称指的是值,但您不能更改值。在命令式编程术语中,所有变量都是常量,即根本不是 variable / 变化 / 可变的。这就是为什么,为了在 Java 中模拟相同的行为,使参数和局部变量final,因此它们不能被重新分配/更新/修改。
  • @LexUshakov,在函数式编程中没有对象或引用,只有值,值不会改变。您不会在程序中更改 4 的值,因此如果您说 x=4 那么您也不会更改 x,因为这与更改 4 的值相同。第一行说e = 员工“Lex”等。然后您将 e 更改为员工“Vasili”。那么是 e Lex 还是 Vasili?不可能两者兼而有之。在 FP 中没有“控制流”的概念,其中变量在不同时间具有不同的值。

标签: java oop functional-programming


【解决方案1】:

由于这个问题被标记为“java”,我假设问题是关于 Java 中的 FP 实践(即不变性)。

今天在 Java 中的良好做法是使用构建器:

Employee e = Employee.builder()
                     .surname("Lex")
                     .age(24)
                     .name("Vasili")
                     .salary(2500)
                     .build();

或静态构造函数:

Employee e = Employee.of("Vasili", "Lex", 24, 2500);

在这两种情况下,都应将“经典”构造函数声明为私有,以确保对象无法被实例化并以不一致的状态提供给客户端。

然后对象变异器应该返回新对象:

Employee.of("Vasili", "Lex", 24, 2500)  // creates an object
        .updateName("Sergey")           // returns 1st modified copy
        .updateSalary(3500);            // returns 2nd modified copy based on 1st copy

按照这些做法,对非最终本地参考的需求通常会消失。
一个非常流行的例子是Date and Time API

现在,关于使用可变局部变量。没关系,但是可以使用方法链接来缩短代码并使其更具表现力。尝试按原样链接静态方法看起来不太优雅:

Employee e = setSalary(setAge(setName(new Employee("Lex", 24, 250), "Vasili"), 12), 2500);

作为模拟 monad 的一种尝试,可以将对象包装在一些类似 monad 的容器中,该容器定义了一个接受函数的绑定方法,该函数将接受存储在 monad 中的对象并返回一些结果,该结果将再次被包装在一个单子里。一个简单的示例如下所示:

static class Employee {
    public String name;
    public int age;
    public long salary;
}

static class Monad<T> {
    private final T value;

    private Monad(T value) { this.value = value; }

    public static <T> Monad<T> of(T value) {
        return new Monad<>(value);
    }

    public T getValue() { return value; }

    public Monad<T> bind(UnaryOperator<T> operator){
        return of(operator.apply(value));
    }
}

public static void main(String[] args) {
    Employee value = Monad.of(new Employee())
                          .bind(e -> {e.name = "Lex"; return e; })
                          .bind(e -> {e.age = 24; return e; })
                          .bind(e -> {e.salary = 2500; return e; })
                          .getValue();
}

但是这可以通过核心 Java 来完成,因为版本 8 - Stream API 可以做到这一点以及更多:

Stream.of(new Employee())
      .map(e -> {e.name = "Lex"; return e; })
      .map(e -> {e.age = 24; return e; })
      .map(e -> {e.salary = 2500; return e; })
      .findFirst()
      .get();

【讨论】:

  • 感谢您的回复,@jihor!在您的示例中存储了可变对象 - 但可以将其替换为具有静态方法引用的不可变对象?
  • 你能帮忙理解一下,为什么下一个代码不起作用? pastebin.com/DMN18EQv
  • @LexUshakov map() T 流上的方法需要 Function&lt;? super T, ?&gt;。工作行将显示为.map((e) -&gt; Employee.setName(e, "Vasilii"))
【解决方案2】:

Haskell一无所知,但我相信你正在努力实现这样的目标:

Employee e = new Employee("Lex")
    .setAge(25)
    .setSalary(2500)
    .setGender(Gender.Male);

这只是chaining函数的结果

public Employee setParam(param){
   this.param = param;
   return this;
}

但方法不是静态的,它们属于实例。

也不需要将实例作为参数传递。


还有:

  • this 不是必需的关键字;在我上面的示例中,两个参数具有相同的名称,因此如果没有this,代码基本上会将参数的值重新分配给自身。如果参数名称不同,则不需要 this。然而返回 this 是必要的,因为它表示对当前实例的引用。

例如:

public Employee setParam(String param) {
    parameter = param; // parameter is a field in class Employee
    return this; // this "this", is still necessary
}
  • final变量

可能会限制您尝试通过自己的风格实现的目标

final Employee e = Employee.setName(e, "Name"); // invalid, e is unkown

// ----------------

final Employee e;
e = Employee.setName(e, "Name"); // invalid, e may not be initialized

// ----------------

final Employee e;
e = Employee.setName("Name"); // valid

// ----------------

final Employee e = null;
e = Employee.setName(e, "Name"); // invalid. e was already initalized to null

// ----------------

final Employee e = Employee.setName("Name"); // valid
e = Employee.setName("Name2"); // invalid, final variable already initialized

【讨论】:

  • 感谢您的回复,但我已经知道了。所以,换句话说,没有this关键字就不可能做出流畅的API,对吧?
  • @LexUshakov this 不是必需的,让我更新我的答案
  • 当使用非静态方法时,简单来说,它们通过隐藏的this链接绑定到某个结构。恕我直言,这是OOP langs 的主要功能。
【解决方案3】:

这类似于每个函数返回新项目的条款,在许多 FP 语言(例如 Haskell)中,您甚至无法更新该值,只需创建一个新项目:

let myBook = beginBook "Haskell"
let myBook' = addChapter (Chapter "Intro" ["Hello","World"]) myBook

所以这里beginBook 函数将返回一本书,然后addChapter 将返回另一本书并修改了一些字段。

【讨论】:

  • 但是你可以在 Haskell 中 re-let 变量 myBook 吗?这就是问题所在,即是否可以更新局部变量。
  • @Andreas,不,在haskell 这绝对是不可能的。这里myBookmyBook' 是函数定义,而不是java 类的数据结构。
【解决方案4】:

这样的东西对你有用吗(忽略 OptionalifPresent 的不当使用,我们可以用更有意义的东西代替它)?

    Optional.of( new Employee("Lex", 24, 250) )
    .map( e -> Employee.setName(e, "Vasili") )
    .map( e -> Employee.setAge(e, 12) )
    .map( e -> Employee.setSalary(e, 2500) )
    .ifPresent( e -> Employee.log(e) );

【讨论】:

    猜你喜欢
    • 2017-12-12
    • 1970-01-01
    • 2013-05-19
    • 1970-01-01
    • 2023-03-21
    • 2018-07-05
    • 2010-11-04
    • 2015-09-13
    • 2011-11-16
    相关资源
    最近更新 更多