date: 2024-10-30
类型应用入门一
求json的类型
3
number (either integer or float)
true
boolean
'patchouli'
string
null
null
[11,13,17]
array
{
'甲' : 1,
'乙' : 2,
'丙' : 3
}
javascript object (or named-value pair, dictionary, map)
至少有六个可以是描述json的类型。我们可以使用Union
(联合)来描述多个类型。python
的写法是
json_ty = int|float|bool|None|str|list|dict
Options
有些函数实现可以要么返回空值以表示没有,要么是结果。可以描述其类型为T|None
例子,
def copy_file(input_path: str, output_path: str|None = None) -> None:
'''
Example Usage
---
```
copy_file('foo.txt', 'bar.txt')
copy_file('foo.txt') ==> copy_file('foo.txt','foo_copy.txt')
```
'''
import os
if output_path is None:
wkdir, file_name = os.path.split(input_path)
file_stem, file_ext = os.path.splitext(file_name)
output_path = os.path.join(wkdir, file_stem + '_copy' + file_ext)
with open(input_path, 'rb') as inp:
with open(output_path, 'wb+') as out:
out.write(inp.read())
因为太常用,python
也提供Optional
的写法
from typing import Optional
def copy_file(input_path: str, output_path: Optional[str] = None) -> None:
'''
Example Usage
---
```
copy_file('foo.txt', 'bar.txt')
copy_file('foo.txt') ==> copy_file('foo.txt','foo_copy.txt')
```
'''
import os
if output_path is None:
wkdir, file_name = os.path.split(input_path)
file_stem, file_ext = os.path.splitext(file_name)
output_path = os.path.join(wkdir, file_stem + '_copy' + file_ext)
with open(input_path, 'rb') as inp:
with open(output_path, 'wb+') as out:
out.write(inp.read())
多态
def max(x: int|float, y: int|float) -> int|float:
if x < y:
return y
else:
return x
以上max
函数会比较两个数,返还较大的数。
max
函数实现可以支持不同类型的参数比如整数,分数,实数。
当我们尝试让程序接受不同类型,这程序是多态的。
好几种方式可以让程序达到多态
- 子类多态 (例: 继承)
- 参数多态 (例: 泛型)
- 函数分派 (例: Julia dynamic dispatch, C++ overload resolution)
- 鸭子类型 (例: Python,
那么可爱肯定是男孩子) - 变体类型
进阶:泛型
from typing import Callable
def compose(f: Callable[[int], int], g: Callable[[int], int]) -> Callable[[int], int]:
def wrapped(x: int) -> int:
return f(g(x))
return wrapped
def sqr(x: int) -> int:
return x * x
f = compose(sqr,sqr)
print(f(2)) # 16
如上,我们实现compose
可以复合两个函数成一个函数(如同复合函数)。
以上compose
只给整数函数定义,但compose
应该是通用,接受其他类型比如
str.strip : ((str) -> str)
str.__len__ : ((str) -> int)
---
compose(str.strip, str.__len__) : ((str) -> int)
sqr : ((int) -> int)
---
compose(compose(str.strip, str.len),str.__len__) : ((str) -> int)
为了可以描述这种通用性,我们可以引入泛型的概念,如同以类型代入变量那样。在python3.12之后,可以这么写。
from typing import Callable
def identity[X](x: X) -> X:
return x
def compose[X, Y, Z](f: Callable[[X], Y], g: Callable[[Y], Z]) -> Callable[[X], Z]:
def wrapped(x: X) -> Z:
return f(g(x))
return wrapped
def sqr(x: int) -> int:
return x * x
x: int = identity(10)
y: str = identity('s')
f: Callable[[int], int] = compose(sqr,sqr)
g: Callable[[str], int] = compose(str.strip, str.__len__)
h: Callable[[str], int] = compose(compose(str.strip, str.__len__),sqr)
H: Callable[[str], int] = compose(compose(str.strip, str.__len__),compose(sqr,identity))
进阶:递归类型
刚才json的类型描述还是不够精确。 javascript object是一种关联表,键类型必须是string但是值可以任何json类型。 数组的元素可以是任何json类型。比如
{
'jan' : 1,
'feb' : 2,
'mac' : '三月',
'foo' : false,
'bar' : null,
'哦,卖糕的' : {
'甲' : 1,
'乙' : 2,
'丙' : 3
},
'basic' : [1,true,false,null]
}
在python
里,可以写成
type json_ty = None|bool|int|float|str|list[json_ty]|dict[str,json_ty]
https://ocaml.org/docs/basic-data-types#recursive-variants
ocaml
教程写法
type json =
| Null
| Bool of bool
| Int of int
| Float of float
| String of string
| Array of json list
| Object of (string * json) list;;
外传:关于继承也是一种代数数据类型
The First Bit of Advice
When specifying a collection of data, use abstract classes for data types and extended classes for variants.
摘之The Little Java: Few Patterns
比如以下是haskell
风格的ADT写法
data Peano = Zero | Succ Peano
Succ (Succ (Succ Zero)) :: Peano
在java
, 可以写成:
abstract class Peano {}
...
class Zero extends Num {}
...
class Succ extends Num {}
...
Succ(Succ(Zero)) // also an instance of Peano number
后日谈
写这笔记的时候,发现python已经糖化泛型的写法。可恶,竟然在我眼皮子底下偷偷加入的。