074 — Bifunctor
Tutorial Video
Text description (accessibility)
This video demonstrates the "074 — Bifunctor" functional Rust example. Difficulty level: Advanced. Key concepts covered: Functional Programming. A bifunctor is a type with two type parameters where both can be independently mapped. Key difference from OCaml: 1. **No stdlib trait**: Neither Rust nor OCaml has a standard `Bifunctor` trait/typeclass. Implement the functions per type. Haskell has `Data.Bifunctor`.
Tutorial
The Problem
A bifunctor is a type with two type parameters where both can be independently mapped. Result<T, E> is the canonical bifunctor: map transforms the T (success side), map_err transforms the E (error side), and bimap applies both transformations simultaneously.
Other bifunctors: tuples (A, B) where both elements can be transformed independently, and Either<L, R> (common in Haskell and Scala). Bifunctors formalize the observation that both sides of a sum type or product type are mappable channels.
This generalizes Result::map + Result::map_err into a single unified concept. Used in category theory, functional programming libraries (Haskell's Data.Bifunctor, Scala's cats.Bifunctor), and error-type transformation pipelines where you need to convert both success and error types simultaneously.
🎯 Learning Outcomes
bimap(f, g) that transforms both sides of a Resultfirst(f) and second(g) as single-side maps(A, B) as a bifunctor: bimap(f, g) on a pairbimap generalizes map and map_err into a unified interfaceCode Example
#![allow(clippy::all)]
// Stub — awaiting conversion from OCaml source.Key Differences
Bifunctor trait/typeclass. Implement the functions per type. Haskell has Data.Bifunctor.map as first**: Rust's Result::map is exactly bifunctor::first — it maps the left (Ok) side. map_err is bifunctor::second. Recognizing this shows Result's functor structure.(A, B) maps to (f(A), g(B)). Rust: let (new_a, new_b) = (f(a), g(b)). This is the product type bifunctor.bimap id id = id, bimap (f . g) (h . k) = bimap f h . bimap g k. These ensure bimap preserves function composition.OCaml Approach
OCaml's Result is a bifunctor with bimap defined by pattern matching:
let bimap f g = function
| Ok x -> Ok (f x)
| Error e -> Error (g e)
(* first = map on Ok side *)
let first f r = bimap f Fun.id r
(* second = map on Error side *)
let second g r = bimap Fun.id g r
(* pair bifunctor *)
let bimap_pair f g (a, b) = (f a, g b)
Result.map f r is exactly first f r. Result.map_error g r is second g r. The Bifunctor typeclass from Haskell is not part of OCaml stdlib but is trivially implemented per-type.
Full Source
#![allow(clippy::all)]
// Stub — awaiting conversion from OCaml source.Deep Comparison
OCaml vs Rust: Bifunctor
Overview
See the example.rs and example.ml files for detailed implementations.
Key Differences
| Aspect | OCaml | Rust |
|---|---|---|
| Type system | Hindley-Milner | Ownership + traits |
| Memory | GC | Zero-cost abstractions |
| Mutability | Explicit ref | mut keyword |
| Error handling | Option/Result | Result<T, E> |
See README.md for detailed comparison.
Exercises
enum Either<L, R> { Left(L), Right(R) }. Implement bimap, map_left, and map_right. Implement From<Result<R, L>> for Either<L, R>.dimap(f: B -> A, g: C -> D) -> impl Fn(A -> C) -> impl Fn(B -> D) for functions. This is the foundation of optics (lenses).bi_traverse(v: Result<T, E>, f: impl Fn(T) -> Option<T2>, g: impl Fn(E) -> Option<E2>) -> Option<Result<T2, E2>> that transforms both sides optionally.