Programming Languages PartA Week5学习笔记——SML进阶与编程哲学

2023-05-16

文章目录

      • Week5 Introduction
      • What is Type Inference
      • ML Type Inference
      • Type Inference Examples
      • Polymorphic Examples
      • Optional: The Value Restriction and Other Type-Inference Challenges
      • Mutual Recursion
      • Modules for Namespace
      • Signatures and Hiding Things
      • A Module Example
      • Signatures for Our Example
      • Signature Matching
      • An Equivalent Structure
      • Another Equivalent Structure
      • Different Modules Define Different Types
      • Equivalent Functions
      • Standard Equivalences
      • Equivalence Versus Performance

Week5 Introduction

第五周内容的简单导引

image-20220523094729868

What is Type Inference

关于type-checking。ML属于静态类型语言(虽然在编写代码时并不要求显式标出变量类型),在编译时会判断变量类型。

image-20220523095311535

所以ML语言其实是一种隐含类型(Implicitly typed)的静态类型语言

image-20220523095830742

类型推断,为每个bingding或者表达式给定一个类型,这样就判断type-chcking成功

image-20220523095924976

ML Type Inference

ML的类型推断

image-20220523100639096

类型推断的关键步骤:

image-20220523100715823

类型推断的简单例子:

image-20220523101313567

ML中为无法判断的类型给定一个Type Variable (例如 'a, 'b,可用于实现多态)。但需要注意的是并不是所有type inference的语言都有type variable(例如Java),有type variable的语言也不一定会做type inference

image-20220523101610032

Type Inference Examples

类型推断始终遵循几大步骤(与上一节相同的步骤)

1、按照顺序收集所有binding,并决定类型

2、对于每个binding(变量或者表达式或者函数等)

(1)收集所有类型检查需要的事实,分析约束条件**(Constraints)**来确定类型

(2)类型的事实有冲突,抛出type error (Over-Constrained)

(3)对没有事实可以判断类型的binding给定一个type variable (Unconstrained)

其中2-(1)是本节阐述的内容,如何通过事实分析约束条件

image-20220523102649632

例子1:

image-20220523102639833

例子2:

image-20220523103753502

Polymorphic Examples

多态的例子,首先仍然是同样的判断准则:

image-20220523123614988

例子1

image-20220523124138794

例子2,T4存在两种情况,因为编译器不知道会执行哪一条语句,只知道type的可能性。只有在运行时才能确定执行哪条语句。

image-20220523124331658

例子3:

image-20220523125053148

Optional: The Value Restriction and Other Type-Inference Challenges

截至目前,课程讲述的ML 类型推断体系是不完全的,但想要让它更完善就不得不引入更高级但更不优雅的内容

image-20220523125617511

例如如下问题:

image-20220523150956697

我们不能只是为reference类型制定特殊规则来避免这个问题,因为ML的函数可传递,同时类型又可以取别名,type-checker不知道reference的别名(type-chekcer在运行之前检查)。

image-20220523151637034

解决方案是为整个语言引入Value restriction的机制,只让变量binding(变量或者值的表达式或者函数定义时)获得多态类型,但函数调用不能获得多态类型(会给出一个警告和无约束的假类型):

image-20220523152816119

当编译器无法判断是否有reference参加或者是否生成reference的时候,也会使用value restriction

image-20220523153600683

image-20220523153626034

image-20220523153724138

image-20220523153812962

关于子类型的讨论,子类型 所实现的多态(类似于其他语言中子类和超类的关系,例如超类指针指向子类地址),在一定的细节约束下也可以支持,但会让语言中的类型更难以推断和理解(所以在C++和Java这类静态类型语言中需要显式声明变量的类型)

image-20220523154126529

Mutual Recursion

互递归就是两个函数相互调用,有一定用处,例如可以实现状态机,但也会带来一些问题(由于相互调用,难以分清binding的先后顺序):

image-20220523155051159

ML的做法是定义一套新的结构,使用and关键词来连接多个函数或datatype(因为datatype定义时也可以递归定义)等,这些使用and连接的变量将会作为一个整体(bundle),同时被type-check或valuate

image-20220523155417285

例子:

image-20220523155654539

image-20220523155934741

第二个例子:

image-20220523160230597

同时,也可以使用高等函数(传递一等函数)来替代and结构,实现同样的功能

image-20220523160427754

image-20220523161117596

Modules for Namespace

