Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arg order in <^> vs >>- #152

Closed
aryairani opened this issue Dec 30, 2014 · 39 comments
Closed

Arg order in <^> vs >>- #152

aryairani opened this issue Dec 30, 2014 · 39 comments
Labels

Comments

@aryairani
Copy link

Is there a reason why <^> and >>- don't take their args in the same order? Should one of them be switched (eg <^>) or do you recommend using the map method directly in that case?

Thanks!

@CodaFi
Copy link
Member

CodaFi commented Dec 30, 2014

I think Max flipped fmap to better align with Swift's "syntax optimizations" for closures. I agree it's confusing, but it's still Functorial.

@aryairani
Copy link
Author

Like better type inference? Or what do you mean?

Was it not an issue for >>- ?

@CodaFi
Copy link
Member

CodaFi commented Dec 30, 2014

Bind is in its "natural order" with the monad in the left and the function on the right, as in

Maybe.some(5) >>- { Maybe.some($0 + 5) }
Maybe.some(5).bind { Maybe.some($0 + 5) }

Fmap, not so much:

Maybe.some(5) <^> (+5) // currently
(+5) <$> Maybe.some(5) // "correctly"
Maybe.some(5).fmap(+5)

@aryairani
Copy link
Author

I like the monad on the left and the function on the right, but doesn't <^> currently use the reverse of that?

@aryairani
Copy link
Author

Ok I think you're saying <^> and <$> should have the function on the left and the FA on the right, possibly to match how functions appear on the left when they're invoked in Haskell.

Currently that's how they are defined in swiftz too, afaict. But that makes it very cumbersome to chain with <^> doesn't it? Or is it easy to compose those functions before a single map?

Is fmap also defined as in your example above? Where can I find it in the source?

Thanks!

@CodaFi
Copy link
Member

CodaFi commented Dec 31, 2014

(<^> and <$> are the same thing. I'm showing you how you'd like it in Swift versus Haskell).

But yes, it makes it very awkward for Fmap. I believe we sectioned the operator, so it's possible to compose with it the way you might be used to

(f <^>)  (g <^>) = ((f  g) <^>)

I can't do anything about the function fmap in our Functor protocol. It's the backwards one here.

@CodaFi
Copy link
Member

CodaFi commented Dec 31, 2014

I think the confusion here is where each function is applicable and what each does. Bind is defined for all Monads, but only some Functors. Bind is also inherently sequential composition, so it makes total sense to write equations from monad left to function chain right and see them evaluated as such.

Fmap is a generalization of map for Functors. It takes a function first and applies it to the contents of a Functor because map takes a function first and a list second. The important thing to remember here is fmap is not compositional by default. It needs • in order to have coherence laws, so it makes more sense for it to appear "out of order".

Really, bind is the backwards one here. It's written to allow imperative looking code (left to right) instead of the normal equational looking code (right to left).

@aryairani
Copy link
Author

Sorry, when you said <$>, I had read it as <*>. Is there even a <$> in Swiftz? I don't see one.

So why does <^> take its function first? To read like Haskell? Or is there some Swift-related benefit to doing it that way? Why not write <^> to allow "imperative-looking" code too? (andThen semantics instead of compose). Or is there already a way of doing that?

You could write bind right-to-left too if you wanted; it wouldn't make it less or more equational, but I don't think it'd be a good idea either.

Thanks!

@aryairani
Copy link
Author

More importantly, why not be consistent in the syntax between <^> and >>-?

@CodaFi
Copy link
Member

CodaFi commented Dec 31, 2014

Bind is meant to be non-traditional to enable do-notation. We should not be consistent between the two operators because they are built for vastly different generalizations of structures with different purposes. Besides, if you flip bind backwards, you get a new operator -<<. If you flip fmap you get fmap.

@CodaFi
Copy link
Member

CodaFi commented Dec 31, 2014

We wish to enable declarative-looking code. Imperative-looking code is easy enough to do without this framework.

@aryairani
Copy link
Author

Sure, but it's in the exact same "do" situation that you'd want map to be right to left, right?

fa bind f1 map f2 bind f3 map f4 map f5

Argument order doesn't intrinsically make things more declarative or more imperative, does it? Or did you mean "Haskell-looking" vs "Swift-looking"? Surely you don't mean that the big contribution of this framework is a particular argument order? I'd think that's the most arbitrary part and not at all what's awesome about swiftz.

@CodaFi
Copy link
Member

