每次调用`sqrt`时,是否会更新`letfn`中定义的绑定?

时间:2017-01-04 08:31:30

标签: clojure

如果absavg已定义:

(defn sqrt [x]
  (letfn [(s [guess]
            (if (good-enough? guess)
              guess
              (s (improve guess))))
          (good-enough? [guess]
            (< (abs (- (square guess) x)) 0.0001))
          (improve [guess]
            (avg (/ x guess) guess))]
    (s 1.0)))

请忽略一下我在这里重新发明轮子。 :)这只是一个例子。

如果是这样,有没有办法绕过这个,所以每次调用函数时都不会反复绑定名称,并且不在函数外引入更多名称?

4 个答案:

答案 0 :(得分:3)

  

每次调用letfn时,是否会重新定义sqrt中定义的函数?

是。

sgood-enough绑定将在每次输入sqrt函数时重新制作。他们必须重新构建的东西。

  • sgood-enough个函数(有效fn个特殊表单) 当(defn sqrt ... )为时,编译一次 进行。
  • 对于x的每次通话,它们都会被sqrt关闭。
  • 闭包可以作为内部类在JVM上实现 遵守IFn接口。
  • 每次sqrt时都会构造闭包/函数对象 虽然他们举例说明的课程已被编译,但已进入。

这取决于重新定义的含义。我认为不是,但绑定两侧的元素都会更新。所以现在我想所以

  

有没有更惯用的方法来编写平方根函数?

是。

在JVM上......

(defn sqrt [x] (Math/sqrt x))

(sqrt 2);1.4142135623730951

答案 1 :(得分:2)

简短的回答是&#34;是&#34;,绑定将被更新。但是,这与实例化一个非常简单的类一样便宜。您可以在下面看到反编译的java类,如何使用一个简单的参数实现clojure,以及嵌套函数将如何导致更多的类。所以这还是很便宜的。

import angular from 'angular';
import '../services/user';
import './modals/users';

const module = angular.module('MyApp.controllers.users', [
  'MyApp.services.user',
  'MyApp.services.globals',
  'MyApp.modals.user',
]);

const UsersController = ($scope, UserService) => {
  'ngInject';
  $scope.title = 'Users';

  $scope.users = UserService.GetAll();
}
module.exports = module.controller('UsersController', UsersController);

答案 2 :(得分:0)

明智的答案是,不定义自己的平方根操作是最惯用的。对(Math/sqrt x)进行互操作的java.util.Math.sqrt()将是首选。看到(defn sqrt [x] (Math/sqrt x))浮出水面,或者至少是我为多个项目所做的事情,这是相当普遍的。

更好的答案是使用clojure.algo.generic已经以可扩展和惯用的方式定义sqrt等其他操作。

牛顿方法的这种特殊实现是很好的,并且采用非常传统的方案风格,但由于它通过多个fn使用盒装算术,因此它将大大优于{{ 1}}它没有提供algo.generic的数字塔灵活性,或algo.generic上的等效实现。

至于每次都会重新定义Math/sqrt中的函数,letfn的传统Scheme实现类似于:

  • 为每个letfn名称
  • 创建一个nil的绑定
  • fn每个绑定到setq正文,因为每个名称都有绑定
  • 评估正文表格

Clojure在幕后做了同样的事情。 fn中的每个fn都编译为letfn实例类,该实例类接受对封闭的AFn s的实例参数的引用。发出的字节码在逻辑上与Scheme实现的格式相同:

  • 将每个fn的已命名本地绑定到fn
  • 使用与其关闭的其他Var正文相对应的AFn初始化每个Var个实例
  • 将每个实例化的letfn绑定到相应的AFn
  • 评估正文表格

技术上是的,每次执行主体时都需要重新建立Var中的每个绑定,并且新绑定是对新letfn实例的新(匿名)Var已编译的类。没有lambda提升或任何其他转换来防止这种情况,但这样做的开销可以忽略不计。

答案 3 :(得分:-1)

以下代码显示了差异。请注意,只需要一个简单的let来定义本地函数:

(defn my-inc [x] (+ 1 x))
(defn my-square [x] (* x x))
(println :1 (my-square (my-inc 2)))

(let [my-inc-2    (fn [x] (+ 1 x))
      my-square-2 (fn [x] (* x x)) ]
  (println :2 (my-square-2 (my-inc-2 2))))

(println :sqrt-9 (Math/sqrt 9))

;=> :1 9
;=> :2 9
;=> :sqrt-9 3.0

两种情况之间的唯一区别是my-inc-2my-square-2的可见性仅限于let块。我比使用letfn更喜欢这种方式,因为我认为常规(fn ...)语法更清晰。

关于sqrt,使用Java内置函数要比编写自己的函数好得多。

对于一般的数值技术,一种有效的技术是使用多项式第一近似,然后进行牛顿方法的几次迭代。这就是Matlab计算正态分布函数N(0,1)的方法,例如。