Open14

Rustの書き方メモ

Yos_KYos_K

カリー化

closureを返す関数を作る

fn add(x: isize) -> Box<dyn Fn(isize) -> isize> {
    Box::new(move |y| x + y)
} 

fn main() {
    let a = add(1);
    let b = a(2);
    println!("{b}");
}

Fnはトレイトのため、型として記述する際はBoxで包む必要がある

Yos_KYos_K

これでもOK

fn add(x: isize) -> impl Fn(isize) -> isize {
    move |y| x + y
} 

fn main() {
    let a = add(1);
    let b = a(2);
    println!("{b}");
}
Yos_KYos_K

でもこの書き方だと引数を1つしか受け取れないので微妙と言えば微妙
(2つの引数を与えて和を出したいし、部分適用して関数を受け取るというようなこともしたい)
まぁ、add(1)(2)のように書けば和が求められるからできるといえばできるのか・・・

Yos_KYos_K

再帰関数

match式を使って基底部と再帰部を定義する

fn fac(n: usize) -> usize {
    match n {
        0 => 1,
        _ => n * fac(n-1),
    }
}

fn main() {
    let a = 0;
    let b = 3;
    println!("{}", fac(a));
    println!("{}", fac(b));
}
Yos_KYos_K

多重再帰

フィボナッチ数列のn番目の数を求める関数

fn fib(n: usize) -> usize {
    match n {
        0 => 0,
        1 => 1,
        _ => fib(n-2) + fib(n-1),
    }
}

fn main() {
    println!("{}", fib(6));
}
Yos_KYos_K

相互再帰

整数nの偶奇判定

fn even(n: usize) -> bool {
    match n {
        0 => true,
        _ => odd(n-1),
    }
}

fn odd(n: usize) -> bool {
    match n {
        0 => false,
        _ => even(n-1),
    }
}

fn main() {
    println!("{}", even(6));
    println!("{}", even(3));
    println!("{}", odd(5));
    println!("{}", odd(4))
}
Yos_KYos_K

関数合成

pub fn compose<A, B, C, F, G>(
    f: F,
    g: G,
) -> impl Fn(A) -> C
where
    F: Fn(B) -> C,
    G: Fn(A) -> B,
{
    move |x| f(g(x))
}

fn main() {
    let multiply_and_add = compose(|x| x * 2, |x| x + 2);
    println!("{}", multiply_and_add(1));
}
Yos_KYos_K

Haskellの

inc :: [Int] -> [Int]
inc [] = []
inc (n:ns) = n+1 : inc ns

をRustで書くと

fn inc(x: &Vec<isize>) -> Vec<isize> {
    match x.as_slice() {
        [] => vec![],
        [head, rest @ ..] => {
            let mut result = vec![head+1];
            result.append(&mut inc(&rest.to_vec()));
            result
        },
    }
}

もう少しスッキリした書き方はできないものか

Yos_KYos_K

この関数部分(+1のとこ)を抽象化するとmapになるらしい

fn map<A, B, F>(f: F, x: &Vec<A>) -> Vec<B>
where
    A: Clone,
    F: Fn(&A) -> B,
{
    match x.as_slice() {
        [] => vec![],
        [head, rest @ ..] => {
            let mut result = vec![f(head)];
            result.append(&mut map(f, &rest.to_vec()));
            result
        }
    }
}

この書き方が正しいかはちょっと自信ない・・・

Yos_KYos_K

このような、データ構造の中の各要素に関数を適用するmap関数を提供する型クラス(Rustだとトレイト?)を関手(functor)と呼ぶらしい

Yos_KYos_K

関手(Functor)

pub trait Functor {
    type A;
    type Wrapped<B>: Functor;

    fn fmap<F, B>(self, f: F) -> Self::Wrapped<B>
    where
        // ここのトレイト境界、人によってFnOnceにしていたりFnMutにしていたりする
        F: Fn(Self::A) -> B;
}

Fのトレイト境界はどうするべきだろう(FnMutにした方が汎用性は高い?)

Yos_KYos_K

実装はこんな感じ?

enum Maybe<A> {
    Just(A),
    Nothing,
}

impl<A> Functor for Maybe<A> {
    type A = A;
    type Wrapped<B> = Maybe<B>;
    fn fmap<F, B>(self, f: F) -> Self::Wrapped<B>
        where
            F: Fn(Self::A) -> B {
        match self {
            Maybe::Just(x) => Maybe::Just(f(x)),
            Maybe::Nothing => Maybe::Nothing,
        }
    }
}