使用structure关键词定义modules 。modules的概念在ML中类似于C++的命名空间和类的混合概念(可以用来定义ADT,见后面的例子)。

image-20220523161243681

例子:

image-20220523161600929

image-20220523162427991

open关键词用于直接引入一个module,可以不带module名直接使用其中的bindings(类似python的 from xxx import *)。也可以通过函数传递的方式,将module的某个函数binding到某个变量上,直接使用。

image-20220523162542407

Signatures and Hiding Things

Signature是module的一种类型,其中定义有哪些binding与binding的类型,可以通过 :> 符号作用于module。(这里的signature和module又有点像Java的接口和类的关系了,但也不太一样)。

image-20220523163209104

image-20220523163701451

** signatures的真正价值是用来隐藏某些bindings及其类型的定义,并对外隐藏实现细节(从这个意义上来说,signatures实际上也确实类似接口的功能)。

image-20220523163846467

image-20220523164148417

通过signatures我们可以实现private和public的bindings。

  • 在signatures中定义的bindings可以在module外部使用(public)
  • 不在signatures中定义的bindings 不能在module外部使用,但可以在内部使用(private)

image-20220523164827906

A Module Example

定义一个ADT,表示实数及其运算

image-20220523165829634

(* Section 4: A Module Example *)

(* will assign different signatures to this module in later segments *)

structure Rational1 = 
struct

(* Invariant 1: all denominators > 0
   Invariant 2: rationals kept in reduced form *)

  datatype rational = Whole of int | Frac of int*int
  exception BadFrac

(* gcd and reduce help keep fractions reduced, 
   but clients need not know about them *)
(* they _assume_ their inputs are not negative *)
  fun gcd (x,y) =
       if x=y
       then x
       else if x < y
       then gcd(x,y-x)
       else gcd(y,x)

   fun reduce r =
       case r of
	   Whole _ => r
	 | Frac(x,y) => 
	   if x=0
	   then Whole 0
	   else let val d = gcd(abs x,y) in (* using invariant 1 *)
		    if d=y 
		    then Whole(x div d) 
		    else Frac(x div d, y div d) 
		end

(* when making a frac, we ban zero denominators *)
   fun make_frac (x,y) =
       if y = 0
       then raise BadFrac
       else if y < 0
       then reduce(Frac(~x,~y))
       else reduce(Frac(x,y))

(* using math properties, both invariants hold of the result
   assuming they hold of the arguments *)
   fun add (r1,r2) = 
       case (r1,r2) of
	   (Whole(i),Whole(j))   => Whole(i+j)
	 | (Whole(i),Frac(j,k))  => Frac(j+k*i,k)
	 | (Frac(j,k),Whole(i))  => Frac(j+k*i,k)
	 | (Frac(a,b),Frac(c,d)) => reduce (Frac(a*d + b*c, b*d))

(* given invariant, prints in reduced form *)
   fun toString r =
       case r of
	   Whole i => Int.toString i
	 | Frac(a,b) => (Int.toString a) ^ "/" ^ (Int.toString b)

end

image-20220523170742104

image-20220523171140212

Signatures for Our Example

使用signatures,隐藏上一节的例子中的gcd和reduce,将bindings私有化

image-20220523171721110

但是只隐藏函数当然会有问题,我们的变量并没有得到有效封装(暴露给了用户),用户可以通过例如Rationall.Frac(1,0)来自己产生这个变量。我们应该把这些部分封装起来。

image-20220523171844265

所以需要将内部datatype定义的类型一并设为私有,并留出给用户定义变量的接口 make_frac。但问题在于signatures中开放的函数需要用到datatype定义的rational类型,但现在signatures不知道它存在。

image-20220523172539192

要解决这个问题,ML中可以通过type关键词在signatures中标明某种类型的存在(一种抽象类型),但不开放给用户(用户不知道其定义)

image-20220523172755231

image-20220523173320863

因此,使用signatures的两大关键在于:

image-20220523173539979

另外,比较有趣的事情是,由于Whole不会引发异常带来问题,我们也可以对用户开放Whole(相当于开放rational的一部分),记得datatype的Constructor本身就是一个函数,所以我们可以直接在signatures中写上Whole函数的部分,而不需要在module中重复定义(因为定义datatype的时候已经写了datatype rational = Whole of int | Frac of int*int,Whole在这里就会被认为是一个int->rational的函数定义)

image-20220523173836002

Signature Matching

image-20220523223757700

An Equivalent Structure

