Closed jonwhelan closed 5 years ago
Hmm this is an interesting thought, I'm sure there is a reason for this behaviour based in the rules/laws, however doing some research I can see that your suggested behaviour is the more common one in other similiar libraries, like folk-tale, https://folktale.origamitower.com/api/v2.3.0/en/folktale.maybe.maybe.concat.html
For not i'm guessing you have these values in a list and your reducing that list with concat
, you could compose your call to concat
with a call to alt
to get the behaviour you want for now, but I think i'll wait to hear what @evilsoft has to say about this
So this is one of the things I actually do not like about Prelude.
While it is very 🌽venient, it turns any Semigroup
into a Monoid
and does not have a real empty value, and has to rely on the isomorphism between the Terminal Monoid
and something that may not be a full on Monoid
which means when we need the real value we need to transport the empty
to the edge when we option: Mon => option(Mon.empty())
.
But let me do some diagramming and see if I can come up with an alternative that can help get you what you need.
But if worse comes to worst, than I will def look into make this change to Maybe
as suggested (but I am not going to like it 😜)
But as @dalefrancis88 pointed out. You could use the perfectly legal and currently available alt
function with something like:
import alt from 'crocks/pointfree/alt'
import Assign from 'crocks/Assign'
import Maybe from 'crocks/Maybe'
const { Just ) = Maybe
// fn :: Monoid m => MonoidTypeRep -> Maybe m -> Maybe m
const fn = Mon =>
alt(Just(Mon.empty()))
// withAssign :: Maybe Assign -> Maybe Assign
const withAssign =
fn(Assign)
A more thourough example could be demonstrated as:
import alt from 'crocks/pointfree/alt'
import compose from 'crocks/helpers/compose'
import concat from 'crocks/pointfree/concat'
import flip from 'crocks/combinators/flip'
import isObject from 'crocks/predicates/isObject'
import map from 'crocks/pointfree/map'
import mapReduce from 'crocks/helpers/mapReduce'
import safe from 'crocks/Maybe/safe'
import Assign from 'crocks/Assign'
import Maybe from 'crocks/Maybe'
const { Just, Nothing } = Maybe
// fn :: Monoid m => MonoidTypeRep -> Maybe m -> Maybe m
const fn = ({ empty }) =>
alt(Just(empty()))
// withAssign :: Maybe Assign -> Maybe Assign
const withAssign =
fn(Assign)
withAssign(
Just(Assign({ a: 1 }))
)
//=> Just Assign { a: 1 }
withAssign(Nothing())
//=> Just Assign {}
// mapper :: a -> Maybe Assign
const mapper = compose(
withAssign,
map(Assign),
safe(isObject)
)
mapper(22)
//=> Just Assign {}
mapper({ a: 22 })
//=> Just Assign { a: 22 }
// concatObjects :: [ a ] -> Maybe Assign
const concatObjects = mapReduce(
mapper,
flip(concat),
Just(Assign.empty())
)
concatObjects([ { a: 11 }, { b: 2 } ])
//=> Just Assign { a: 11, b: 2 }
concatObjects([ { a: 11 }, null, { b: 2 } ])
//=> Just Assign { a: 11, b: 2 }
While this seems like extra work, it allows for the information of the empty to stay isolated with the fold and does not do strange things over in the Category of Mon
@evilsoft Thanks for the thorough explanation! If I'm understanding it correctly, essentially all the Maybe
s are getting mapped over and the Nothing
s are defaulted to an empty Just(Assign.empty())
by virtue of alt
, thus allowing the reduce
to encounter all Just
s.
My solution was similar but I was just mapping over with a function like the following prior to reducing: either(constant(Just(Assign.empty())), Just)
. But I like the elegance of using alt
as you and @dalefrancis88 have suggested.
@evilsoft Your explanation and philosophy behind the current implementation of concat
is a bit over my head at this point. Is it related to this comment which states that the type constraint of a Monoid
on Maybe is too strong and should instead be a Semigroup
?
If this is the case, would this imply that the "correct" implementation is indeed the one we have in this library, or is it an orthogonal concern and still just left to preference?
If it does just come down to preference and one way is not more correct than the other, then perhaps we should make the decision on a.) which implementation is more useful the majority of the time and b.) fallback to the common implementation among different libraries.
Since I'm not fully grasping the justification, I just have to defer to how other libraries have implemented this to inform my preference. However, I do appreciate the rigor on this and don't want to do something just because other implementors have done it.
So in playing with this for a while now, I cannot get around the laws for anything like this. This issue is the empty or Monoid portion. It is okay with Semigroup because it speaks to the value the Maybe is wrapping, but adding implicit empty with Nothing
, we would then to to transport the context of the inner Semigroup type to the Outer Maybe type.
So because we have alt
and can transport that inner context to the outer, I am going to have to pass on this alternative. Even though other libraries allow it, I see it as having other implications for such an non-common case.
I think this may be just an alternative implementation of Maybe#concat rather than a bug, but the
concat
method is biased towardsNothing
and will result in the expression evaluating toNothing
if one is present.In haskell if we do the same:
Just [1] <> Nothing
will evaluate toJust [1]
.My particular use case for the proposed implementation is to build up an object from several
Maybe Assign({})
and have it evaluate to aJust
even if aNothing
is encountered.E.g.