reset password
Author Message
kmarlis
Posts: 35
Posted 16:55 Mar 13, 2019 |

Hi everyone! Here's my fmap code from class today. 

from functools import singledispatch

# singledispatch dispatches based on the type of the first argument
@singledispatch
def fmap(functor, func):
    pass

@fmap.register(list)
def _(functor, func):
    return [func(elem) for elem in functor]

@fmap.register(tuple)
def _(functor, func):
    return tuple((func(elem) for elem in functor))

@fmap.register(str)
def _(functor, func):
    return ''.join((func(char) for char in functor))

@fmap.register(set)
def _(functor, func):
    return {func(elem) for elem in functor}

@fmap.register(dict)
def _(functor, func):
    return {k: func(v) for k, v in functor.items()}

@fmap.register(GeneratorType)
def _(functor, func):
    for elem in functor:
        yield func(elem)

@fmap.register(range)
def _(functor, func):
    for elem in functor:
        yield func(elem)

print("None:", fmap(None, lambda x: x + 1))
print("String:", fmap([1,2,3], lambda x: x + 1))
print("Tuple:", fmap((1,2,3), lambda x: x + 1))
print("Set:", fmap({1,2,3}, lambda x: x + 1))
print("Dict:", fmap({1:1,2:2,3:3}, lambda x: x + 1))
print("Str:", fmap("Hello", lambda x: x.upper()))
print("Range:", list(fmap(range(15), lambda x: x + 1)))

def countdown(num):
    while num > 0:
        yield num
        num -= 1

print("Generator:", list(fmap(countdown(10), lambda x: x + 1)))

 

This doesn't have Jay's Iterator idea implemented. I also just realized I didn't put typing in there. Oops! 

Last edited by kmarlis at 17:16 Mar 13, 2019.
jpatel77
Posts: 44
Posted 00:58 Mar 14, 2019 |

As I tried to register Iterable instead of individual types, I ended up combining the possibilities into a single definition with type-wise implementation, with some modifications in transformation:

from functools import singledispatch
from types import GeneratorType, FunctionType
from typing import Iterable

# singledispatch dispatches based on the type of the first argument

@singledispatch
def fmap(functor: Iterable, func: FunctionType) -> None:
    pass

@fmap.register(Iterable)
def _(functor: Iterable, func: FunctionType) -> Iterable:
    fType = type(functor)
    if fType in (GeneratorType, range):
        return map(func, functor)
    elif fType is str:
        return ''.join(map(func, functor))
    elif fType is dict:
        return {k: func(v) for k, v in functor.items()}

    else:
        return fType(map(func, functor))

 

It is obvious that we can register each cases individually, however, I felt this one to be more sensible as it conveys that it works for "an iterable object" (somewhat similar to the "works for foldable" in haskell) rather than for a specific subtype. Although, argument can be made the other way too, I decided to keep it this way only.

I thought about merging it under the def fmap() itself, which would allow us to completely get away with the whole singledispatch and would make it more native, but I think that would be too harsh for the original code.

Edit: I think registering each case separately would be the best thing to do, if we were to use singledispatch , as I realized it is meant exactly for that purpose.

 

Last edited by jpatel77 at 01:25 Mar 14, 2019.