CodaFi commented Dec 31, 2014

Remember, fmap is not meant to be compositional. Fmap is map. You can compose maps together, but you need a composition operator. Bind is Kleisli Composition in an operator.

Try to run your example through GHCI.

@aryairani
Copy link
Author

I guess I don't understand what you mean when you say it's not meant to be compositional. It's definitely convenient to be able to chain them together like I showed, even if it wasn't meant to be.

Does the current parameter order maybe enable some other convenient usage, besides making it difficult to chain together? If not, maybe it'd be worth switching the current syntax around. And if there is, maybe it'd be worth adding the alternate syntax in addition to the current syntax!

Cheers,
Arya

@CodaFi
Copy link
Member

CodaFi commented Dec 31, 2014

Compositional means the whole "chaining them together" thing. Not every function is meant to mesh together (because types!) with ease because not every function is built with that in mind. Of the big three typeclasses (Functor, Applicative, and Monad), only the last has compositional operators for a specific kind of semantics. For everything else there's .

@aryairani
Copy link
Author

I'm trying to ask if there's any advantage to keeping the order the way it is — because there is an advantage to switching it.

@sharplet
Copy link
Contributor

@refried Permit me to dump some of my random thoughts for a moment!

Something I've observed in learning a bit of Haskell is that pervasive currying really seems to affect the way arguments are normally ordered. Whereas in an OO, imperative language like Swift you might see something like

// [Int] -> (Int -> String) -> [String]
let repeated = numbers.map { x in "\(x)\(x)" }

in Haskell this might look like

-- (Int -> String) -> [Int] -> [String]
let repeated = map (\x -> show x ++ show x) numbers

The interesting thing with the Haskell style is when you partially apply the map function, and you get this:

repeatTwice :: (Show a) => [a] -> [String]
repeatTwice = map (\x -> show x ++ show x)

The interesting thing to me about repeatTwice here is that you've taken a function that works on normal values (i.e., (\x -> show x ++ show x) :: (Show a) => a -> String) and "lifted" into a function that does the same thing but works on a list and then returns a list (Show a => [a] -> [String]).


This same style is possible in Swift, and it looks to me like that's what's primarily influencing the order of the arguments for <^>. However if you look at one of the examples of map as defined in the Swift standard library, it's the reverse:

// kind of looks like [a] -> (a -> b) -> [b]
func map<S : SequenceType, T>(source: S, transform: (S.Generator.Element) -> T) -> [T]

(As an aside, is it just me or does having the types mixed in with the parameter names make it much harder to read?)

The method form of map must implicitly accept the array (or optional or whatever) first, followed by the transform function. But the free function <^> is, ahem, free to accept arguments in the other order.


I'm not familiar enough with the history of Haskell's design to say just how intentional this is, but it feels fairly intentional to me!

HTH. (@CodaFi please jump in if you have anything to add to that!)

@aryairani
Copy link
Author

Hi @sharplet, I can appreciate random thoughts, thanks for sharing them.

I'm not super familiar with Swift, if only because Playgrounds are super crashy and I get angry whenever I try :-/ I am familiar with Scala and Scalaz (which I assumed Swiftz was related to) though.

Could you show an example of partially applied <^> in Swift?

Re mixing parameter types and names, I have mixed feelings. :-)

@CodaFi
Copy link
Member

CodaFi commented Dec 31, 2014

OK, let's try this again now that I've found a proper computer instead of my old iPhone:

Is there a reason why <^> and >>- don't take their args in the same order? Should one of them be switched (eg <^>) or do you recommend using the map method directly in that case?

<^> is a synonym for fmap, or a Functorial Map

fmap :: (a -> b) -> f a -> f b

As such the map is the most important part of the signature. The map is the reason the word Functor was stolen from the Category Theorists. The map is the point of us stealing it from Haskell. While it may be a combinator, it is not compositional, in that its coherence laws require the presence of a real composition operator like

fmap (f  g)  ==  fmap f  fmap g

One does not compose Functors with fmap without just as one does not compose functions without because it just doesn't work.

Monads, on the other hand, were built with sequencing in mind rather than the proper coherence laws implied by their unit triangle. Haskellers quickly realized that sequential composition could be made to look highly imperative, and so introduced do-notation which desugared into >>= and >>

main :: IO ()
main = do
  putStrLn "Phlakaton!"
  args <- getArgs
  putStrLn $ show args
  return ()

