zoukankan      html  css  js  c++  java
  • [翻译]Rust 模拟高阶类型

    原文地址 https://leshow.github.io/post/cheat_rank_n/

    假设你有一个 enum 类描述一组可能的分支, 有一些函数需要对可能的分支进行处理,
    而对每个分支存在一个对应的类型, 比如

    enum Var {
        One,
        Two,
    }
    
    #[derive(Serialize)]
    struct Foo;
    #[derive(Serialize)]
    struct Bar;
    
    fn write<W>(var: Var, mut writer: W) -> serde_json::Result<()>
    where
        W: Write,
    {
        match var {
            Var::One => serde_json::to_writer(&mut writer, &Foo),
            Var::Two => serde_json::to_writer(&mut writer, &Bar),
        }
    }
    

    这里我们有枚举 Var, 在 write 函数中, 分支 One 对应 Foo 类型, 分支 Two 对应 Bar 类型. 一切都很美好. 但是当需求变更时, 你可能需要以不同的方式输出这些类型, 一个是 to_writer, 另一个是 to_writer_pretty. 你可以再加一个 write_pretty 函数, 但是这样的实现不优雅. 它们之间的唯一区别是 serde_json中的函数:

    fn write<W>(var: Var, mut writer: W) -> serde_json::Result<()>
    where
        W: Write,
    {
        match var {
            Var::One => serde_json::to_writer(&mut writer, &Foo),
            Var::Two => serde_json::to_writer(&mut writer, &Bar),
        }
    }
    
    fn write_pretty<W>(var: Var, mut writer: W) -> serde_json::Result<()>
    where
        W: Write,
    {
        match var {
            Var::One => serde_json::to_writer_pretty(&mut writer, &Foo),
            Var::Two => serde_json::to_writer_pretty(&mut writer, &Bar),
        }
    }
    

    你可能会想到将这两函数抽象出来, 添加一个格式化函数作为参数:

    fn write<W, T, F>(var: Var, mut writer: W, f: F) -> serde_json::Result<()>
    where
        W: Write,
        T: Serialize + ?Sized,
        F: Fn(&mut W, &T) -> serde_json::Result<()>,
    {
        match var {
            Var::One => f(&mut writer, &Foo),
            Var::Two => f(&mut writer, &Bar),
        }
    }
    

    但是这里会有问题, write 函数声明对任意的 T 有效, 而函数 f 的实现需要传实体类型 T, 我们这里分别传了 FooBar 类型, 会产生编译错误:

    error[E0308]: mismatched types
      --> src/lib.rs:23:36
       |
    16 | fn write<W, T, F>(var: Var, mut writer: W, f: F) -> serde_json::Result<()>
       |             - this type parameter
    ...
    23 |         Var::One => f(&mut writer, &Foo),
       |                                    ^^^^ expected type parameter `T`, found struct `Foo`
       |
       = note: expected reference `&T`
                  found reference `&Foo`
       = help: type parameters must be constrained to match other types
       = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
    
    error[E0308]: mismatched types
      --> src/lib.rs:24:36
       |
    16 | fn write<W, T, F>(var: Var, mut writer: W, f: F) -> serde_json::Result<()>
       |             - this type parameter
    ...
    24 |         Var::Two => f(&mut writer, &Bar),
       |                                    ^^^^ expected type parameter `T`, found struct `Bar`
       |
       = note: expected reference `&T`
                  found reference `&Bar`
       = help: type parameters must be constrained to match other types
       = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
    

    这个时候我们就需要高阶类型 (higher ranked types). 如果能将 F 声明成 F: for<T> Fn(&mut W, &T) -> serde_json::Result<()>, 这样就可以将 Twrite 依赖变成依赖 F. 但是现在 Rust 不允许我们这样做.

    解决方案

    类型消除

    通常我们可以通过 trait object 来消除一个类型参数:

    fn write<W, F>(var: Var, mut writer: W, f: F) -> serde_json::Result<()>
    where
        W: Write,
        F: Fn(&mut W, &dyn Serialize) -> serde_json::Result<()>,
    {
        match var {
            Var::One => f(&mut writer, &Foo),
            Var::Two => f(&mut writer, &Bar),
        }
    }
    

    这里的问题是 Serialize 不是 object safe. 如果一个 trait 中有泛型方法, 那么我们就不能将其转成 trait object. Serialize 就是一个这样的 trait.

    使用 erased_serde 这样的库, 可以让我们使用 Box<dyn Serialize> 这样的 trait object.

    使用另一个 Enum

    还可以定义另外一个 enum 来包含各种可能的类型. 不过这种方式并不比使用两个 write/write_pretty 更好.

    enum Foobar<'a> {
        Foo(&'a Foo),
        Bar(&'a Bar)
    }
    
    fn write<W, T, F>(var: Var, mut writer: W, f: F) -> serde_json::Result<()>
    where
        W: Write,
        T: Serialize + ?Sized,
        F: Fn(&mut W, FooBar) -> serde_json::Result<()>,
    {
        match var {
            Var::One => f(&mut writer, Foo(&Foo)),
            Var::Two => f(&mut writer, Bar(&Bar)),
        }
    }
    

    我们必须要在传递的参数 f 中处理新的 enum.

    write(var, writer, |writer, foobar| {
        match foobar {
            Foo(foo) => serde_json::to_writer_pretty(writer, foo),
            Bar(bar) => serde_json::to_writer_pretty(writer, bar),
        }
    })
    // 以及:
    write(var, writer, |writer, foobar| {
        match foobar {
            Foo(foo) => serde_json::to_writer(writer, foo),
            Bar(bar) => serde_json::to_writer(writer, bar),
        }
    })
    

    Rank-2 高阶类型

    Rust 中, 我们可以使用 trait 来解决. 我们可以添加一个如下的 trait:

    trait Format {
        fn format<W, T>(&self, writer: W, val: &T) -> serde_json::Result<()>
        where
            W: Write,
            T: Serialize + ?Sized;
    }
    
    struct Ugly;
    struct Pretty;
    
    impl Format for Ugly {
        fn format<W, T>(&self, writer: W, val: &T) -> serde_json::Result<()>
        where
            W: Write,
            T: Serialize + ?Sized,
        {
            serde_json::to_writer(writer, val)
        }
    }
    
    impl Format for Pretty {
        fn format<W, T>(&self, writer: W, val: &T) -> serde_json::Result<()>
        where
            W: Write,
            T: Serialize + ?Sized,
        {
            serde_json::to_writer_pretty(writer, val)
        }
    }
    

    这时, 我可以将 write 抽象成使用这个 trait:

    fn write<W, F>(var: Var, mut writer: W, format: F) -> serde_json::Result<()>
    where
        W: Write,
        F: Format,
    {
        match var {
            Var::One => format.format(&mut writer, &Foo),
            Var::Two => format.format(&mut writer, &Bar),
        }
    }
    

    可以很方便的使用它:

    write(var, writer, Ugly)?;
    

    因为泛型参数 <T> 不是在 Format 上, 而是在 format 函数上, 我们可以将其看成是二阶类型 for<T> Fn(T). 我认为这是 trait 或者 typeclasses 类型系统一个有趣的地方, 有时候我们可以添加新的类型, 即使该类型不包含任何数据, 即使它们唯一的作用是抽象出一些行为, 然后允许你将这些行为绑定到类型上.

  • 相关阅读:
    Unity的DrawCall
    社交化分享SDK for Unity
    【收藏】75个很有用的开源移动工具
    日积月累--exception记录
    AndroidStudio 编译异常java.lang.OutOfMemoryError: GC overhead limit exceeded
    聊一聊 Android 6.0 的运行时权限
    一个卡片式的ViewPager,带你玩转ViewPager的PageTransformer属性!
    Git查看、删除、重命名远程分支和tag
    移动数据统计平台分析
    手把手教你AndroidStudio多渠道打包
  • 原文地址:https://www.cnblogs.com/wbin91/p/14339396.html
Copyright © 2011-2022 走看看