Rust中的多参数列表

huangapple go评论63阅读模式
英文:

Multiple parameter lists in Rust

问题

我刚开始学习Rust,并且想知道如何最好地转换多个参数列表的模式。

在Scala中,我可以这样定义接受多个参数列表的函数:

def add(n1: Int)(n2: Int) = n1 + n2

例如,可以用于函数专门化:

val incrementer = add(1)
val three = incrementer(2)
val four = incrementer(three)

我最喜欢这个特性的一种用法是逐步构建不可变数据结构。例如,最初的调用者可能不知道所有必需的字段,因此他们可以填写其中一些字段,得到一个闭包,接收其余的字段,然后将其传递给其他人填写剩下的字段,完成构建。

我尝试在Rust中实现这种部分构建模式:

#[derive(Debug)]
#[non_exhaustive]
pub struct Name<'a> {
    pub first: &'a str,
    pub last: &'a str,
}

impl<'a> Name<'a> {
    pub fn new(first: &'a str, last: &'a str) -> Result<Self, &'static str> {
        if first.len() > 0 && last.len() > 0 {
            return Ok(Self { first, last });
        }
        return Err("first and last must not be empty");
    }

    pub fn first(first: &'a str) -> impl Fn(&'a str) -> (Result<Name, &'a str>) {
        |last| Name::new(first, last)
    }

}

但这非常啰嗦,似乎应该有更简单的方法(想象一下有5个字段,我将不得不编写5个函数)。

实质上,我想要类似于这样的东西(伪Rust):

pub fn first(first: &'a str)(last: &'a str) -> Result<Name, &'static str> {
    Name::new(first, last)
}

let takes_last = first("John");
let name = takes_last("Smith").unwrap();

在Rust中有什么最好的方法来使用这种模式呢?

英文:

I've just started learning Rust, and I wonder how best to translate the pattern of multiple parameter lists.

In Scala, I can define functions taking multiple parameter lists as follows:

def add(n1: Int)(n2: Int) = n1 + n2

This can be used, for example, for function specialisation:

val incrementer = add(1)
val three = incrementer(2)
val four = incrementer(three)

One of my favourite uses of this feature is incrementally constructing immutable data structures. For example, where the initial caller might not know all of the required fields, so they can fill some of them, get back a closure taking the rest of the fields, and then pass that along for someone else to fill in the rest, completing construction.

I tried to implement this partial construction pattern in Rust:

#[derive(Debug)]
#[non_exhaustive]
pub struct Name&lt;&#39;a&gt; {
    pub first: &amp;&#39;a str,
    pub last: &amp;&#39;a str,
}

impl&lt;&#39;a&gt; Name&lt;&#39;a&gt; {
    pub fn new(first: &amp;&#39;a str, last: &amp;&#39;a str) -&gt; Result&lt;Self, &amp;&#39;static str&gt; {
        if first.len() &gt; 0 &amp;&amp; last.len() &gt; 0 {
            return Ok(Self { first, last });
        }
        return Err(&quot;first and last must not be empty&quot;);
    }

    pub fn first(first: &amp;&#39;a str) -&gt; impl Fn(&amp;&#39;a str) -&gt; (Result&lt;Name, &amp;&#39;a str&gt;) {
        |last| Name::new(first, last)
    }

}

But it's extremely verbose, and it seems like there should be a much easier way (imagine there were 5 fields, I'd have to write 5 functions).

In essence I would like something like this (pseudo-Rust):

pub fn first(first: &amp;&#39;a str)(last: &amp;&#39;a str) -&gt; Result&lt;Name, &amp;&#39;static str&gt; {
    Name::new(first, last)
}

let takes_last = first(&quot;John&quot;)
let name = takes_last(&quot;Smith&quot;).unwrap()

What is the best way to have this pattern in Rust?

答案1

得分: 2

Chayim在评论中所说,你已经以最佳方式完成了柯里化部分,当然在Rust中,通常只需定义一个接受所有参数的函数,如果要部分应用函数,只需在调用处使用闭包:

fn add(a: i32, b: i32) -&gt; i32 {
    a + b
}

let incrementer = |b| add(1, b);
let three = incrementer(2)
let four = incrementer(three)
英文:

As Chayim said in a comment you did the currying part the best way possible, of course in Rust you'd usually just define the function taking all parameters, if you want to partially apply a function just use a closure at the call site:

fn add(a: i32, b: i32) -&gt; i32 {
    a + b
}