抽象的一大目的是让不同实现(implementation)相等(equivalent)(对用户而言),例如接口的抽象也是需要让类实现对用户等效。用户不关心具体实现细节,但相同的接口(即使实现不同),也应该得到等效的结果。

image-20220523231132837

例如,以前面的三种RATIONAL signatures为例:

老师给出的例子:

(* Section 4: An Equivalent Structure *)

(* this signature hides gcd and reduce.  
That way clients cannot assume they exist or 
call them with unexpected inputs. *)
signature RATIONAL_A = 
sig
datatype rational = Frac of int * int | Whole of int
exception BadFrac
val make_frac : int * int -> rational
val add : rational * rational -> rational
val toString : rational -> string
end

(* the previous signature lets clients build 
 any value of type rational they
 want by exposing the Frac and Whole constructors.
 This makes it impossible to maintain invariants 
 about rationals, so we might have negative denominators,
 which some functions do not handle, 
 and print_rat may print a non-reduced fraction.  
 We fix this by making rational abstract. *)
signature RATIONAL_B =
sig
type rational (* type now abstract *)
exception BadFrac
val make_frac : int * int -> rational
val add : rational * rational -> rational
val toString : rational -> string
end
	
(* as a cute trick, it is actually okay to expose
   the Whole function since no value breaks
   our invariants, and different implementations
   can still implement Whole differently.
*)
signature RATIONAL_C =
sig
type rational (* type still abstract *)
exception BadFrac
val Whole : int -> rational 
   (* client knows only that Whole is a function *)
val make_frac : int * int -> rational
val add : rational * rational -> rational
val toString : rational -> string
end 

(* this structure can have all three signatures we gave
   Rationa1 in previous segments, and/but it is 
   /equivalent/ under signatures RATIONAL_B and RATIONAL_C 

   this structure does not reduce fractions until printing
*)
       
structure Rational2 :> RATIONAL_A (* or B or C *) =
struct
  datatype rational = Whole of int | Frac of int*int
  exception BadFrac

   fun make_frac (x,y) =
       if y = 0
       then raise BadFrac
       else if y < 0
       then Frac(~x,~y)
       else Frac(x,y)

   fun add (r1,r2) = 
       case (r1,r2) of
	   (Whole(i),Whole(j))   => Whole(i+j)
	 | (Whole(i),Frac(j,k))  => Frac(j+k*i,k)
	 | (Frac(j,k),Whole(i))  => Frac(j+k*i,k)
	 | (Frac(a,b),Frac(c,d)) => Frac(a*d + b*c, b*d)

   fun toString r =
       let fun gcd (x,y) =
	       if x=y
	       then x
	       else if x < y
	       then gcd(x,y-x)
	       else gcd(y,x)

	   fun reduce r =
	       case r of
		   Whole _ => r
		 | Frac(x,y) => 
		   if x=0
		   then Whole 0
		   else
		       let val d = gcd(abs x,y) in 
			   if d=y 
			   then Whole(x div d) 
			   else Frac(x div d, y div d) 
		       end
       in 
       (* 注意这里调用的reduce,Rational1中没有reduce *)
	   case reduce r of
	       Whole i   => Int.toString i
	     | Frac(a,b) => (Int.toString a) ^ "/" ^ (Int.toString b)
       end
end

可以发现,如果给用户过多的权限(例如在signatures中定义了rational类型,用户能直接调用Frac构造器)就会导致如下的不同实现中结果不相等的情况,这样的抽象就不是一个好的抽象。所以将rational在signature中作为一个抽象类型是非常重要的。

image-20220523231614460

结论是:当signature的公共接口暴露的越少(抽象程度越高),不同实现对用户的呈现结果越可能实现equivalent。

Another Equivalent Structure

不同的structures可以为signature中的抽象类型提供不同实现,例如让type rational = int * int

image-20220524145326750

注意第二点细节,虽然signature规定Whole需要int -> rational,但这是对外部而言(signature规定都是对外部而言),在内部由于目前的例子中Whole没有定义,所以可以自行定义,甚至可以实现多态’a -> 'a * int(在内部调用时),但在外部就必须是int->int*int ,也就是目前的int -> rational

image-20220524145648364

Different Modules Define Different Types

即使是实现了同一个signature的modules,不同的实现都定义了不同的类型。

例子,当多个不同的modules相互调用时会抛出异常,这也很容易理解(毕竟module首先具有命名空间的作用,每个module内部函数生成的内部变量都要带上一个命名空间的前缀,例如Rational3.rational)

