Skip to content

Commit

Permalink
Add unit test of comptime runDo for pure Monad
Browse files Browse the repository at this point in the history
  • Loading branch information
flyfish30 committed Sep 19, 2024
1 parent c0d3bae commit 983144c
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 29 deletions.
5 changes: 3 additions & 2 deletions src/array_list_monad.zig
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ test "runDo Arraylist" {
const ArrayListMonad = Monad(ArrayListMonadImpl);
const arraylist_m = ArrayListMonad.init(.{ .allocator = allocator });
const do_ctx = struct {
// In impure Monad, it is must to define monad_impl for support do syntax.
monad_impl: ArrayListMonadImpl,
param1: i32,

Expand All @@ -323,8 +324,8 @@ test "runDo Arraylist" {
b: u32 = undefined,

const Ctx = @This();
// the do context struct must has init function
pub fn init(impl: *ArrayListMonadImpl) ArrayListMonadImpl.MbType(i32) {
// the do context struct must has startDo function
pub fn startDo(impl: *ArrayListMonadImpl) ArrayListMonadImpl.MbType(i32) {
const ctx: *Ctx = @alignCast(@fieldParentPtr("monad_impl", impl));
const as = &([_]i32{ 17, ctx.param1 } ** 2);
var array = ArrayList(i32).init(impl.allocator);
Expand Down
5 changes: 3 additions & 2 deletions src/maybe.zig
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ test "runDo Maybe" {
const MaybeMonad = Monad(MaybeMonadImpl);
const maybe_m = MaybeMonad.init(.{ .none = {} });
const do_ctx = struct {
// In impure Monad, it is must to define monad_impl for support do syntax.
monad_impl: MaybeMonadImpl,
param1: i32,

Expand All @@ -278,8 +279,8 @@ test "runDo Maybe" {
b: u32 = undefined,

const Ctx = @This();
// the do context struct must has init function
pub fn init(impl: *MaybeMonadImpl) MaybeMonadImpl.MbType(i32) {
// the do context struct must has startDo function
pub fn startDo(impl: *MaybeMonadImpl) MaybeMonadImpl.MbType(i32) {
const ctx: *Ctx = @alignCast(@fieldParentPtr("monad_impl", impl));
return ctx.param1;
}
Expand Down
26 changes: 13 additions & 13 deletions src/monad.zig
Original file line number Diff line number Diff line change
Expand Up @@ -90,24 +90,24 @@ pub fn runDo(ctx: anytype) DoRetType(@TypeOf(ctx)) {
}
}

if (!@hasDecl(CtxType, "init")) {
@compileError("The ctx for runDo must has init function!");
if (!@hasDecl(CtxType, "startDo")) {
@compileError("The ctx for runDo must has startDo function!");
}

const MonadType = if (is_pure) CtxType.MonadType else void;
const ImplType = if (is_pure) MonadType.InstanceImpl else @TypeOf(ctx.monad_impl);
// the first do step function
const init_fn_info = @typeInfo(@TypeOf(@field(CtxType, "init")));
if (init_fn_info.Fn.params.len != 1) {
const start_fn_info = @typeInfo(@TypeOf(@field(CtxType, "startDo")));
if (start_fn_info.Fn.params.len != 1) {
@compileError("The first do step function must be only one parameters!");
}
const init_m = if (is_pure)
const start_m = if (is_pure)
// pure monad
CtxType.init(@constCast(&ctx))
CtxType.startDo(@constCast(&ctx))
else
// impure monad
try CtxType.init(@constCast(&ctx.monad_impl));
const MT = @TypeOf(init_m);
try CtxType.startDo(@constCast(&ctx.monad_impl));
const MT = @TypeOf(start_m);
const has_err, const _MT = comptime isErrorUnionOrVal(MT);
_ = has_err;
const T = ImplType.BaseType(_MT);
Expand All @@ -116,7 +116,7 @@ pub fn runDo(ctx: anytype) DoRetType(@TypeOf(ctx)) {
comptime var i = info.Struct.decls.len;
comptime var isLastDoFn = true;
const ImplOrCtxType = MonadImplOrCtxType(is_pure, CtxType);
// A composed continuation of do step functions for bind init_m
// A composed continuation of do step functions for bind start_m
comptime var k: ?*const fn (*ImplOrCtxType, T) MR = null;
inline while (i > 0) : (i -= 1) {
const decl = info.Struct.decls[i - 1];
Expand Down Expand Up @@ -145,15 +145,15 @@ pub fn runDo(ctx: anytype) DoRetType(@TypeOf(ctx)) {
const R = ImplType.BaseType(_MR);
if (k) |_k| {
// free intermediate monad for avoid memory leak
defer ImplType.deinitFa(init_m, base.getFreeNothing(T));
defer ImplType.deinitFa(start_m, base.getFreeNothing(T));
const final_k: *const fn (*ImplOrCtxType, T) MR = @ptrCast(_k);
if (is_pure) {
return MonadType.bindWithCtx(T, R, @constCast(&ctx), init_m, final_k);
return MonadType.bindWithCtx(T, R, @constCast(&ctx), start_m, final_k);
} else {
return try @constCast(&ctx.monad_impl).bind(T, R, init_m, final_k);
return try @constCast(&ctx.monad_impl).bind(T, R, start_m, final_k);
}
} else {
return init_m;
return start_m;
}
}

Expand Down
111 changes: 104 additions & 7 deletions src/pure/array_monad.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const Monad = monad.Monad;

const impure_functor = @import("../functor.zig");
const FunctorFxTypes = impure_functor.FunctorFxTypes;
const impure_monad = @import("../monad.zig");
const runDo = impure_monad.runDo;

const Maybe = base.Maybe;
const Array = base.Array;
Expand All @@ -47,6 +49,8 @@ pub fn ArrayMonadImpl(comptime len: usize) type {
pub const FaLamType = FxTypes.FaLamType;
pub const FbLamType = FxTypes.FbLamType;

pub const MbType = F;

fn FaFnOrLamType(
comptime K: MapFnKind,
comptime M: FMapMode,
Expand Down Expand Up @@ -244,9 +248,9 @@ pub fn ArrayMonadImpl(comptime len: usize) type {
ctx: anytype,
// monad function: (a -> M b), ma: M a
ma: F(A),
k_ctx: *const fn (@TypeOf(ctx), A) F(B),
k: *const fn (@TypeOf(ctx), A) F(B),
) F(B) {
return bindGeneric(A, B, @TypeOf(ctx), ctx, ma, k_ctx);
return bindGeneric(A, B, @TypeOf(ctx), ctx, ma, k);
}

pub fn bindGeneric(
Expand All @@ -256,19 +260,19 @@ pub fn ArrayMonadImpl(comptime len: usize) type {
ctx: Ctx,
// monad function: (a -> M b), ma: M a
ma: F(A),
k_fn_ctx: if (Ctx == void) *const fn (A) F(B) else *const fn (Ctx, A) F(B),
k: if (Ctx == void) *const fn (A) F(B) else *const fn (Ctx, A) F(B),
) F(B) {
const imap_lam = struct {
cont_ctx: Ctx,
cont_fn_ctx: @TypeOf(k_fn_ctx),
cont_fn: @TypeOf(k),
fn call(map_self: @This(), i: usize, a: A) B {
if (Ctx == void) {
return map_self.cont_fn_ctx(a)[i];
return map_self.cont_fn(a)[i];
} else {
return map_self.cont_fn_ctx(map_self.cont_ctx, a)[i];
return map_self.cont_fn(map_self.cont_ctx, a)[i];
}
}
}{ .cont_ctx = ctx, .cont_fn_ctx = k_fn_ctx };
}{ .cont_ctx = ctx, .cont_fn = k };

return imap(A, B, imap_lam, ma);
}
Expand Down Expand Up @@ -359,3 +363,96 @@ test "Array Monad bind" {
}.f);
try testing.expectEqualSlices(f64, &[_]f64{ 130.74, 189.14, 392.22, 375.14 }, &array_c);
}

test "runDo Array" {
const input1: i32 = 42;

const ArrayMonad = Monad(ArrayMonadImpl(ARRAY_LEN));
const do_ctx = struct {
// In pure Monad, it is must to define MonadType for support do syntax.
pub const MonadType = ArrayMonad;

param1: i32,

// intermediate variable
a: i32 = undefined,
b: u32 = undefined,

const Ctx = @This();
// the do context struct must has startDo function
pub fn startDo(ctx: *Ctx) ArrayMonad.InstanceImpl.MbType(i32) {
const array = [_]i32{ 17, ctx.param1 } ** 2;
return array;
}

// the name of other do step function must be starts with "do" string
pub fn do1(ctx: *Ctx, a: i32) ArrayMonad.InstanceImpl.MbType(u32) {
ctx.a = a;
const tmp = @abs(a);
return [_]u32{ tmp + 2, tmp * 2, tmp * 3, tmp + 4 };
}

// the name of other do step function must be starts with "do" string
pub fn do2(ctx: *Ctx, b: u32) ArrayMonad.InstanceImpl.MbType(f64) {
ctx.b = b;

const array = [_]f64{
@floatFromInt(@abs(ctx.a) + b),
@as(f64, @floatFromInt(b)) + 3.14,
@floatFromInt(@abs(ctx.a) * b),
@as(f64, @floatFromInt(b)) * 3.14,
};
return array;
}
}{ .param1 = input1 };
const out = runDo(do_ctx);
try testing.expectEqualSlices(
f64,
&[_]f64{ 36, 87.14, 867, 144.44 },
&out,
);
}

test "comptime runDo Array" {
const input1: i32 = 42;

const ArrayMonad = Monad(ArrayMonadImpl(ARRAY_LEN));
const do_ctx = struct {
// In pure Monad, it is must to define MonadType for support do syntax.
pub const MonadType = ArrayMonad;

param1: i32,

const Ctx = @This();
// the do context struct must has startDo function
pub fn startDo(ctx: *Ctx) ArrayMonad.InstanceImpl.MbType(i32) {
const array = [_]i32{ 17, ctx.param1 } ** 2;
return array;
}

// the name of other do step function must be starts with "do" string
pub fn do1(ctx: *Ctx, a: i32) ArrayMonad.InstanceImpl.MbType(u32) {
_ = ctx;
const tmp = @abs(a);
return [_]u32{ tmp + 2, tmp * 2, tmp * 3, tmp + 4 };
}

// the name of other do step function must be starts with "do" string
pub fn do2(ctx: *Ctx, b: u32) ArrayMonad.InstanceImpl.MbType(f64) {
_ = ctx;
const array = [_]f64{
@floatFromInt(17 + b),
@as(f64, @floatFromInt(b)) + 3.14,
@floatFromInt(17 * b),
@as(f64, @floatFromInt(b)) * 3.14,
};
return array;
}
}{ .param1 = input1 };
const out = comptime runDo(do_ctx);
try testing.expectEqualSlices(
f64,
&[_]f64{ 36, 87.14, 867, 144.44 },
&out,
);
}
43 changes: 38 additions & 5 deletions src/pure/maybe.zig
Original file line number Diff line number Diff line change
Expand Up @@ -266,17 +266,18 @@ test "runDo Maybe" {

const MaybeMonad = Monad(MaybeMonadImpl);
const do_ctx = struct {
// In pure Monad, it is must to define MonadType for support do syntax.
pub const MonadType = MaybeMonad;

param1: i32,

// intermediate variable
a: i32 = undefined,
b: u32 = undefined,

const Ctx = @This();
pub const MonadType = MaybeMonad;

// the do context struct must has init function
pub fn init(ctx: *Ctx) MaybeMonadImpl.MbType(i32) {
// the do context struct must has startDo function
pub fn startDo(ctx: *Ctx) MaybeMonadImpl.MbType(i32) {
return ctx.param1;
}

Expand All @@ -289,9 +290,41 @@ test "runDo Maybe" {
// the name of other do step function must be starts with "do" string
pub fn do2(ctx: *Ctx, b: u32) MaybeMonadImpl.MbType(f64) {
ctx.b = b;
return @as(f64, @floatFromInt(b)) + 3.14;
return @as(f64, @floatFromInt(b + @abs(ctx.a))) + 3.14;
}
}{ .param1 = input1 };
const out = runDo(do_ctx);
try testing.expectEqual(89.14, out);
}

test "comptime runDo Maybe" {
const input1: i32 = 42;

const MaybeMonad = Monad(MaybeMonadImpl);
const do_ctx = struct {
// In pure Monad, it is must to define MonadType for support do syntax.
pub const MonadType = MaybeMonad;

param1: i32,

const Ctx = @This();
// the do context struct must has startDo function
pub fn startDo(ctx: *Ctx) MaybeMonadImpl.MbType(i32) {
return ctx.param1;
}

// the name of other do step function must be starts with "do" string
pub fn do1(ctx: *Ctx, a: i32) MaybeMonadImpl.MbType(u32) {
_ = ctx;
return @abs(a) + 2;
}

// the name of other do step function must be starts with "do" string
pub fn do2(ctx: *Ctx, b: u32) MaybeMonadImpl.MbType(f64) {
_ = ctx;
return @as(f64, @floatFromInt(b)) + 3.14;
}
}{ .param1 = input1 };
const out = comptime runDo(do_ctx);
try testing.expectEqual(47.14, out);
}

0 comments on commit 983144c

Please sign in to comment.