let incrementer = |b| add(1, b);
let three = incrementer(2)
let four = incrementer(three)

答案2

得分: 2

以下是代码的翻译部分:

struct NameBuilder {
    first: Option<String>,
    last: Option<String>,
}

struct Name {
    first: String,
    last: String,
}

impl NameBuilder {
    fn new() -> Self {
        NameBuilder {
            first: None,
            last: None,
        }
    }

    fn with_first(mut self, first: String) -> Result<Self, &'static str> {
        if first.len() == 0 {
            Err("First name cannot be empty")
        } else {
            self.first = Some(first);
            Ok(self)
        }
    }

    fn with_last(mut self, last: String) -> Result<NameBuilder, &'static str> {
        if last.len() == 0 {
            Err("Last name cannot be empty")
        } else {
            self.last = Some(last);
            Ok(self)
        }
    }

    fn to_name(self) -> Result<Name, &'static str> {
        Ok(Name {
            first: self.first.ok_or("Must provide a first name!")?,
            last: self.last.ok_or("Must provide a last name!")?,
        })
    }
}

fn main() -> Result<(), &'static str> {
    let name_builder = NameBuilder::new();
    assert!(name_builder.to_name().is_err());

    let name_builder = NameBuilder::new()
        .with_first("Homer".to_string())?
        .with_last("Simpson".to_string())?;
    let name = name_builder.to_name()?;
    
    assert_eq!(name.first, "Homer");
    assert_eq!(name.last, "Simpson");
    Ok(())
}

希望这有助于您理解代码的含义。

英文:

> One of my favourite uses of this feature is incrementally constructing immutable data structures.

Very well. Rust's ownership rules and type system can be quite awesome for this. Consider your toy example. Here would be one way to implement:

https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=5f823b81c3b2ca2d01e1ffb0d23aff72

struct NameBuilder {
    first: Option&lt;String&gt;,
    last: Option&lt;String&gt;,
}

struct Name {
    first: String,
    last: String,
}

impl NameBuilder {
    fn new() -&gt; Self {
        NameBuilder {
            first: None,
            last: None,
        }
    }

    fn with_first(mut self, first: String) -&gt; Result&lt;Self, &amp;&#39;static str&gt; {
        if first.len() == 0 {
            Err(&quot;First name cannot be empty&quot;)
        } else {
            self.first = Some(first);
            Ok(self)
        }
    }

    fn with_last(mut self, last: String) -&gt; Result&lt;NameBuilder, &amp;&#39;static str&gt; {
        if last.len() == 0 {
            Err(&quot;Last name cannot be empty&quot;)
        } else {
            self.last = Some(last);
            Ok(self)
        }
    }

    fn to_name(self) -&gt; Result&lt;Name, &amp;&#39;static str&gt; {
        Ok(Name {
            first: self.first.ok_or(&quot;Must provide a first name!&quot;)?,
            last: self.last.ok_or(&quot;Must provide a last name!&quot;)?,
        })
    }
}

fn main() -&gt; Result&lt;(), &amp;&#39;static str&gt; {
    let name_builder = NameBuilder::new();
    assert!(name_builder.to_name().is_err());

    let name_builder = NameBuilder::new()
        .with_first(&quot;Homer&quot;.to_string())?
        .with_last(&quot;Simpson&quot;.to_string())?;
    let name = name_builder.to_name()?;
    
    assert_eq!(name.first, &quot;Homer&quot;);
    assert_eq!(name.last, &quot;Simpson&quot;);
    Ok(())
}

This is obviously overkill for what you're doing in your example but can work really nice in situations where there are lots of parameters but where for any given concrete use case you'd only explicitly set a few of them and use default values for the rest.

An added benefit is that you can freely choose the order in which you build it.

In my example I opted for String rather than &amp;&#39;a str mostly so I don't have to type so many awkward &amp;&#39;'s :p

NOTE: Even though the with_first method takes in mut self as argument, we still are dealing with an essentially immutable data structure, because we're just consuming self (i.e. taking ownership). Basically, there's no way that someone would hold a reference and then be surprised by us setting the first name to something else, because you can't move self while someone is still borrowing it.

This is of course not the only way to make a fluent interface. You could also think of a purely functional approach where data is immutable and you don't consume self. Then we're entering "persistent data structures" territory, e.g. https://github.com/orium/rpds

huangapple
  • 本文由 发表于 2023年2月16日 19:05:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/75471352.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定