Skip to content

Instantly share code, notes, and snippets.

@LamourBt
Created January 26, 2018 21:27
Show Gist options
  • Save LamourBt/39446fc6fbf8ac5f70f690f5b0b448f7 to your computer and use it in GitHub Desktop.
Save LamourBt/39446fc6fbf8ac5f70f690f5b0b448f7 to your computer and use it in GitHub Desktop.
Functor
/*
An Object is considered to be a **functor** when it implements fmap (fmap referring to functor map not flatmap)
-[Rules] This fmap should
* preserve Identity (x to y) and (y to x ) should be the same value every time
* and be composable
map isn't fmap but fmap can do what map does
map only operates on pure function, where fmap is lifted that pure function to operate on functor type
*/
struct Functor<A> {
public let value: A
public init(_ value: A) { self.value = value }
}
// functor extension contains default fmap implementation
extension Functor {
static func fmap<B>(_ f:@escaping (A) -> B) -> (Functor<A>) -> Functor<B> {
return { a in
return Functor<B>(f(a.value))
}
}
/**
fmap :: (a -> b) -> f a -> f b (curried)
this pure function (a) -> b is going to be lifted to (f a -> f b)
*/
public func fmap<B>(_ f:@escaping (A) -> B) -> Functor<B> {
return Functor<B>(f(self.value))
}
}
/*
Since Maybe in Haskell is a functor
*/
enum Maybe<T> {
case just(T), nothing
public init(_ value: Optional<T>) {
switch value {
case .none: self = .nothing
case .some(let v):self = .just(v)
}
}
}
/*
The example below would be a bad use/implementation of fmap, if I'm Right?
Because to my understanding
fmap takes a non-lifted pure function such as A -> B not Maybe<A> -> Maybe<B>
then it returns a Functor<B>
let something = Functor(Maybe<Int>(9))
something.fmap({ x -> Maybe<Double> in
switch x {
case .nothing: return Maybe<Double>.nothing
case .just(let v): return Maybe<Double>(Optional<Double>(Double(v)))
}
})
--- a Good example would (if I'm right of course):
let toDouble: (Int) -> Double = { return Double($0) }
let toString: (Double) -> String = { return String($0) }
let result = Functor(Maybe<Int>(9)).fmap(toDouble).fmap(toString)
*/
extension Functor where A == Maybe<Any> {
// [constraint] if we get Functor<Maybe<Any>> you should use this version of fmap not the default fmap of Functor<B>
// this should be improve since `Any` kills the Type. It would be ideal if we have Maybe<Int> or Maybe<S>
func map(_ f:@escaping (Any) -> Any) -> Functor<Maybe<Any>> {
switch self.value {
case .just(let boxValue):
return Functor<Maybe<Any>>(Maybe<Any>(Optional<Any>(f(boxValue))))
case .nothing:
return Functor<Maybe<Any>>(.nothing)
}
}
}
extension Maybe {
public func fmap<B>(_ f:@escaping (T) -> B) -> Functor<Maybe<B>> {
switch self {
case .just(let value):
return Functor<Maybe<B>>(Maybe<B>(Optional<B>(f(value))))
case .nothing:
return Functor<Maybe<B>>(.nothing)
}
}
public func map<B>(_ f:@escaping (T) -> B) -> Maybe<B> {
switch self {
case .just(let value):
return Maybe<B>(Optional<B>(f(value)))
case .nothing:
return .nothing
}
}
}
/* Testing Identity */
let negate: (Int) -> Int = { return (-1) * $0 }
let unnegate: (Int) -> Int = { return $0 / (-1) }
let intMaybe = Maybe<Int>(2)
let result = intMaybe.fmap(negate)
print(result) //Functor<Maybe<Int>>(value: __lldb_expr_115.Maybe<Swift.Int>.just(-2))
let original = result.value.fmap(unnegate)
// result.map(<#T##f: (Any) -> Any##(Any) -> Any#>) //<-- can't use this yet since Any kills the type
print(original) //Functor<Maybe<Int>>(value: __lldb_expr_133.Maybe<Swift.Int>.just(2))
//let fun = Maybe<Character>("C").fmap({ $0.hashValue }).map(toDouble) // <-- can't do that yet
//print(fun)
/*
Testing Composability
*/
// regular map from maybe is composable
let some = Maybe<String>("jake")
.map({ $0.uppercased() })
.map({ x -> Character? in return x.first })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment