面向对象编程 —— java实现函数求导

先是声明一点,本文主要介绍的凡面向对象(OO)的思,顺便称下函数式编程,而非是让而哪些规范地、科学地用java求来函数在少数之导数。

原文地址在自身的博客,
转载注明来源

 

网上同搜查事件循环, 很多章标题的眼前会加上 JavaScript,
但是自觉着事件循环机制以及 JavaScript 没什么关系, JavaScript
只是平等派别解释型语言, 方便开以及清楚的, 由V8 JIT将 JavaScript
编译成机器语言来调用底层, 至于浏览器怎么实施 JavaScript 代码, JavaScript
管不着也不关心. 因此, “JavaScript事件循环机制”这种说法是不客观的.
事件循环机制是出于运行时环境实现的, 具体来说有浏览器、Node等.
这篇稿子就先行来说说浏览器被落实的波循环机制.

一、引子

 

def d(f) :
    def calc(x) :
        dx = 0.000001  # 表示无穷小的Δx
        return (f(x+dx) - f(x)) / dx  # 计算斜率。注意,此处引用了外层作用域的变量 f
    return calc  # 此处用函数作为返回值(也就是函数 f 的导数)
# 计算二次函数 f(x) = x2 + x + 1的导数
f = lambda x : x**2 + x + 1  # 先把二次函数用代码表达出来
f1 = d(f)# 这个f1 就是 f 的一阶导数啦。注意,导数依然是个函数
# 计算x=3的斜率
f1(3)
# 二阶导数
f2 = d(f1)

率先,直接上同样段python代码,请大家先分析下者代码是为此啊点子求导的。请不要为及时段代码吓到,你随便需纠结它的语法,只要知道她的求导思路。

上述代码引用自《呢底我推荐 Python[4]:作为函数式编程语言的
Python》,这篇博客是敦促自己写首文章的要原因。

博主说“如果无用 FP,改用 OOP,上述要求该怎么落实?俺觉得吧,用 OOP
来求导,这代码写起多半是以臭又臭。”

自我以信将疑,于是便用面向对象的java试了碰,最后也远非多少代码。如果因此java8或者下版本,代码更不见。

告大家想一个题材,如何用面向对象的思绪改写这个序。请先好好思考,尝试编个程序还累为生看。

设想到目是题目上的同班大多是模仿过java的,下面我用java,用面向对象的思绪一步步剖析这问题。

 

正文

率先,javascript
在浏览器端运行是单线程的,这是由浏览器决定的,这是为避免多线程执行不一任务会发生冲突的场面。也就是说我们刻画的javascript
代码只以一个线程上运行,称之为主线程(HTML5提供了web worker
API可以叫浏览器开始一个线程运行比较复杂耗时的
javascript任务,但是是线程仍给主线程的操纵)。单线程的话,如果我们举行片“sleep”的操作比如说:

var now = + new Date()
while (+new Date() <= now + 1000){
//这是一个耗时的操所
}

这就是说在即时近一秒内,线程就会见为封堵,无法继续执行下面的任务。

还来把操作比如说获取远程数据、I/O操作等,他们还很耗时,如果以一块的主意,那么进程在履行这些操作时即会以耗时而等待,就比如面那样,下面的天职为只能待,这样效率并无强。

那么浏览器是怎开的也罢?

我们找到WHATWG规范针对Event
loop的介绍:

WHATWG Event loop定义

以协调事件,用户交互,脚本,渲染,网络等,用户代理要采取事件循环。

事件循环的要机制就算是任务队列机制:

  • 一个风波循环有一个还是基本上个任务队列(task
    queues)。任务队列是task的平稳列表,task是调度Events,Parsing,Callbacks,Using
    a resource,Reacting to DOM manipulation这些任务之算法;
  • 每个任务还来源于一个特定的任务源(task
    source)(比如鼠标键盘事件)。来自同一个特定任务源且属于特定事件循环的天职要为投入到与一个职责队列中,来自不同任务源的职责可在不同的天职队列中;
  • 浏览器调用这些队列中的天职时以如此的做法:
    相同队列中的职责仍先进先出的逐一,
    不同之阵按照提前设置的队列优先级来调用.
    例如,用户代理可以发一个用于鼠标和键盘事件的天职队列(用户交互任务源),另一个用以其它任务。然后,用户代理75%概率调用键盘与鼠标事件职责队列,25%调用其他队,
    这样的话就保持界面响应而且无会见饿死其他任务队列.
    但是同队列中的天职而以先进先出的逐条。也就是说单独的职责队列中的职责连续以先进先出的次第执行,但是匪包多个任务队列中之天职优先级,具体贯彻可能会见陆续执行

当调用任务的经过中, 会产生新的天职, 浏览器就是会随地实践任务,
因此称为事件循环.

microtask queue 微任务队列

再有一些特殊任务, 它们不会见吃在task queues中,
会放在一个称呼microtask(微任务) queue受, 继续看标准:

Each event
loop
has a microtask queue. A microtask is a
task
that is originally to be queued on the microtask
queue
rather than a task
queue.

任务队列可以发差不多个, 但是微任务队列只来一个.

这就是说如何任务是位于task queue, 哪些放在microtask queue呢?
通常对浏览器与Node.js来说:

  • macrotask(宏任务): script(整体代码), setTimeout,
    setInterval, setImmediate, I/O, UI rendering等
  • microtask(微任务): process.nextTick,
    Promises(这里因浏览器实现的原生 Promise), Object.observe,
    MutationObserver

吁进一步令人瞩目macrotask中实行总体代码也是一个宏任务

事件循环处理过程

整体来说, 浏览器端事件循环的一个合(go-around或者给cycle)就是:

  • 由macrotask队列中(task queue)取一个宏任务执行, 执行了晚,
    取出具有的microtask执行.

  • 重新回合

任凭以推行macrotask还是microtask,
都出或出新的macrotask或者microtask, 就如此继续执行.

故任务队列机制解释异步操作顺序

这边产生局部大异步操作:

const interval = setInterval(() => {
  console.log('setInterval')
}, 0)

setTimeout(() => {  
  console.log('setTimeout 1')
  Promise.resolve().then(() => {
    console.log('promise 3')
  }).then(() => {
    console.log('promise 4')
  }).then(() => {
    setTimeout(() => {
      console.log('setTimeout 2')
      Promise.resolve().then(() => {
        console.log('promise 5')
      }).then(() => {
        console.log('promise 6')
      }).then(() => {
        clearInterval(interval)
      })
    }, 0)
  })
}, 0)

Promise.resolve().then(() => {
  console.log('promise 1')
}).then(() => {
  console.log('promise 2')
})

结果(Chrome 63.0.3239.84; Mac OS):

promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval // 大部分情况下2次, 少数情况下一次
setTimeout 2
promise 5
promise 6

斯顺序是怎得来的?

咱们事先唠promise 4后面就出现同等不善setInterval的景象,
画个图简单表示一下以此历程:

职责队列机制

注意

本图为了好拿各国时间段(Cycle)队列的职责还画在列中失矣,
实际上执行一个task 和 microtask 后就是会管此职责由相应队列中除去

第一, 主任务就是是执行脚本, 也即是实施上述代码, 这也是一个task.
在实行代码过程被, 遇到setTimeout、setInterval 就会见拿回调函数添加到task
queue中, 遇到 promise 就会将then回调添加到 microtask 中去.

Task执行了, 接着取有 microtask 执行, 所有microtask 执行了了, microtask
queue也就算空了, 接着再取task执行, 如果microtask queue为空, 没有任务,
则继续得到下一个task执行, 就如此循环执行. 图中箭头就象征执行之顺序.

那为什么promise 4后面大部分景象下起2次setInterval,
少数景象出现1浅啊?

我猜测即是以setInterval是起极度短间隔时间的(chrome下4ms左右),
这个时空不同机子、不同浏览器还来或无一样. 代码中之参数是0,
意味着尽可能少的日内即见面发生一个task加入到 task queue中.
浏览器在履行setInterval后及实施下一个task前,
时间间隔就可能超越这个最缺少日, 因此会生一个setInterval task.

我是如此论证的:

自己把带有promise5、promise6回调函数的setTimeout的光阴设置特别一些,
让其推迟插入task queue中:

...  
setTimeout(() => {
      console.log('setTimeout 2')
      Promise.resolve().then(() => {
        console.log('promise 5')
      }).then(() => {
        console.log('promise 6')
      }).then(() => {
        clearInterval(interval)
      })
}, 10)   //这里加上10ms 
...

结果是promise 4后面的setInterval出现了5赖, 因此我觉得promise
4后面大部分景象下起2次setInterval、少数情况出现同样不好的由来纵然是浏览器在推行setInterval回调函数后、执行setTimeout回调函数前,
时间间隔大部分情超过了这个极端短时间.

此外, 我试着又逐一增长1ms, 直到14ms——也即是增长4ms经常, promise
4后面的setInterval变成了6坏,
可以看setInterval最短间隔时间在Chrome下盖为4ms(不考虑机子性能、设置).

Node中之竟然结果

首先说明一下, 在Node中呢反映了职责队列的编制, 但是即刻不是Node实现之,
这是V8实现的, 由Node调用了V8任务队列机制的API. 至于为什么是V8实现的,
我们翻翻ECMA
262
标准对 Job 和 Job queue 的介绍就足以得知

而吃人摸不着头脑的凡, 这段代码在node v8.5.0下蛋有时会晤并发这么的结果:

promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
setTimeout 2
setInterval   // 为什么会出现setInterval???
promise 5
promise 6

按理应该是setTimeout 2 => promise 5 => promise 6,
因为出口setTimeout 2的回调函数是task, 执行了这task后应该调用microtask
输出promise 5 => promise 6啊? 很想得到! Node对V8确实有点改,
不晓得是休是立上面原因…

还呼吁大神解惑!

二、求导

 

文章开始我早已临近声明了了,本文不是来讨论数学的,求导只是自身因此来说明面向目标的一个事例。

假定您就忘记了启幕那段代码的求导思路,请回头再看看,看看用python是怎样求导的。

相信您要是听说过求导,肯定一眼就观望开头那段代码是为此导数概念求导的。

图片 1

代码中单单是将无穷小Δx粗略地到底做一个较小的值0.000001。

 

君还是读到立刻了

总一下:

习技能或者出捷径的, 那即便是朗诵标准 😉

其三、最初的想法

 

//自定义函数
public class Function {
    //函数:f(x) = 3x^3 + 2x^2 + x + 1
    public double f(double x) {
        return 3 * x * x * x + 2 * x * x + x + 1;
    }
}

//一元函数导函数
public class DerivedFunction {
    //表示无穷小的Δx
    private static final double DELTA_X = 0.000001;
    //待求导的函数
    private Function function;

    public DerivedFunction(Function function) {
        this.function = function;
    }

    /**
     * 获取function在点x处的导数
     * @param x 待求导的点
     * @return 导数
     */
    public double get(double x) {
        return (function.f(x + DELTA_X) - function.f(x)) / DELTA_X;
    }
}

public class Main {
    public static void main(String[] args) {
        //一阶导函数
        DerivedFunction derivative = new DerivedFunction(new Function());
        //打印函数在x=2处的一阶导数
        System.out.println(derivative.get(2));
    }
}

先声明一点,考虑到博客篇幅,我动用了未正规之代码注释,希望大家不用给自己误导。

本身思念只要大家好好思考了,应该至少会想到马上步吧。代码我哪怕无说明了,我只是用java改写了稿子开始的那段python代码,做了一个简单的翻译工作。再请大家着想生以上代码的问题。

恰开,我心想这题材想开的凡建一个叫也Function的近乎,类中起一个名为f的办法。但考虑到要是每次要求新的函数导数时便得改这个f方法的落实,明显不便利扩展,这违背了开闭原则。

估价有同学没有听罢之词,我虽讲下:”靶(类,模块,函数等)应针对扩大开放,但对修改封闭“。

于是乎我便从未累写下去,但为吃大家直观的感受及者想法,我写就首博客时即便实现了一下是想法。

请求大家想一下怎么重构代码以解决扩展性问题。

 

季、初步的想法

 

估价学过面向对象的同学会想到将Function类改化接口或抽象类,以后每次上加新的函数时如再写这接口或抽象类吃的f方法,这虽是面向接口编程,符合仗反转原则,下面的代码就是这般做的。

复声明一点,考虑到篇幅的题目,后面的代码我会省去与事先代码重复的注释,有免亮的地方还求看上一个想方设法遭到之代码。

//一元函数
public interface Function {
    double f(double x);
}

//自定义的函数
public class MyFunction implements Function {
    @Override
    public double f(double x) {
        return 3 * x * x * x + 2 * x * x + x + 1;
    }
}

public class DerivedFunction {
    private static final double DELTA_X = 0.000001;
    private Function function;

    public DerivedFunction(Function function) {
        this.function = function;
    }

    public double get(double x) {
        return (function.f(x + DELTA_X) - function.f(x)) / DELTA_X;
    }
}

public class Main {
    public static void main(String[] args) {
        //一阶导函数:f'(x) = 9x^2 + 4x + 1
        DerivedFunction derivative = new DerivedFunction(new MyFunction());
        System.out.println(derivative.get(2));
    }
}

自身眷恋认真看的同学也许会见发觉一个题材,我的翻译做的还非成就,开头那段python代码还得轻松地伸手来二阶导函数(导数的导数),而己之代码却十分。

实则若稍加修改上述代码的一个地方就得轻松实现求其次阶导,请复思考片刻。

 

五、后来之想法

 

当我写有方的代码时,我感觉了可矢口否认“用 OOP
来求导,这代码写起来多半是还要臭又臭”的见。但还无可知要其次阶导,我来接触不甘心。

乃我就算动笔,列了瞬间用定义求一阶导和求二阶导的架势,想了相思少独姿态的分及联络,突然想到导函数为是函数。

DerivedFunction的get方法及Function的f方法的参数与返回值一样,DerivedFunction可以兑现Function接口,于是发出了下的代码。

public interface Function {
    double f(double x);
}

public class DerivedFunction implements Function {
    private static final double DELTA_X = 0.000001;
    private Function function;

    public DerivedFunction(Function function) {
        this.function = function;
    }

    @Override
    public double f(double x) {
        return (function.f(x + DELTA_X) - function.f(x)) / DELTA_X;
    }
}

public class Main {
    public static void main(String[] args) {
        Function f1 = new DerivedFunction(new Function() {
            @Override
            public double f(double x) {
                return 3 * x * x * x + 2 * x * x + x + 1;
            }
        });
        System.out.println(f1.f(2));
        //二阶导函数:f''(x) = 18x + 4
        Function f2 = new DerivedFunction(f1);
        //打印函数f(x) = 3x^3 + 2x^2 + x + 1在x=2处的二阶导数
        System.out.println(f2.f(2));
    }
}

设想到片同学没有学过java8还是上述版本,以上代码没有应用java8部数式编程的新特点。 

要你沾过java8,请考虑如何改写以上代码,使该重简明。

 

六、最后之想法

 

public class DerivedFunction implements Function<Double, Double> {
    private static final double DELTA_X = 0.000001;
    private Function<Double, Double> function;

    public DerivedFunction(Function<Double, Double> function) {
        this.function = function;
    }

    @Override
    public Double apply(Double x) {
        return (function.apply(x + DELTA_X) - function.apply(x)) / DELTA_X;
    }
}

public class Main {
    public static void main(String[] args) {
        //打印函数在x=2处的二阶导
        System.out.println(new DerivedFunction(new DerivedFunction(x -> 3 * x * x * x + 2 * x * x + x + 1)).apply(2.0));
    }
}

事先几乎个想法为了扩大Function接口,使用了表面类、匿名类的艺术,其实呢堪为此其中类。而就当此,我为此了lambda表达式,是未是再简短了。

这边用之Function接口用的是jdk自带的,我们不需要协调定义了。因为马上是一个函数式接口,我们得以为此lambda方便地落实。后来意识,其实这里用UnaryOperator此接口更合适。

今日大家产生没有发察觉,用java、用OOP也可好简短地实现求导,并无比较初步的那段python代码麻烦大多。

 

七、编程范式

 

在我看来,编程范式简短的话就是是编程的均等栽模式,一栽风格。

自身先介绍中的老三个,你差不多就是了解它的意义了。

 

7.1 面向对象程序设计(OOP)

顾这里的同室应该针对面向对象有矣再也直观的认。在面向对象编程中,万物皆对象,抽象出类的概念。基本特色是包、继承、多态,认识不雅的同桌可以再次错过我前面的代码中追寻找就三个特点。

自己前还介绍了面向对象的几只极:开闭原则、据反转原则。其他还有单纯任务规范、里氏替换原则、接口隔离原则。这是面向对象的5独核心条件,合称SOLID)。

 

7.2 函数编程语言(FP)

本文开始那段代码用底就算是python函数式编程的语法,后来本身以用java8部数式编程的语法翻译了立即段代码。

相信您早就直观地感受及其的简要,以函数为主导,几行代码就解决了求导的题材。

 

7.3 过程式编程(Procedural programming)

大概学了编程都仿效过C,C语言就是一致种过程式编程语言。在我看来,过程式编程大概就是为着做到一个需要,像记流水帐一样,平铺直叙下去。 

       

八、结尾

 

由自身初学java,目前只好想到这么多。如果大家来重复好之想法要觉的本身上面说的发生题目,欢迎评论,望各位不吝赐教。

旋即是自身之首先首技术博客,但愿自己说亮了面向对象。如果对您来帮,请点单赞或者评论下,给自身接触持续写的动力。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图