Programming languages 无空语言的最佳解释
每当程序员抱怨空错误/异常时,总会有人问我们在没有空的情况下做什么 我对选项类型的酷有一些基本的想法,但我没有知识或语言技能来最好地表达它。对以下内容的很好的解释是以普通程序员可以理解的方式编写的,我们可以将其指向哪个人Programming languages 无空语言的最佳解释,programming-languages,functional-programming,null,nullpointerexception,non-nullable,Programming Languages,Functional Programming,Null,Nullpointerexception,Non Nullable,每当程序员抱怨空错误/异常时,总会有人问我们在没有空的情况下做什么 我对选项类型的酷有一些基本的想法,但我没有知识或语言技能来最好地表达它。对以下内容的很好的解释是以普通程序员可以理解的方式编写的,我们可以将其指向哪个人 默认情况下引用/指针为空是不可取的 选项类型如何工作,包括简化检查空情况的策略,例如 模式匹配和 一元理解 替代解决方案,如消息吃掉零 (我错过的其他方面) 让引用/指针在默认情况下为空是不可取的。 我不认为这是空值的主要问题,空值的主要问题是它们可能意味着两件事: 引用
- 默认情况下引用/指针为空是不可取的
- 选项类型如何工作,包括简化检查空情况的策略,例如
- 模式匹配和
- 一元理解
- 替代解决方案,如消息吃掉零
- (我错过的其他方面)
//首先创建选项列表,然后过滤掉所有的None选项类型和
//将所有某些选项类型映射到它们的值。看看类型推断是如何发光的。
让optionList=[Some(1);Some(2);None;Some(3);None]
optionList |>List.choose id//计算结果为[1;2;3]
//下面是一个简单的模式匹配示例
//打印“1;2;无;3;无;”。
//注意在匹配过程中如何从op中提取值
选择者
|>List.iter(函数Some(value)->printf“%i;”value | None->printf“None;”)
但是,在像Java这样的语言中,如果不直接支持选项类型,我们会有如下内容:
//这里我们执行与F#示例中相同的筛选/映射操作。
List optionList=Arrays.asList(新的一些(1),新的一些(2),新的无(),新的一些(3),新的无());
List filteredList=新建ArrayList();
用于(选项op:list)
如果(某些操作实例)
add(((一些)op.getValue());
替代解决方案,如消息吃掉零
Objective-C的“消息吃零”与其说是一个解决方案,不如说是试图减轻空检查带来的头痛。基本上,在尝试调用null对象上的方法时,表达式本身的计算结果为null,而不是引发运行时异常。暂停怀疑,就好像每个实例方法都以
if(this==null)返回null开始代码>。但是还有信息丢失:您不知道该方法返回null是因为它是有效的返回值,还是因为该对象实际上是null。这很像异常吞咽,在解决前面概述的null问题时没有任何进展。汇编为我们带来了地址,也称为非类型指针。C直接将它们映射为类型指针,但引入了Algol的null作为唯一指针值,与所有类型指针兼容。C中null的一个大问题是,由于每个指针都可以为null,因此如果不进行手动检查,就永远无法安全地使用指针
在更高级的语言中,使用null很尴尬,因为它实际上传达了两个截然不同的概念:
- 告诉某人某事是未定义的
- 告诉某人某事是可选的
拥有未定义的变量是非常无用的,并且无论何时发生,都会导致未定义的行为。我想每个人都会同意,应该不惜一切代价避免未定义的东西
第二种情况是可选性,最好明确提供,例如使用
假设我们在一家运输公司,我们需要创建一个应用程序来帮助我们的司机创建时间表。对于每位驾驶员,我们会存储一些信息,例如:他们拥有的驾驶执照和紧急情况下拨打的电话号码
在C中,我们可以:
struct PhoneNumber { ... };
struct MotorbikeLicence { ... };
struct CarLicence { ... };
struct TruckLicence { ... };
struct Driver {
char name[32]; /* Null terminated */
struct PhoneNumber * emergency_phone_number;
struct MotorbikeLicence * motorbike_licence;
struct CarLicence * car_licence;
struct TruckLicence * truck_licence;
};
正如您所观察到的,在对驱动程序列表的任何处理中,我们都必须检查空指针。编译器不会帮助你,程序的安全取决于你的肩膀
在OCaml中,相同的代码如下所示:
type phone_number = { ... }
type motorbike_licence = { ... }
type car_licence = { ... }
type truck_licence = { ... }
type driver = {
name: string;
emergency_phone_number: phone_number option;
motorbike_licence: motorbike_licence option;
car_licence: car_licence option;
truck_licence: truck_licence option;
}
现在让我们假设我们想要打印所有司机的姓名以及他们的卡车牌照号码
在C中:
正如您在这个简单的示例中所看到的,安全版本中没有什么复杂的东西:
- 它更简洁
- 您可以得到更好的保证,并且根本不需要空检查
- 编译器确保您正确处理了该选项
而在C语言中,你可能只是忘记了一个空校验,然后砰的一声
注意:这些代码示例没有编译,但我希望您能理解。我认为关于null为什么不受欢迎的简洁总结是,无意义的状态不应该是可表示的
假设我正在做一个门的模型。它可以处于三种状态之一:打开、关闭但未锁定和关闭并锁定。现在我可以按照
class Door
private bool isShut
private bool isLocked
很清楚如何将我的三个状态映射到这两个布尔变量中。但这留下了第四个不希望出现的状态:isShut==false&&isLocked==true
。因为我选择作为代表的类型
open Printf
(* Here we are guaranteed to have a driver instance *)
let print_driver_with_truck_licence_number driver =
printf "driver %s has " driver.name;
match driver.truck_licence with
| None ->
printf "no truck licence\n"
| Some licence ->
(* Here we are guaranteed to have a licence *)
printf "truck licence %04d-%04d-%08d\n"
licence.area_code
licence.year
licence.num_in_year
(* Here we are guaranteed to have a valid list of drivers *)
let print_drivers_with_truck_licence_numbers drivers =
List.iter print_driver_with_truck_licence_number drivers
class Door
private bool isShut
private bool isLocked
type DoorState =
| Open | ShutAndUnlocked | ShutAndLocked
class Door
private DoorState state
class Person
private string FirstName
private Option<string> MiddleName
private string LastName
let TotalNumCharsInPersonsName(p:Person) =
let middleLen = match p.MiddleName with
| None -> 0
| Some(s) -> s.Length
p.FirstName.Length + middleLen + p.LastName.Length
class Person
private string FirstName
private string MiddleName
private string LastName
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length + p.MiddleName.Length + p.LastName.Length
let TotalNumCharsInPersonsName(p:Person) =
(if p.FirstName=null then 0 else p.FirstName.Length)
+ (if p.MiddleName=null then 0 else p.MiddleName.Length)
+ (if p.LastName=null then 0 else p.LastName.Length)
let TotalNumCharsInPersonsName(p:Person) =
p.FirstName.Length
+ (if p.MiddleName=null then 0 else p.MiddleName.Length)
+ p.LastName.Length
UNIQUE(a, b, id_a)
UNIQUE(a, b, id_b)
def fullNameLength(p:Person) = {
val middleLen =
if (null == p.middleName)
p.middleName.length
else
0
p.firstName.length + middleLen + p.lastName.length
}
def fullNameLength(p:Person) = {
val middleLen = p.middleName match {
case Some(x) => x.length
case _ => 0
}
p.firstName.length + middleLen + p.lastName.length
}
def fullNameLength(p:Person) = {
val middleLen = p.middleName map {_.length} getOrElse 0
p.firstName.length + middleLen + p.lastName.length
}
def fullNameLength(p:Person) =
p.firstName.length +
p.middleName.map{length}.getOrElse(0) +
p.lastName.length
people flatMap(_ find (_.firstName == "joe")) map (fullNameLength)
//convert an Option[List[Person]] to an Option[S]
//where the function f takes a List[Person] and returns an S
people map f
//find a person named "Joe" in a List[Person].
//returns Some[Person], or None if "Joe" isn't in the list
validPeopleList find (_.firstName == "joe")
//returns None if people is None
//Some(None) if people is valid but doesn't contain Joe
//Some[Some[Person]] if Joe is found
people map (_ find (_.firstName == "joe"))
//flatten it to return None if people is None or Joe isn't found
//Some[Person] if Joe is found
people flatMap (_ find (_.firstName == "joe"))
//return Some(length) if the list isn't None and Joe is found
//otherwise return None
people flatMap (_ find (_.firstName == "joe")) map (fullNameLength)