• balsoft@lemmy.ml
    link
    fedilink
    arrow-up
    5
    ·
    edit-2
    19 hours ago

    This is actually a good alternative explanation, as long as you clarify it a bit.

    What is meant by “container” type is that it’s possible to

    1. “Put” a single thing into our container (unit/pure)
    2. “Modify” the insides our container (map/fmap), without “taking any values out”

    Take for example a list.

    unit is simple, as I’ve described in my comment:

    def unit(a):
        return a
    

    Map is also fairly straight-forward, just applying a given function to every element of the list:

    def map(fun, lst):
        new_lst = []
        for element in lst:
            new_lst.append(fun(element))
        return new_lst
    

    Then your final operation (“flattening” the container) is traditionally called join. It concatenates a list of lists into a single flat list:

    def join(lst):
        new_lst = []
        for element in lst:
            new_lst.extend(element)
        return new_lst
    

    (you might notice that it is extremely similar to both map and bind)

    This allows us to define bind from my other comment (which you call flatMap) in terms of join and map:

    def bind(lst, fun):
        return join(map(fun, lst))
    

    Or, if you already have a bind (and unit) defined, you can define join and map as follows:

    def join(lst):
        return bind(lst, unit)
    def map(fun, lst):
        return bind(lst, lambda x: unit(fun(x)))
    

    Showing that a type defining unit and bind is equivalent to a type defining unit, join and map - they are both equivalent definitions of a monad, you can derive one from another and vice versa.

    Historically, the unit+bind definition is more popular and aligns with what most functional languages do, so I went with it for my explanation. But yours is probably a bit easier to understand for an FP outsider.