main :: IO ()
main = putStrLn "Phlakaton!" >> getArgs >>= { \args -> putStrLn $ show args } >> return ()

But in doing so, they had to "flip" the traditional right-to-left evaluation semantics, otherwise you'd be reading do-notation from bottom to top.

In keeping with tradition, we have left <^> and >>= in the order implied by their semantics, rather than the order that would be most convenient for a certain person (mostly because we also supply the combinators flip and ). While it may seem that the two are "opposite", the comparison is what's really at fault here.

@aryairani
Copy link
Author

Hi, thanks for the reply. I could be way off base here, but I think all of the examples and rationale above are in Haskell. Wouldn't it make sense to discuss Swiftz decisions in relation to Swift?

I'm not suggesting to change the library for a certain person, I'm asking if there's any practical reason not to flip it, because there is a practical reason to flip it. If there were also a practical reason to not flip it, then it might make more sense not to flip it. I'm asking if the current order is most convenient for anyone in any context, in Swift, and if so, could you enlighten me with an example?

Or if the first goal of Swiftz is simply to write code that looks as close as possible to historical Haskell code, then it's fine to say that explicitly. If that's not the primary goal, then it doesn't really matter what Functors or Monads were built with in mind, because that was in the past.

Am I making any sense? Thanks!

@CodaFi
Copy link
Member

CodaFi commented Dec 31, 2014

The practical reason not to flip it is we provide it flipped in the Functor Typeclass, and we provide the combinator flip ;)

@CodaFi
Copy link
Member

CodaFi commented Jan 9, 2015

@refried Any further input, or has this thread run its course?

@aryairani
Copy link
Author

Oh hello :)

Well, to be honest, I was left with the feeling that I wasn't able to get
my point across at all. Is that something you'd be interested in still?
On Thu, Jan 8, 2015 at 22:35 Robert Widmann notifications@github.com
wrote:

@refried https://github.com/refried Any further input, or has this
thread run its course?


Reply to this email directly or view it on GitHub
#152 (comment).

@CodaFi
Copy link
Member

CodaFi commented Jan 9, 2015

Then let's start over.

Restate the question and I'll try to reply more in-depth this time.

@aryairani
Copy link
Author

Thanks -- I guess to start, could you show a working Swift example of the
anticipated use case for <^>? (hopefully including composition?)
On Thu, Jan 8, 2015 at 23:51 Robert Widmann notifications@github.com
wrote:

Then let's start over.

Restate the question and I'll try to be more succinct about replying this
time.


Reply to this email directly or view it on GitHub
#152 (comment).

@CodaFi
Copy link
Member

CodaFi commented Jan 10, 2015

UserExample.swift has a pretty good example of using <^>

public class func fromJSON(x: JSONValue) -> User? {
    var n: String?
    var a: Int?
    var t: [String]?
    var r: Dictionary<String, String>?
    switch x {
    case let .JSONObject(d):
        n = d["name"]   >>- JString.fromJSON
        a = d["age"]    >>- JInt.fromJSON
        t = d["tweets"] >>- JArray<String, JString>.fromJSON
        r = d["attrs"]  >>- JDictionary<String, JString>.fromJSON
        // alternatively, if n && a && t... { return User(n!, a!, ...
        return (User.create <^> n <*> a <*> t <*> r)
    default:
        return .None
    }
}

If you're asking for something like fmap • fmap • fmap I'll refer you here.

@aryairani
Copy link
Author

Thanks -- that is certainly a different use case than I expected: producing a partially applied lifted function. Is that the expected use case?

The second link you sent was in Haskell though, so it didn't answer my question about Swift at all.

@CodaFi
Copy link
Member

CodaFi commented Jan 11, 2015

The reason I continue to bring up Haskell is that as a language it has a beautiful and straightforward approach to laying out the kinds of abstractions we're using here. Our work stems from that, and our semantics does the same.

@aryairani
Copy link
Author

I understand that, but it doesn't help. My question was about Swiftz, which
I'm assuming only exists at all due to the fact that Swift is not the same
thing as Haskell.
On Sat, Jan 10, 2015 at 19:42 Robert Widmann notifications@github.com
wrote:

The reason I continue to bring up Haskell is that as a language it has a
beautiful and straightforward approach to laying out the kinds of
abstractions we're using here. Our work stems from that, and our semantics
does the same.


Reply to this email directly or view it on GitHub
#152 (comment).

@CodaFi
Copy link
Member

CodaFi commented Jan 11, 2015

This is one of those cases where semantics transcends language boundaries.


But an example in Swift is an easy thing to produce. The interpretation of a Functor that we take (because we're restricted to the Category of Swift Types- hereafter $) is an Endofunctor- or a Functor that maps the same domain to the same range ($ -> $). The members of the category $ are any valid Swift structure, protocol, and class, the arrows are the free and member functions that map them between each other. Because we're restricted to Endomorphic behaviour, our fmap must take Functors over one Swift type to Functors over other Swift types. Such a function looks like this:

func fmap<T, U>(f : T -> U, fun : Functor<T>) -> Functor<U>

which is curried as:

func fmap<T, U>(f : T -> U) -> Functor<T> -> Functor<U>

Is there a reason why <^> and >>- don't take their args in the same order? Should one of them be switched (eg <^>)

Take a look at that type signature one more time.

(T -> U) -> F<T> -> F<U>

Notice anything special about the last half of the function?

(T -> U) -> (F<T> -> F<U>)

fmap is lifting a function to a function over Functors. It is not binding data, it's lifting functions. The function is the most important part of the type signature, and so we keep it as such. If we flip the types around we get:

F<T> -> (T -> U) -> F<U>

This appears to be nothing important. It's certainly convenient, but we lose the meaning behind fmap.


Thanks -- that is certainly a different use case than I expected: producing a partially applied lifted function. Is that the expected use case?

Yes and no. Yes in the sense of the wall of text I just posted. No in the sense that because we can't actually use Functors in general because of Swift's type system, we instead use the protocol Functor to mark structures that can map themselves over to different types. We define a large amount of those structures that perhaps overload Functor to perform a little more than lifting (JSON stuff mostly). Ultimately they all accomplish the same thing: Lifting a function to a function over a Functor.

@aryairani
Copy link
Author

I think I must not be smart enough to know how to ask a question that you
can understand and answer in such a way that it is recognizable to me as an
answer to my question. Thanks anyway!

@CodaFi
Copy link
Member

CodaFi commented Jan 11, 2015

I'm a chronic overthinker :(

If you think of a way to reformulate this question, reopen this issue.

@CodaFi CodaFi closed this as completed Jan 11, 2015
@aryairani
Copy link
Author

I read your answer again this morning, just wanted to add a quick note to say that even though I don't feel that it answers my question, it was informative and interesting. So thanks for now!

@aryairani
Copy link
Author

If you flip the types around, you get:

F<A> -> (A -> B) -> (B -> C) -> ... -> (Y -> Z) -> F<Z>

How would you do that instead now? Thanks!

@aryairani
Copy link
Author

You has previously pointed out that

(f <^>) • (g <^>) = ((f • g) <^>)

but is there an example in swift?

@CodaFi
Copy link
Member

CodaFi commented Jan 11, 2015

I read your answer again this morning, just wanted to add a quick note to say that even though I don't feel that it answers my question, it was informative and interesting. So thanks for now!

You're very welcome. I'm terribly sorry I'm incapable of grokking your question right now. I don't want this to wind up as a situation where you get turned off by jargon, or feel like FP means I can't help you. You're absolutely "smart enough" to ask questions around here!


If you flip the types around, you get:
F -> (A -> B) -> (B -> C) -> ... -> (Y -> Z) -> F
How would you do that instead now? Thanks!

Short Answer:

flip(<^>)

Long Answer:

By virtue of our definition of Functor we already give you an fmap that looks like

F<A> -> (A -> B) -> F<B>

public protocol Functor {
    /// ...
    /// Map a function over the value encapsulated by the Functor.
    func fmap(f : A -> B) -> FB
}

Though it may not look like it, Every member function in Swift actually looks like this internally:

struct Foo<A> : Functor {
    func fmap(f : A -> B) -> Foo<B> { // do stuff }
}

fmap : Foo<A> -> (A -> B) -> Foo<B>

but you have to invoke it statically (Foo<A>.fmap rather than Foo<A>().fmap) to see that. By yet another virtue, this time of the language, . to invoke functions can be used to compose fmaps rather than like function composition, albeit backwards.

You has previously pointed out that
(f <^>) • (g <^>) = ((f • g) <^>)
but is there an example in swift?

Yes! I'll show you with the Identity Functor (Box) out of laziness. Throw this in a playground

// --------- Prerequisites ----------- //
public class K1<A> {}

infix operator • {}

public func •<A, B, C>(f: B -> C, g: A -> B) -> A -> C {
    return { (a: A) -> C in
        return f(g(a))
    }
}


public protocol Functor {
    /// Source
    typealias A
    /// Target
    typealias B
    /// A Target Functor
    typealias FB = K1<B>

    /// Map a function over the value encapsulated by the Functor.
    func fmap(f : A -> B) -> FB
}


/// The Identity Functor holds a singular value.
public struct Id<A> {
    private let a : @autoclosure () -> A

    public init(_ aa : A) {
        a = aa
    }

    public var runId : A {
        return a()
    }
}

extension Id : Functor {
    public typealias B = Any

    public func fmap<B>(f : A -> B) -> Id<B> {
        return (Id<B>(f(self.runId)))
    }
}

//--------------!!--------------//

let id = Id<Int>(5)
let f : Int -> Int = { x in x + 5 }
let g : Int -> String = { x in String(x) }

let x = id.fmap(f).fmap(g).runId // String("10")
let y = id.fmap(g • f).runId // String("10")

x == y // true

@aryairani
Copy link
Author

Does swiftz include an fmap implementation for every <^> implementation?
(since AFAIK, swift won't let us share one single definition)
On Sun, Jan 11, 2015 at 12:56 Robert Widmann notifications@github.com
wrote:

I read your answer again this morning, just wanted to add a quick note to
say that even though I don't feel that it answers my question, it was
informative and interesting. So thanks for now!

You're very welcome. I'm terribly sorry I'm incapable of grokking your
question right now. I don't want this to wind up as a situation where you
get turned off by jargon, or feel like FP means I can't help you. You're

absolutely "smart enough" to ask questions around here!

If you flip the types around, you get:
F -> (A -> B) -> (B -> C) -> ... -> (Y -> Z) -> F
How would you do that instead now? Thanks!

Short Answer:

flip(<^>)

Long Answer:

By virtue of our definition of Functor we already give you an fmap that
looks like

F -> (A -> B) -> F

public protocol Functor {
/// ...
/// Map a function over the value encapsulated by the Functor.
func fmap(f : A -> B) -> FB
}

Though it may not look like it, Every member function in Swift actually
looks like this internally:

struct Foo : Functor {
func fmap(f : A -> B) -> Foo { // do stuff }
}

fmap : Foo -> (A -> B) -> Foo

but you have to invoke it statically (Foo.fmap rather than
Foo().fmap) to see that. By yet another virtue, this time of the
language, . to invoke functions can be used to compose fmaps rather than •
like function composition.

You has previously pointed out that
(f <^>) • (g <^>) = ((f • g) <^>)
but is there an example in swift?

Yes! I'll show you with the Identity Functor (Box) out of laziness. Throw
this in a playground

// --------- Prerequisites ----------- //
public class K1 {}

infix operator • {}

public func •<A, B, C>(f: B -> C, g: A -> B) -> A -> C {
return { (a: A) -> C in
return f(g(a))
}
}

public protocol Functor {
/// Source
typealias A
/// Target
typealias B
/// A Target Functor
typealias FB = K1

/// Map a function over the value encapsulated by the Functor.
func fmap(f : A -> B) -> FB

}

/// The Identity Functor holds a singular value.
public struct Id {
private let a : @autoclosure () -> A

public init(_ aa : A) {
    a = aa
}

public var runId : A {
    return a()
}

}

extension Id : Functor {
public typealias B = Any

public func fmap<B>(f : A -> B) -> Id<B> {
    return (Id<B>(f(self.runId)))
}

}

//--------------!!--------------//

let id = Id(5)
let f : Int -> Int = { x in x + 5 }
let g : Int -> String = { x in String(x) }

let x = id.fmap(f).fmap(g).runId // String("10")
let y = id.fmap(g • f).runId // String("10")

x == y // true


Reply to this email directly or view it on GitHub
#152 (comment).

@CodaFi
Copy link
Member

CodaFi commented Jan 11, 2015

(Unfortunately for my poor fingers) Yes. We have to write an fmap for every Functor.

@aryairani
Copy link
Author

:)
On Sun, Jan 11, 2015 at 13:07 Robert Widmann notifications@github.com
wrote:

(Unfortunately for my poor fingers) Yes. We have to write an fmap for
every Functor.


Reply to this email directly or view it on GitHub
#152 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants