• Programming Languages PartC Week1学习笔记——Ruby与面向对象编程


    Introduction to Ruby

    终于来到了我们比较熟悉的领域,OOP

    image-20220607105302547

    我们关注的Ruby的特性,最重要的是“纯面向对象语言”,基于类Class-based,动态类型。

    image-20220607105837127

    我们不关注的部分:

    image-20220607110031886

    特性对比:

    image-20220607110137224

    image-20220607110304554

    例子:

    class Hello
        def my_first_method
            puts "Hello, World!"
        end
    end
    x = Hello.new
    x.my_first_method
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在CMD中运行Ruby程序使用ruby命令,通过irb命令使用Ruby的REPL:

    image-20220607110708796

    Classes and Objects

    基于类的OOP规则

    image-20220607161842813

    image-20220607162121309

    例子:

    image-20220607162258442

    image-20220607162433838

    创建和使用对象,这部分语法都很熟悉的。

    image-20220607162656198

    变量以任意字母作为变量名的首字母,需要注意变量是可修改的mutable(修改内容还是修改引用?)

    image-20220607162748331

    关于self,跟python的self一样。语法糖也类似,在相同对象中调用方法可以省略self

    image-20220607163331891

    Ruby中self的常见用法,直接使用方法返回self本身,例如:

    image-20220607163643848

    image-20220607163631036

    最后,在Ruby中,每条语句换行很重要,如果多条语句放在同一行,那就需要使用分号“ ; ”分割。另外,跟python不一样,Ruby中缩进(indentation)对语义(semantics)不重要,只是良好的代码风格而已。

    Object State

    对象拥有自己的状态。状态组成实例变量(fields),只能被对象方法访问,state变量以@开头。如果使用了某个没有的state变量,会生成nil对象

    image-20220607164510038

    对象变量赋值创建了一个别名(引用),这时他们具有同样的state。

    但如果new一个新的对象的引用,创建的对象就有不同的state

    image-20220607165252447

    例子:

    image-20220607170919573

    image-20220607170952331

    image-20220607171140572

    例子2:initialize特殊方法,用于创建对象实例时初始化,相当于构造函数。

    image-20220607171241186

    Ruby中因为不要求所有变量都预先声明并且初始化,相同类的不同对象实例可以有不同的实例变量。

    image-20220607172125116

    通过**@@关键词**声明类变量,也就是类的静态变量

    image-20220607172310283

    类常量和类方法,就是类中的静态常量和静态方法

    类常量用在类中以大写字母开头,不能被修改。在外部需要使用类名修饰访问,例如C::Foo

    类方法定义需要使用self.method_name来定义(说实话有点奇怪),它属于类本身,所以需要使用类名来调用

    image-20220607172424494

    Visibility

    image-20220609164553258

    Ruby中,对象的状态state总是private的,同一个类的不同对象也无法访问。通常定义getter和setter来让对象state可见(跟JAVA的POJO类似)

    image-20220609164634690

    Ruby的语法糖。用state变量的名称来直接定义一个getter,用变量名加=来定义一个setter(并且以等号结尾地方法在调用时可以在等号前面加空格)。这样相当于将state封装成了一个普通的成员变量。

    同时,getter/setter还有简记的定义方式(类似java的注解)

    image-20220609165144560

    为何需要private的对象state:

    image-20220609165613740

    Ruby的三种visibility,public是默认值:

    public在任何类中都能调用

    protected在相同类或者子类中能够调用

    private只有在相同实例中能调用

    image-20220610123445498

    三种visibility访问权限的使用方法类似C++

    image-20220610123625970

    Ruby中比较特别的一个细节,对于private的成员或方法,必须通过简化的方式,即m或m(args)来调用(唯一方式),不能通过self来访问。

    image-20220610124209960

    A Longer Example

    本节以有理数类为例,综合应用前几节的内容:

    image-20220610161950604

    该例子中展现了很多Ruby的语法特性:

    # Section 7: A Longer Example
    
    class MyRational
    
      def initialize(num,den=1) # second argument has a default
        if den == 0
          raise "MyRational received an inappropriate argument"
        elsif den < 0 # notice non-english word elsif
          @num = - num # fields created when you assign to them
          @den = - den
        else
          @num = num # semicolons optional to separate expressions on different lines
          @den = den
        end
        reduce # i.e., self.reduce() but private so must write reduce or reduce()
      end
    
      def to_s 
        ans = @num.to_s
        if @den != 1 # everything true except false _and_ nil objects
          ans += "/"
          ans += @den.to_s 
        end
        ans
      end
    
      def to_s2 # using some unimportant syntax and a slightly different algorithm
        dens = ""
        dens = "/" + @den.to_s if @den != 1
        @num.to_s + dens
      end
    
      def to_s3 # using things like Racket's quasiquote and unquote
        "#{@num}#{if @den==1 then "" else "/" + @den.to_s end}"
      end
    
      def add! r # mutate self in-place
        a = r.num # only works b/c of protected methods below
        b = r.den # only works b/c of protected methods below
        c = @num
        d = @den
        @num = (a * d) + (b * c)
        @den = b * d
        reduce
        self # convenient for stringing calls
      end
    
      # a functional addition, so we can write r1.+ r2 to make a new rational
      # and built-in syntactic sugar will work: can write r1 + r2
      def + r
        ans = MyRational.new(@num,@den)
        ans.add! r
        ans  # 因为add!返回ans运算后的self,因此这一句其实不需要
      end
        
    protected  
      # there is very common sugar for this (attr_reader)
      # the better way:
      # attr_reader :num, :den
      # protected :num, :den
      # we do not want these methods public, but we cannot make them private
      # because of the add! method above
      def num
        @num
      end
      def den
        @den
      end
    
    private
      def gcd(x,y) # recursive method calls work as expected
        if x == y
          x
        elsif x < y
          gcd(x,y-x)
        else
          gcd(y,x)
        end
      end
    
      def reduce
        if @num == 0
          @den = 1
        else
          d = gcd(@num.abs, @den) # notice method call on number
          @num = @num / d
          @den = @den / d
        end
      end
    end
    
    # can have a top-level method (just part of Object class) for testing, etc.
    def use_rationals
      r1 = MyRational.new(3,4)
      r2 = r1 + r1 + MyRational.new(-5,2) # (r1.+(r1)).+ (...)
      puts r2.to_s
      (r2.add! r1).add! (MyRational.new(1,-4))
      puts r2.to_s
      puts r2.to_s2
      puts r2.to_s3
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101

    Everything is an Object

    纯粹的面向对象语言

    可以在任何东西(都是对象)上调用方法,如果方法不存在则抛出“undefined method”异常,不需要判断某个东西是不是可以调用方法(都能调用,但方法不一定都存在)。

    几乎所有东西都是方法调用,包括基本运算等

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ssJwFzDF-1663159644531)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611155402688.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dM12k3JW-1663159644532)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611160920913.png)]

    关于nil:在Ruby中用来描述某些没有包含任何数据的对象。类似于ML的unit,或者java的null。重点是nil也是一个对象。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1xqKF3Be-1663159644532)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611161246206.png)]

    另外,nil counts as false,nil在逻辑判断时相当于false。因此Ruby中,false代表fasle,nil也可以代表false

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fBUncJ1c-1663159644533)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611161456293.png)]

    所以,Ruby中任何结果都是对象,任何操作都是对象方法的调用

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JHmwlaAa-1663159644533)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611161706403.png)]

    反射,了解java应该对这个词不陌生,指程序能在运行时获取一个(类)对象,查询“对象能做的事”并随之响应的能力:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v8jld8UG-1663159644533)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611161902045.png)]

    ruby中的用法之一,查询可调用的方法(甚至可以在两个对象的methods结果之间做运算,下图是在3的类方法中而不在nil类方法中的方法):

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p8pZyU8g-1663159644534)(E:\OneDrive_WHU\OneDrive - whu.edu.cn\Programming_Languages_PartC\week1\week1.assets\image-20220611163037777.png)]

    Class Definitions are Dynamic

    Ruby能够在运行时操作对象,添加改变替换方法,某些时候会有用,但会破坏抽象和封装。在大型语言中是有争议的。

    image-20220613151725084

    image-20220614121901761

    甚至能够向内部类添加新方法

    image-20220614121956960

    由于所有的东西都是对象,即使是顶层函数,也是Object的对象,因此直接用def定义顶层函数,相当于添加新Object的方法。然后由于所有对象都直接或间接继承自Object,因此顶层函数可以作为每个对象的方法使用。例如:

    image-20220614122759257

    直接定义在Object中的方法与顶层函数相同,重复名称的方法会覆盖:

    image-20220614122930274

    但是,对Ruby预定义的运算符覆盖时一定要当心,例如:

    class Fixnum
        def + x
            13
        end
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这段代码覆盖了原有+运算符的定义,此时Ruby中一切的Fixnum都会受到影响,因此irb(REPL)会崩溃,因为irb本身也是Ruby编写的,会被这个覆盖影响。

    (可见,Ruby给运行时程序的权力过大还是挺危险的)

    image-20220614123516632

    Duck Typing

    面向对象中经典的鸭子类型:

    在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的,实际上是一种不严格的多态实现。

    image-20220614163229800

    例子:

    image-20220614165652765

    得益于动态类型语言的鸭子类型某些时候可能是有用的,但也在一定程度上属于poor style(如果使用者调用了duck typing,就难以更改函数的内部实现了,比如double那个例子,如果有字符串参数调用过这个方法,就不能再改成x * 2的实现方式了)。因此,这种不严格的多态方式也可能带来问题。

    image-20220614165940065

    Arrays

    Array类提供了Ruby的数组类型

    image-20220614195914906

    Ruby数组具有与python的list类似的语法,但注意Ruby数组在超出引用时会返回nil而不是抛出异常。

    image-20220615135729852

    同样,由于是动态类型语言,array允许不同数据类型;并且允许使用重载的运算符+作为作为concat方法。

    image-20220615135816575

    image-20220615140146768

    Array的初始化方法(具体的语法需要去翻阅文档)

    image-20220615140338253

    然后是push和pop,从右侧入栈出栈方法

    image-20220615140456938

    利用shift和unshift,从左侧入栈出栈方法

    image-20220615140828227

    Array对象变量仍然是一个引用,因此

    image-20220616102220863

    值得注意的是,多维数组是引用的引用(类似C++指针的指针),因此a+[]只能改变b的外层引用,但内层地址指向的一维数组没变。

    image-20220616102621609

    对数组的切片操作,例如f[2,4]表示从idx为2的元素开始,取4个元素。同时,切片的替换并不需要对应数量的数组(这一点很灵活)。

    image-20220616104607061

    Array可以调用一些迭代方法,例如each

    image-20220616104915900

    Blocks

    代码块,又一个与闭包相关的重要概念。代码块基本上就是一种闭包。

    image-20220616105227691

    image-20220616105549578

    一些奇怪的事情(block的语法)。{}可以用do end来代替。

    image-20220616105609854

    标准库函数通过代码块定义了大量有用的高等函数,代码块充当了其他函数中一等函数闭包的作用。

    image-20220616105937251

    代码块在Ruby中非常强大,甚至在初始化Array时都能使用

    image-20220616110640707

    对于any?和all?方法,如果没有参数,就默认去判断每个元素是否为真(注意Ruby中,只有false和nil是假)

    image-20220616110937069

    常用高等方法:

    方法名常见作用
    map / collect与Racket的map类似,对每个元素使用f方法
    inject与Racket的reduce类似,对每个函数用f方法进行累计
    select类似filter

    image-20220616113531809

    代码块嵌套的使用,其中(0…i)是序列(range),与python的range类似

    image-20220616115506833

    Using Blocks

    定义使用Block的函数时,通过yield关键词代替Block(类似匿名函数)。

    Snipaste_2022-06-16_16-12-53

    例子,这个例子用到了递归,并且递归传递了Block

    image-20220616162245314

    Procs

    Block实际上是第二级,能通过yield来调用,但不能在一个对象中被传递、返回和存储。它不是真正的闭包,但可以转换成真正的闭包。(Block不是对象!)

    闭包是Proc类的实例,Proc才是真正的一等表达式。

    Obeject的lambda表达式可以通过一个Block来生成Proc实例闭包。

    image-20220616170142003

    例子:

    例如Block难以实现类似Partial Application的应用,但如果使用Proc就能做到

    # 由于Block不能传递,不能用Block作为存储对象
    c = a.map {|x| {|y| x >= y }}
    # 但Proc实例可以传递
    c = a.map {|x| lambda {|y| x >= y} }
    
    • 1
    • 2
    • 3
    • 4

    并且,Proc实例通过call方法调用其中的代码块。

    image-20220616171344832

    这让我们更加理解**“First Class”**

    image-20220616172015457

    Hashes and Ranges

    (1)Hash

    相当于Racket的python的字典或动态的ML的Record

    语法类似python的字典

    image-20220616172314534

    能够根据键在hash表中添加值

    image-20220616172810548

    也能用键值对初始化hash表,使用**=>**连接键值。

    由于和Array是不同的数据结构,hash的each方法需要两个参数(key和value)

    image-20220616172859145

    (2)Range

    类似于用来动态存储一系列连续数的数组,但不是数组而是单独的数据结构,类似python的range,可以用于迭代。同时,Range相比之下更高效。

    image-20220616173518300

    两个优秀的风格:

    • 尽可能使用Range

    • 非数字索引时使用Hash

    Hash和Range拥有一些与Array相同的方法(迭代器方法),这也是对duck typing有利。

    image-20220616173857724

    Subclassing

    回到面向对象的部分,介绍Ruby的继承

    image-20220622152540770

    子类定义:

    子类会继承超类所有的方法,而不会有继承权限的问题(例如C++和Java)。子类也不会被类型检测,能访问所有方法和成员。

    image-20220622152617905

    image-20220622220641263

    注意反射机制:

    image-20220622220924615

    另外,与所有OOP语言类似,子类都(is)超类,所以子类实例(子类)是属于的超类

    image-20220622221002459

    但需要区分的是,子类实例本身并不是超类的实例,类有继承关系,但子类对象实例不是超类对象实例。

    image-20220622221355682

    注意,is_a? 或者 instance_of?之类的方法并不通常是OOP style,因为我们假设了使用的对象是 某个假定的类或类对象,它拒绝了duck typing(我们不能使用一个具有类似x,y,color等 成员和方法的其他类对象)。

    image-20220622221419406

    Why Use Subclassing?

    image-20220623212323926

    image-20220623212422873

    image-20220623212632495

    image-20220623212750729

    Overriding and Dynamic Dispatch

    例子

    image-20220623225532227

    image-20220623225549016

    对象与闭包在很多特性上类似,但最大的不同是覆写可以让一个方法定义在超类中但在子类中调用。

    image-20220623225717580

    image-20220623230417661

    image-20220623230527983

    image-20220623230711107

    image-20220623231010363

    Method-Lookup Rules, Precisely

    动态分派(dynamic dispatch),比较熟知的名字是后期绑定或者虚函数,用于在运行期选择调用方法的实现的流程。被认为是面向对象语言(Object-Oriented programming:OOP)的基本特性。

    定义方法查找语法是需要的。

    image-20220624205053885

    回顾各种语言中的变量查找过程。

    image-20220624205348869

    Ruby中使用self来查找各种类或实例的成员或方法。

    image-20220624205539680

    Ruby的方法查找:

    image-20220624205801610

    image-20220624210107516

    动态分派的过程比闭包更复杂,必须将self特殊对待。

    image-20220624210157295

    在Java等语言中,方法查找规则是类似的,但是更复杂(因为方法在类中能具有相同名字(重载Overload)),只有在方法名与参数数量、顺序、类型等都完全相同时,才会发生子类方法的覆写Override。这与Java等语言的静态类型有关,能够通过参数的静态类型来判断选择最符合条件的方法。这种信赖根本上基于type-checking的规则

    **但在Ruby中,名字相同的方法就总是会被覆写。**因为不存在type checking。

    这一点对于Java或者C++的使用者来说是比较容易理解的。

    总的来书就是,Java和Ruby等都有动态分派(dynamic dispatch)的特性(区别于静态分派),但只有具有静态类型(static type or type chcking) 和 静态重载(static overloading)的语言才能实现上述复杂的覆写机制(即方法名与参数数量、顺序、类型等都完全相同时,才会发生子类方法的覆写)。因此Ruby的覆写机制没有那么复杂,名字相同的方法就总是会被覆写。

    image-20220624210540077

    Dynamic Dispatch Versus Closures

    动态分派与闭包的比较:

    ML的闭包示例:闭包不会被后续的shadowing影响,这在某些时候是有用的但某些时候是糟糕的(这就是闭包的代价)

    image-20220624220933885

    image-20220624222102557

    Ruby的动态分派示例:覆写将会改变超类已定义的方法,它的代价恰好与闭包相反。可能最终会覆写其他方法所依赖的方法,甚至不知道它是重要的。例如下面的B类中的odd没有任何问题甚至更快,但是C类由于错误的覆写导致问题产生(这恰好是闭包能防止的事情)。

    image-20220624221629652

    image-20220624221641732

    image-20220624221703213

    image-20220624222044973

    面向对象的代价:

    某些方法的可覆写性会带来方法行为改变的问题(甚至不被覆写时也可能),这让我们难以推断“正在关注的代码”是哪一段。因此我们需要去避免一些覆写,通过类似private继承或者final 方法(类似C++或Java中的做法)的方式。

    但面向对象的优势是,它使得子类更容易影响超类方法的行为(在不复制代码的情况下)。

    image-20220624222141100

    Optional: Dynamic Dispatch Manually in Racket

    如何在Racket这样的语言中,手动实现类似动态分派的特性?

    这一节的做法有点类似于之前实现解释器的方式。

    尽管Racket已经有内置的类和对象,但这一节为了示范一种语言的某种语法(特性)可以在(通过)另一种语言的习惯用法(来构建)。并且为了更好的理解动态分派。

    image-20220624223141713

    借助struct。这里的示例中,self只是lambda匿名函数的一个用来记录对象本身的额外参数(这个额外参数的做法有点类似python,用来保证对象方法中的对象self调用),但在这里,self不是一个特殊关键词(不会被特殊对待,它可以不叫self,可以叫this、it等等)

    image-20220624223643984

    image-20220624223842409

    本节的代码:其中make-polar-point子类的实现方式是重点。

    ; Section 7: Optional: Dynamic Dispatch Manually in Racket
    
    #lang racket
    
    ;; We can "use" dynamic dispatch in a language without it manually
    
    ;; Our "objects" will have:
    ;;  * an immutable list of mutable "fields" (symbols and contents)
    ;;  * an immutable list of immutable "methods" (symbols and functions taking self)
    (struct obj (fields methods))
    
    ; like assoc but for an immutable list of mutable pairs
    (define (assoc-m v xs)
      (cond [(null? xs) #f]
            [(equal? v (mcar (car xs))) (car xs)]
            [#t (assoc-m v (cdr xs))]))
    
    (define (get obj fld)
      (let ([pr (assoc-m fld (obj-fields obj))])
        (if pr
            (mcdr pr)
            (error "field not found"))))
    
    (define (set obj fld v)
      (let ([pr (assoc-m fld (obj-fields obj))])
        (if pr
            (set-mcdr! pr v)
            (error "field not found"))))
     
    (define (send obj msg . args) ; convenience: multi-argument functions (2+ arguments)
      (let ([pr (assoc msg (obj-methods obj))])
        (if pr
            ((cdr pr) obj args) ; do the call
            (error "method not found" msg))))
    
    (define (make-point _x _y)
      (obj
       (list (mcons 'x _x)
             (mcons 'y _y))
       (list (cons 'get-x (lambda (self args) (get self 'x)))
             (cons 'get-y (lambda (self args) (get self 'y)))
             (cons 'set-x (lambda (self args) (set self 'x (car args))))
             (cons 'set-y (lambda (self args) (set self 'y (car args))))
             (cons 'distToOrigin 
                   (lambda (self args)
                     (let ([a (send self 'get-x)]
                           [b (send self 'get-y)])
                       (sqrt (+ (* a a) (* b b)))))))))
    
    (define (make-color-point _x _y _c)
      (let ([pt (make-point _x _y)])
        (obj
         (cons (mcons 'color _c) 
               (obj-fields pt))
         (append (list
                  (cons 'get-color (lambda (self args) (get self 'color)))
                  (cons 'set-color (lambda (self args) (set self 'color (car args)))))
               (obj-methods pt)))))
          
    (define (make-polar-point _r _th)
      (let ([pt (make-point #f #f)])
        (obj
         (append (list (mcons 'r _r)
                       (mcons 'theta _th))
                 (obj-fields pt)) ; Java-style field extension
         (append ; overriding by being earlier in the list (see send function)
          (list 
           (cons 'set-r-theta 
                 (lambda (self args)
                   (begin 
                     (set self 'r (car args))
                     (set self 'theta (cadr args)))))
           (cons 'get-x (lambda (self args)
                          (let ([r (get self 'r)]
                                [theta (get self 'theta)])
                            (* r (cos theta)))))
           (cons 'get-y (lambda (self args)
                          (let ([r (get self 'r)]
                                [theta (get self 'theta)])
                            (* r (sin theta)))))
           (cons 'set-x (lambda (self args) 
                          (let* ([a     (car args)]
                                 [b     (send self 'get-y)]
                                 [theta (atan b a)]
                                 [r     (sqrt (+ (* a a) (* b b)))])
                            (send self 'set-r-theta r theta))))
           (cons 'set-y (lambda (self args) 
                          (let* ([b     (car args)]
                                 [a     (send self 'get-x)]
                                 [theta (atan b a)]
                                 [r     (sqrt (+ (* a a) (* b b)))])
                            (send self 'set-r-theta r theta)))))
          (obj-methods pt)))))
    
    (define p1 (make-point -4 0))
    p1
    (send p1 'get-x)
    (send p1 'get-y)
    (send p1 'distToOrigin)
    (send p1 'set-y 3)
    (send p1 'distToOrigin)
    
    (define p2 (make-color-point -4 0 "red"))
    p2
    (send p2 'get-x)
    (send p2 'get-y)
    (send p2 'distToOrigin)
    (send p2 'set-y 3)
    (send p2 'distToOrigin)
    
    (define p3 (make-polar-point 4 3.1415926535))
    p3
    (send p3 'get-x)
    (send p3 'get-y)
    (send p3 'distToOrigin)
    (send p3 'set-y 3)
    (send p3 'distToOrigin)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117

    image-20220624225236774

    但某些语言仍然难以实现类似特性,例如ML,他的type system不能实现类似的子类型判断。

    但注意,老师强调了一下,难以实现并不是不能实现,我们仍然可以用之前学到的方法,在ML中用datatype构造一个足够大的“Obeject”,然后让所有的“类”都是这个datatype的值,这样,这些所谓“类”之间的“继承关系”就可以通过ML的类型系统来判断了(还是借助pattern match之类的特性)。

    这其实也算是一种代价交换,ML虽然难以实现动态分派的特性,但却很容易实现闭包,相反Java是面向对象的,很容易动态分派,但闭包的实现就更难一些(例如PartA中week4时我们在Java中实现闭包的方式)。

    image-20220624230456469

  • 相关阅读:
    Linux服务器被入侵后的排查思路(应急响应思路)
    科大讯飞2022秋招笔试知识点总结(超详细)
    2023.10.01 homework
    Flutter Json解析工具
    Linux,计算机网络,数据库
    docker安装可视化工具portainer中文版
    校内评奖评优|基于Springboot+Vue实现高校评优管理系统
    飞桨paddlespeech语音唤醒推理C INT8 定点实现
    【笨~~~】在python中导入另一个模块的函数时,为什么会运行模块中剩下的部分??顶层?
    git LFS
  • 原文地址:https://blog.csdn.net/df1445/article/details/126860064