image-20220524150518902

从这些模块的函数类型可以看出,不同的模块就是不同的类型

image-20220524150728715

image-20220524150756836

Equivalent Functions

image-20220524151005779

为什么我们需要Equivalence

(1)为了代码维护或重构

(2)为了代码迭代向下兼容

(3)为了优化代码运行效率

(4)为了代码抽象

例如我们需要在重构垃圾代码时保持函数功能(包括接口)不变,这就是一种Equivalence

image-20220524151039367

两个函数什么时候能够实现等效?在他们在任何地方都拥有观察起来相同的行为的时候

image-20220524151747063

这里注意一下副作用(side-effect),在函数式编程中,我们尽可能让所有的函数都定义成实现一种类似于数学函数的行为模式(接受自变量,返回因变量),而没有多余的操作。这些多余的操作,例如在函数中(不是通过返回值shadowing的方式)改变某个变量的现有值(mutation),读取设备或文件等的输入或者向设备或文件输出(input/output),抛出异常和处理异常(exception raise and handle)等,都相当于是函数的副作用

image-20220524152657266

纯粹的函数更可能让事物(让更多事物)等效(Pure functions make more things equivalent)

image-20220524152847877

Standard Equivalences

其他一些标准的等效情况

(1)先是语法糖 andalso orelse之类的等效

image-20220524154230540

(2)然后是替换函数内部的一些参数名

image-20220524154419427

(3)是否使用helper function不影响等效情况(在使用的函数的scope没改变的情况下)

image-20220524154654540

(4)不必要的function wrapping不影响等效

值得注意的是,下面例子中的反例,左侧g会表现为定义时不会print(因为是函数定义,不会调用),但调用时print every time,右侧g表现为定义(实际上不是定义函数,而是传递(h())的调用结果)时print一次,之后调用不会再print

image-20220524155044467

(5) 在忽略类型的时候,下面的两种表达式等效(let 和 匿名函数)

但事实上两者的类型不同,左侧的x可以多态,而右侧不可。右侧的涵盖范围相对小一些。(why?这里其实没太理解)

image-20220524160059220

Equivalence Versus Performance

Equivalence虽然是外部表现相同,但由于内部实现不同,会导致运行表现上的差异(例如时间、空间差异)。这也是为什么我们能够优化代码效率(内部)并保持Equivalence(外部)。

image-20220524161741775

为不同的任务定义不同的等效定义:

