// 980: Map over Async
// Rust: async { f(x.await) } is the idiom for Lwt.map f promise
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
fn block_on<F: Future>(mut fut: F) -> F::Output {
let mut fut = unsafe { Pin::new_unchecked(&mut fut) };
fn noop(_: *const ()) {}
fn clone(p: *const ()) -> RawWaker { RawWaker::new(p, &VT) }
static VT: RawWakerVTable = RawWakerVTable::new(clone, noop, noop, noop);
let waker = unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &VT)) };
let mut cx = Context::from_waker(&waker);
match fut.as_mut().poll(&mut cx) {
Poll::Ready(v) => v,
Poll::Pending => panic!("not ready"),
}
}
// The base future
async fn base_value() -> i32 { 5 }
// --- map: transform the output of a future ---
// Lwt.map (fun x -> x * 2) fut โก async { fut.await * 2 }
async fn map_double(fut: impl Future<Output = i32>) -> i32 {
fut.await * 2
}
async fn map_to_string(fut: impl Future<Output = i32>) -> String {
fut.await.to_string()
}
// --- Functor-style: compose maps ---
async fn map_chain() -> String {
let raw = base_value().await; // 5
let doubled = raw * 2; // 10 (map)
let as_str = doubled.to_string(); // "10" (map)
as_str
}
// --- map derived from bind (async block = bind + return) ---
async fn map_via_bind<T, U, F>(fut: impl Future<Output = T>, f: F) -> U
where
F: FnOnce(T) -> U,
{
// .await is bind, wrapping in async is return
f(fut.await)
}
// --- Functor laws ---
async fn identity_law() -> bool {
let val = base_value().await;
let mapped = async { base_value().await }.await; // map id
val == mapped
}
async fn composition_law() -> bool {
let f = |x: i32| x + 1;
let g = |x: i32| x * 3;
// map (f . g) fut
let composed = async { f(g(base_value().await)) }.await;
// map f (map g fut)
let chained = async { f(async { g(base_value().await) }.await) }.await;
composed == chained
}
fn main() {
let s = block_on(map_chain());
println!("map_chain: {}", s);
let r = block_on(map_via_bind(base_value(), |x| x * x));
println!("map_via_bind (square): {}", r);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_map_double() {
assert_eq!(block_on(map_double(base_value())), 10);
}
#[test]
fn test_map_to_string() {
assert_eq!(block_on(map_to_string(base_value())), "5");
}
#[test]
fn test_map_chain() {
assert_eq!(block_on(map_chain()), "10");
}
#[test]
fn test_map_via_bind() {
assert_eq!(block_on(map_via_bind(base_value(), |x| x * x)), 25);
}
#[test]
fn test_identity_law() {
assert!(block_on(identity_law()));
}
#[test]
fn test_composition_law() {
assert!(block_on(composition_law()));
}
#[test]
fn test_inline_map() {
// Inline Lwt.map style
let result = block_on(async { base_value().await + 100 });
assert_eq!(result, 105);
}
}
(* 980: Map over Async *)
(* OCaml: Lwt.map f promise โ transform a resolved value *)
(* --- Approach 1: map over a simple future thunk --- *)
type 'a future = unit -> 'a
let return_ x : 'a future = fun () -> x
let map f fut = fun () -> f (fut ())
let bind fut k = fun () -> k (fut ()) ()
let run f = f ()
let () =
let fut = return_ 5 in
let doubled = map (fun x -> x * 2) fut in
let stringed = map string_of_int doubled in
assert (run doubled = 10);
assert (run stringed = "10");
Printf.printf "Approach 1 (map chain): %s\n" (run stringed)
(* --- Approach 2: functor laws on future --- *)
(* map id = id, map (f . g) = map f . map g *)
let () =
let fut = return_ 42 in
(* map id law *)
let id_mapped = map (fun x -> x) fut in
assert (run id_mapped = run fut);
(* composition law *)
let f x = x + 1 in
let g x = x * 3 in
let composed = map (fun x -> f (g x)) fut in
let chained = map f (map g fut) in
assert (run composed = run chained);
Printf.printf "Approach 2 (functor laws): โ\n"
(* --- Approach 3: map as derived from bind --- *)
let map_from_bind f fut =
bind fut (fun x -> return_ (f x))
let () =
let fut = return_ 7 in
let r1 = map (fun x -> x * x) fut in
let r2 = map_from_bind (fun x -> x * x) fut in
assert (run r1 = run r2);
Printf.printf "Approach 3 (map via bind): %d = %d\n" (run r1) (run r2)
let () = Printf.printf "โ All tests passed\n"