Juria의 매크로를 Rust로 다시 쓰는 느낌.

36078 단어 Rustmacroidea
Rust로 다시 써봤어요여기.E₁_taylor64.
전체 설치
fn e1_taylor_coefficients(n: isize) -> Vec<f64> {
  use natural_constants::math::euler_mascheroni;
  if n < 0 {
    panic!("n ≥ 0 is required");
  }
  if n == 0 {
    return vec![];
  }
  if n == 1 {
    return vec![-euler_mascheroni];
  }
  let mut term = 1.0;
  let mut terms = vec![-euler_mascheroni, term];
  for k in 2..=n {
    let k = k as f64;
    term = -term * (k - 1.0) / (k * k);
    terms.push(term);
  }
  terms
}

fn evalpoly64_impl(ident: proc_macro2::Ident, coefficients: &[f64]) -> proc_macro2::TokenStream {
  use quote::quote;
  let mut code = quote::quote! { 0.0 };
  let mut cs = coefficients.iter().copied().collect::<Vec<_>>();
  cs.reverse();
  for (i, c) in cs.into_iter().enumerate() {
    code = if 0 == i {
      quote! { #c as f64 }
    } else {
      quote! { #c as f64 + #ident * (#code) }
    }
  }
  quote::quote! {{ let #ident = #ident as f64; #code }}
}

#[proc_macro]
pub fn e1_taylor64(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
  use proc_macro2::{
    TokenStream,
    TokenTree::{Ident, Literal, Punct},
  };
  let input = TokenStream::from(input);
  let vec = input.into_iter().collect::<Vec<_>>();
  let (z, n) = match &vec[..] {
    [Ident(z), Punct(_), Literal(n)] => (z, n.to_string().parse::<isize>().unwrap()),
    _ => panic!("e1_taylor64 requires (ident, isize). eg let z = 2f64; e1_taylor64!(z, 12)"),
  };
  let c = e1_taylor_coefficients(n);
  let ident = z.to_owned();
  let pl = evalpoly64_impl(ident.clone(), &c);
  let gen = quote::quote! {{
    let t = #ident as f64;
    let pl = #pl;
    pl - t.log(std::f64::consts::E)
  }};
  gen.into()
}
주리아가 뭐예요?
최근 수치 계산계 근처에서 주목받고 있는 것 같다.[1]
파이썬과 같은 동적 프로그래밍 언어는 C의 절반 정도의 우수한 속도가 특징이다.
LLVM을 사용한 것 같습니다. 실행 파일과 공유 라이브러리로 컴파일할 수 있습니다.
Scheme처럼 건강하고 강력한 매크로를 사용할 수 있습니다.

Rust로 쓸 때 겪는 어려움


정의할 수 있는 곳에 제한이 있어요.


수속형 매크로는 fn(TokenStream) -> TokenStream의 형식이다
정의하려면 프로그램 매크로만 공개하는 프로그램 라이브러리에 있어야 합니다.
결국 파일을 분할해야 한다는 얘기다.

eval 없음


Rust에 eval이 없음(REPL도 없음)
부분적으로 eval이 매개 변수를 만들려고 하는 Token Stream의 상황은 난감하다.
예를 들어, 고려evalpoly64의 매크로입니다.
이것은 변수와 여러 정수를 바탕으로 코드를 생성하는 것이다.
let num = evalpoly64!(x, [1.0, 2.0, 3.0, 4.0]);
\begin{aligned}
evalpoly(x, [1, 2, 3, 4]) &= x^3 + 2x^2 + 3x + 4\\\\
&= 4 + x (3 + x (2 + x))
\end{aligned}
전개 후
let num = 4.0 + x * (3.0 + x * (2.0 + x * 1.0));
마크로입니다.rules로 정의할 수도 있습니다.
macro_rules! evalpoly64 {
  ( $i: ident ) => {
    0.0
  };
  ( $i: ident, [ $x:expr ] ) => {
    $x
  };
  ( $i: ident, [$h:expr, $( $x:expr ),+] ) => {
    $h + $i * (evalpoly64!($i, [$($x),*]))
  };
}
이거 수속형 매크로로 쓸게요.
fn evalpoly64_impl(ident: proc_macro2::Ident, coefficients: &[f64]) -> proc_macro2::TokenStream {
  use quote::quote;
  let mut code = quote::quote! { 0.0 };
  let mut cs = coefficients.iter().copied().collect::<Vec<_>>();
  cs.reverse();
  for (i, c) in cs.into_iter().enumerate() {
    code = if 0 == i {
      quote! { #c as f64 }
    } else {
      quote! { #c as f64 + #ident * (#code) }
    }
  }
  quote::quote! {{ let #ident = #ident as f64; #code }}
}

#[proc_macro]
pub fn evalpoly64(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
  use proc_macro2::{
    TokenStream,
    TokenTree::{Group, Ident, Literal, Punct},
  };
  let message = "evalpoly64 requires (ident, [f64]). eg let z = 2f64; evalpoly64!(z, [1, 2, 3])";
  let input = TokenStream::from(input);
  let vec = input.into_iter().collect::<Vec<_>>();
  let (z, n) = match &vec[..] {
    [Ident(z), Punct(_), Group(a)] => (z, a),
    [Ident(z), Punct(_), Group(a), Punct(_)] => (z, a),
    _ => panic!(message),
  };
  let ident = z.to_owned();
  let mut list: Vec<f64> = vec![];
  let mut lit = true;
  for t in n.stream() {
    if lit {
      match t {
        Literal(l) => list.push(l.to_string().parse().unwrap()),
        _ => panic!(message),
      }
    } else {
      match t {
        Punct(_) => {}
        _ => panic!(message),
      }
    }
    lit = !lit;
  }
  let gen = evalpoly64_impl(ident, &list);
  gen.into()
}
길다.
하고 싶은 게 evalpoly64_impl라는 내용이에요.TokenStreamIdent&[f64]를 얻기 위해서는 비교적 긴 코드가 필요하다.
프로그램형 매크로 안에서 매크로를 펼쳐 Token Stream을 얻으려 했으나 포기하고 함수를 실행한 경우도 있었다.
(상기 evalpoly64를 펼칠 수 없기 때문에 evalpoly64_impl 개작하였습니다)

총결산


eval이 있으면 쓰기 쉬울 것 같아요.
매크로를 쓰지 않아도 최적화를 통해 같은 코드를 만들 수 있는 사람은 루스트와 비슷하다고 생각합니다.
각주
https://muuuminsan.hatenablog.com/entry/2020/10/08/021903 ↩︎

좋은 웹페이지 즐겨찾기