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函数实现可以支持不同类型的参数比如整数,分数,实数。 当我们尝试让程序接受不同类型,这程序是多态的。

好几种方式可以让程序达到多态

进阶:泛型

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已经糖化泛型的写法。可恶,竟然在我眼皮子底下偷偷加入的。