image-20220524161932375

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Programming Languages PartA Week5学习笔记——SML进阶与编程哲学 的相关文章

  • 硬件笔记之Thinkpad T470P更换2K屏幕

    0x00 前言 手上的Thinkpad T470P屏幕是1920x1080的屏幕 xff0c 色域范围NTSC 45 xff0c 作为一块办公用屏是正常配置 xff0c 但是考虑到色彩显示和色域范围 xff0c 计划升级到2K屏幕 2k屏幕
  • Kafka学习笔记之Kafka High Availability(下)

    0x00 摘要 本文在上篇文章基础上 xff0c 更加深入讲解了Kafka的HA机制 xff0c 主要阐述了HA相关各种场景 xff0c 如Broker failover xff0c Controller failover xff0c To
  • Boyer-Moore算法的C++实现

    BM算法 阮一峰的网络日志 以上给出了通俗易懂的算法讲解 xff0c 下面给出代码实现 xff0c 使用的宽字符 xff0c 这样就不限于英文字母了 stdafx h编译不过去就屏蔽掉 StringSearch BoyerMoore cpp
  • Kafka学习笔记之Kafka Consumer设计解析

    0x00 摘要 本文主要介绍了Kafka High Level Consumer xff0c Consumer Group xff0c Consumer Rebalance xff0c Low Level Consumer实现的语义 xff
  • Kafka学习笔记之Kafka性能测试方法及Benchmark报告

    0x00 概述 本文主要介绍了如何利用Kafka自带的性能测试脚本及Kafka Manager测试Kafka的性能 xff0c 以及如何使用Kafka Manager监控Kafka的工作状态 xff0c 最后给出了Kafka的性能测试报告
  • 用Clion运行C++代码时输出中文乱码解决方法

    用Clion运行C 43 43 代码时输出中文乱码解决方法 1 File gt setting 2 在页面最下面找到UTF 8 xff0c 将其改成GBK 3 根据提示选择Convert 4 问题解决啦
  • Java核心技术卷1:基础知识(原书第10版)

    本书为专业程序员解决实际问题而写 xff0c Java基础知识面覆盖很完整 xff0c 可以帮助你深入了解Java语言和库 在卷I中 xff0c Horstmann主要强调基本语言概念和现代用户界面编程基础 xff0c 深入介绍了从Java
  • 斜率K的意义

    夹角公式 设直线l1 l2的斜率存在 xff0c 分别为k1 k2 xff0c l1到l2的转向角为 则tan 61 k2 k1 xff09 1 43 k1k2 xff09 l1与l2的夹角为 xff0c 则tan 61 k2 k1 xff
  • 阿里巴巴 2014校招 研发工程师 笔试

    刚杭州这边阿里巴巴校招笔试回来 回忆一下题 xff0c 为大家将来的笔试做点准备 选择题 xff1a 1 字符串 alibaba 的huffman编码有几位 2 以下哪些用到贪婪算法 xff1a 最小生成树的Prim算法最小生成树的Krus
  • Bag of Words(词袋模型)

    词袋模型的提出是为了解决文档分类 xff0c 主要应用在 NLP Natural Language Process xff0c IR Information Retrival xff0c CV xff08 Computer Vision x
  • 全向和定向天线区别,何为天线增益

    文 白浪 为什么网络信号弱 速率低 时断时续 xff1f 为什么购买了大量的AP xff0c 但是还有地方信号不好 xff0c 而有的地方信号多到互相干扰 xff1f 为什么布置了大增益的天线 xff0c 结果还未能得偿所愿 xff1f 无
  • 2017电赛国赛可见光定位装置(精确度达到1cm)

    先不多说上源码 Github网址 xff1a https github com DerrickRose25 Indoor positioning of visible light 用的畸变摄像头 xff0c 用Matlab做线性拟合和畸变矫
  • 如何给图片添加黑色边框

    有时候图片有了黑色边框才能与相近的背景区分开 xff0c 这个时候给图片添加边框就是必须的了 你好这位朋友 xff01 很简单 xff0c 要这样操作 xff1a 将照片打开 xff0c 裁切好后 xff0c 应用工具箱中的矩形选择工具 x
  • STLINK怎么与STM32单片机连接

    STLink是ST官方开发的单片机仿真工具 xff0c 可以烧写程序 在线仿真 xff0c 使用非常方便 STLink具有两种接口 xff0c 分别为 1 SWD模式 2 SWIM单总线模式 SWD模式主要针对STM32系列的单片机 xff
  • 【树莓派4B为例的树莓派接口认识】

    1 xff1a SOC芯片 树莓派采用博通 xff08 Broadcom xff09 BCM2711芯片作为SOC芯片 xff0c 芯片上集成了CPU GPU DSP及SDRAM内存等 xff0c 其中CPU和GPU共享内存 xff0c 可
  • 仿真阿克曼小车+3D激光雷达velodyne并使用lego-LOAM与octomap建图

    仿真的学习内容基于以下项目 xff0c 然后将2D激光雷达换成3D的velodyne试了试效果 xff0c 想跑lego loam然后用octomap转成2D栅格地图看能不能导航 阿克曼小车仿真开源 首先需要下载当前ros版本的velody
  • 树莓派3B安装Ubuntu 18.04

    这里展示的是使用显示器的方法 xff0c 不用ssh 树莓派3b安装Ubuntu18 04完全遵照的Ubuntu wiki中的步骤 如果产生显示器显示问题可以看树莓派与电视之间的显示问题 xff08 1 xff09 下载并写入 下载Ubun
  • ls-remote --tags --heads git://github.com/adobe-webplatform/eve.git

    报错日志 leepandar 64 localhost ant design vue jeecg yarn install yarn install v1 22 19 1 4 x1f50d Resolving packages 2 4 x1
  • python遇到‘\u’开头的unicode编码

    web信息中常会遇到 u4f60 u597d 类型的字符 首先 u 开头就基本表明是跟unicode编码相关的 xff0c u 后的16进制字符串是相应汉字的utf 16编码 python里decode 和encode 为我们提供了解码和编
  • gVim配色和字体选择

    本文简述如何跟换gVim的字体和选择喜欢的配色方案 xff1a 1 下载配色方案 xff1a gVim官网提供了很多配色方案 xff0c 可以根据自己的需要来选择下载 xff0c 本人比较喜欢深色背景系列的 xff0c 所以以下列举一些 x

随机推荐