From 659dcc28cedcaee485c5c583a2a4a24000e5ae89 Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Thu, 15 Feb 2018 18:23:50 +0100 Subject: [PATCH 1/7] Implement bind for Nullable, in analogy to Haskell's monad operation --- std/typecons.d | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/std/typecons.d b/std/typecons.d index 32d06356f9d..17406154459 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -3353,6 +3353,77 @@ auto nullable(alias nullValue, T)(T t) assert(ntts.to!string() == "2.5"); } +/** +When called on a $(D Nullable), $(D bind) will unpack the value contained in the $(D Nullable), +pass it to the function you provide and wrap the result in another $(D Nullable) (if necessary). +If the Nullable is null, $(D bind) will return null itself. + */ +template bind(alias fun) +{ + import std.functional : unaryFun; + + auto bind(T)(T t) + if (isInstanceOf!(Nullable, T) && is(typeof(unaryFun!fun(T.init.get)))) + { + alias FunType = typeof(unaryFun!fun(T.init.get)); + + enum MustWrapReturn = !isInstanceOf!(Nullable, FunType); + + static if (MustWrapReturn) + { + alias ReturnType = Nullable!FunType; + } + else + { + alias ReturnType = FunType; + } + + if (!t.isNull) + { + static if (MustWrapReturn) + { + return fun(t.get).nullable; + } + else + { + return fun(t.get); + } + } + else + { + return ReturnType.init; + } + } +} + +/// +@safe unittest +{ + alias toFloat = Alias!(i => cast(float) i); + + Nullable!int sample; + auto f = sample.bind!toFloat; + assert(sample.isNull && f.isNull); + + sample = 3; + f = sample.bind!toFloat; + assert(!sample.isNull && !f.isNull); + + alias greaterThree = Alias!(i => (i > 3) ? i.nullable : Nullable!(typeof(i)).init); + + sample.nullify; + auto result = sample.bind!greaterThree; + assert(sample.isNull && result.isNull); + + sample = 3; + result = sample.bind!greaterThree; + assert(!sample.isNull && result.isNull); + + sample = 4; + result = sample.bind!greaterThree; + assert(!sample.isNull && !result.isNull); +} + /** Just like $(D Nullable!T), except that the object refers to a value From cbf791718fa00fad003f670361384281758c4467 Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Thu, 15 Feb 2018 18:36:40 +0100 Subject: [PATCH 2/7] add changelog entry --- changelog/std-typecons-nullable-bind.dd | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 changelog/std-typecons-nullable-bind.dd diff --git a/changelog/std-typecons-nullable-bind.dd b/changelog/std-typecons-nullable-bind.dd new file mode 100644 index 00000000000..c8cead9e361 --- /dev/null +++ b/changelog/std-typecons-nullable-bind.dd @@ -0,0 +1,12 @@ +`bind` was added to std.typecons. + +$(REF bind, std, typecons) is an operation for $(D Nullable) values that "unpacks" the $(D Nullable), performs some +operation (that is passed as a template parameter), then packs the result into another $(D Nullable) if necessary. +When the initial $(D Nullable) is $(D null), the resulting $(D Nullable) is also $(D null) and the function is not +called. + +----- +Nullable!int square(Nullable!int n) +{ + return n.bind!(i => i * i); +} From b0c382a6d660bfb01670f992ce012c81ddf61e85 Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Thu, 15 Feb 2018 18:44:57 +0100 Subject: [PATCH 3/7] std.typecons.bind: fix syntax error --- std/typecons.d | 2 ++ 1 file changed, 2 insertions(+) diff --git a/std/typecons.d b/std/typecons.d index 17406154459..8ffb2ae95ee 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -3399,6 +3399,8 @@ template bind(alias fun) /// @safe unittest { + import std.meta : Alias; + alias toFloat = Alias!(i => cast(float) i); Nullable!int sample; From 680e5c757a71511a4243c3e9575ef598929c6ee7 Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Thu, 15 Feb 2018 18:59:47 +0100 Subject: [PATCH 4/7] std.typecons.bind: improve documentation --- changelog/std-typecons-nullable-bind.dd | 1 + std/typecons.d | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/changelog/std-typecons-nullable-bind.dd b/changelog/std-typecons-nullable-bind.dd index c8cead9e361..0fa02b78403 100644 --- a/changelog/std-typecons-nullable-bind.dd +++ b/changelog/std-typecons-nullable-bind.dd @@ -10,3 +10,4 @@ Nullable!int square(Nullable!int n) { return n.bind!(i => i * i); } +----- diff --git a/std/typecons.d b/std/typecons.d index 8ffb2ae95ee..b666fc64a0b 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -3354,16 +3354,27 @@ auto nullable(alias nullValue, T)(T t) } /** +Unpacks the content of a $(D Nullable), performs an operation and packs it again. Does nothing if isNull. + When called on a $(D Nullable), $(D bind) will unpack the value contained in the $(D Nullable), pass it to the function you provide and wrap the result in another $(D Nullable) (if necessary). If the Nullable is null, $(D bind) will return null itself. + +Params: + t = a $(D Nullable) + fun = a function operating on the content of the nullable + +Returns: + `fun(t.get).nullable` if `!t.isNull`, else `Nullable.init`. + +See also: + $(HTTP en.wikipedia.org/wiki/Monad_(functional_programming)#The_Maybe_monad, The `Maybe` monad) */ template bind(alias fun) { import std.functional : unaryFun; - auto bind(T)(T t) - if (isInstanceOf!(Nullable, T) && is(typeof(unaryFun!fun(T.init.get)))) + auto bind(T)(T t) if (isInstanceOf!(Nullable, T) && is(typeof(unaryFun!fun(T.init.get)))) { alias FunType = typeof(unaryFun!fun(T.init.get)); From 28047d6b8e442cd32a6617f688539918c0164e3c Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Fri, 16 Feb 2018 17:18:59 +0100 Subject: [PATCH 5/7] std.typecons.bind: style fixes, address github feedback --- changelog/std-typecons-nullable-bind.dd | 18 +++++++++------- std/typecons.d | 28 ++++++++++++++++++------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/changelog/std-typecons-nullable-bind.dd b/changelog/std-typecons-nullable-bind.dd index 0fa02b78403..4309060c779 100644 --- a/changelog/std-typecons-nullable-bind.dd +++ b/changelog/std-typecons-nullable-bind.dd @@ -1,13 +1,15 @@ -`bind` was added to std.typecons. +`bind` was added to `std.typecons`. -$(REF bind, std, typecons) is an operation for $(D Nullable) values that "unpacks" the $(D Nullable), performs some -operation (that is passed as a template parameter), then packs the result into another $(D Nullable) if necessary. -When the initial $(D Nullable) is $(D null), the resulting $(D Nullable) is also $(D null) and the function is not +`bind` is an operation for $(REF Nullable, std, typecons) values that "unpacks" the `Nullable`, performs some +operation (that is passed as a template parameter), then packs the result into another `Nullable` if necessary. +When the initial `Nullable` is `null`, the resulting `Nullable` is also `null` and the function is not called. ----- -Nullable!int square(Nullable!int n) -{ - return n.bind!(i => i * i); -} +Nullable!int n; +alias square = i => i * i; +n = n.bind!square; // does nothing if isNull +assert(n.isNull); +n = 2; +assert(n.bind!square.get == 4); ----- diff --git a/std/typecons.d b/std/typecons.d index b666fc64a0b..3bd59712b8e 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -3374,7 +3374,8 @@ template bind(alias fun) { import std.functional : unaryFun; - auto bind(T)(T t) if (isInstanceOf!(Nullable, T) && is(typeof(unaryFun!fun(T.init.get)))) + auto bind(T)(T t) + if (isInstanceOf!(Nullable, T) && is(typeof(unaryFun!fun(T.init.get)))) { alias FunType = typeof(unaryFun!fun(T.init.get)); @@ -3408,36 +3409,47 @@ template bind(alias fun) } /// -@safe unittest +nothrow pure @nogc @safe unittest { - import std.meta : Alias; - - alias toFloat = Alias!(i => cast(float) i); + alias toFloat = i => cast(float) i; Nullable!int sample; + + // bind(null) results in a null $(D Nullable) of the function's return type. auto f = sample.bind!toFloat; assert(sample.isNull && f.isNull); sample = 3; + + // bind(non-null) calls the function and wraps the result in a $(D Nullable). f = sample.bind!toFloat; assert(!sample.isNull && !f.isNull); + assert(f.get == 3.0f); +} - alias greaterThree = Alias!(i => (i > 3) ? i.nullable : Nullable!(typeof(i)).init); +/// +nothrow pure @nogc @safe unittest +{ + alias greaterThree = i => (i > 3) ? i.nullable : Nullable!(typeof(i)).init; - sample.nullify; + Nullable!int sample; + + // when the function already returns a $(D Nullable), that $(D Nullable) is not wrapped. auto result = sample.bind!greaterThree; assert(sample.isNull && result.isNull); + // The function may decide to return a null $(D Nullable). sample = 3; result = sample.bind!greaterThree; assert(!sample.isNull && result.isNull); + // Or it may return a value already wrapped in a $(D Nullable). sample = 4; result = sample.bind!greaterThree; assert(!sample.isNull && !result.isNull); + assert(result.get == 4); } - /** Just like $(D Nullable!T), except that the object refers to a value sitting elsewhere in memory. This makes assignments overwrite the From beef0a98c9e134d34bfddfa553a26ec3917461a0 Mon Sep 17 00:00:00 2001 From: Mathis Beer Date: Mon, 19 Feb 2018 08:50:43 +0100 Subject: [PATCH 6/7] std.typecons.bind: rename to apply --- ...bind.dd => std-typecons-nullable-apply.dd} | 8 +++---- std/typecons.d | 22 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) rename changelog/{std-typecons-nullable-bind.dd => std-typecons-nullable-apply.dd} (57%) diff --git a/changelog/std-typecons-nullable-bind.dd b/changelog/std-typecons-nullable-apply.dd similarity index 57% rename from changelog/std-typecons-nullable-bind.dd rename to changelog/std-typecons-nullable-apply.dd index 4309060c779..575cbf4a7f3 100644 --- a/changelog/std-typecons-nullable-bind.dd +++ b/changelog/std-typecons-nullable-apply.dd @@ -1,6 +1,6 @@ -`bind` was added to `std.typecons`. +`apply` was added to `std.typecons`. -`bind` is an operation for $(REF Nullable, std, typecons) values that "unpacks" the `Nullable`, performs some +`apply` is an operation for $(REF Nullable, std, typecons) values that "unpacks" the `Nullable`, performs some operation (that is passed as a template parameter), then packs the result into another `Nullable` if necessary. When the initial `Nullable` is `null`, the resulting `Nullable` is also `null` and the function is not called. @@ -8,8 +8,8 @@ called. ----- Nullable!int n; alias square = i => i * i; -n = n.bind!square; // does nothing if isNull +n = n.apply!square; // does nothing if isNull assert(n.isNull); n = 2; -assert(n.bind!square.get == 4); +assert(n.apply!square.get == 4); ----- diff --git a/std/typecons.d b/std/typecons.d index 3bd59712b8e..20b5ec14286 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -3356,9 +3356,9 @@ auto nullable(alias nullValue, T)(T t) /** Unpacks the content of a $(D Nullable), performs an operation and packs it again. Does nothing if isNull. -When called on a $(D Nullable), $(D bind) will unpack the value contained in the $(D Nullable), +When called on a $(D Nullable), $(D apply) will unpack the value contained in the $(D Nullable), pass it to the function you provide and wrap the result in another $(D Nullable) (if necessary). -If the Nullable is null, $(D bind) will return null itself. +If the Nullable is null, $(D apply) will return null itself. Params: t = a $(D Nullable) @@ -3370,11 +3370,11 @@ Returns: See also: $(HTTP en.wikipedia.org/wiki/Monad_(functional_programming)#The_Maybe_monad, The `Maybe` monad) */ -template bind(alias fun) +template apply(alias fun) { import std.functional : unaryFun; - auto bind(T)(T t) + auto apply(T)(T t) if (isInstanceOf!(Nullable, T) && is(typeof(unaryFun!fun(T.init.get)))) { alias FunType = typeof(unaryFun!fun(T.init.get)); @@ -3415,14 +3415,14 @@ nothrow pure @nogc @safe unittest Nullable!int sample; - // bind(null) results in a null $(D Nullable) of the function's return type. - auto f = sample.bind!toFloat; + // apply(null) results in a null $(D Nullable) of the function's return type. + auto f = sample.apply!toFloat; assert(sample.isNull && f.isNull); sample = 3; - // bind(non-null) calls the function and wraps the result in a $(D Nullable). - f = sample.bind!toFloat; + // apply(non-null) calls the function and wraps the result in a $(D Nullable). + f = sample.apply!toFloat; assert(!sample.isNull && !f.isNull); assert(f.get == 3.0f); } @@ -3435,17 +3435,17 @@ nothrow pure @nogc @safe unittest Nullable!int sample; // when the function already returns a $(D Nullable), that $(D Nullable) is not wrapped. - auto result = sample.bind!greaterThree; + auto result = sample.apply!greaterThree; assert(sample.isNull && result.isNull); // The function may decide to return a null $(D Nullable). sample = 3; - result = sample.bind!greaterThree; + result = sample.apply!greaterThree; assert(!sample.isNull && result.isNull); // Or it may return a value already wrapped in a $(D Nullable). sample = 4; - result = sample.bind!greaterThree; + result = sample.apply!greaterThree; assert(!sample.isNull && !result.isNull); assert(result.get == 4); } From 057386f75c15129b7268b44c40e39d32fc6a96d2 Mon Sep 17 00:00:00 2001 From: FeepingCreature Date: Fri, 9 Mar 2018 18:18:18 +0100 Subject: [PATCH 7/7] std.typecons.apply: make unittest clearer --- std/typecons.d | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/typecons.d b/std/typecons.d index 20b5ec14286..2222cc23d52 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -3416,7 +3416,7 @@ nothrow pure @nogc @safe unittest Nullable!int sample; // apply(null) results in a null $(D Nullable) of the function's return type. - auto f = sample.apply!toFloat; + Nullable!float f = sample.apply!toFloat; assert(sample.isNull && f.isNull); sample = 3;