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
Comments
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. |
Like better type inference? Or what do you mean? Was it not an issue for >>- ? |
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) |
I like the monad on the left and the function on the right, but doesn't <^> currently use the reverse of that? |
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! |
(<^> 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. |
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). |
Sorry, when you said So why does 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! |
More importantly, why not be consistent in the syntax between |
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 |
We wish to enable declarative-looking code. Imperative-looking code is easy enough to do without this framework. |
Sure, but it's in the exact same "do" situation that you'd want map to be right to left, right?
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. |
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. |
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, |
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 |
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. |
@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 repeatTwice :: (Show a) => [a] -> [String]
repeatTwice = map (\x -> show x ++ show x) The interesting thing to me about 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 // 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 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!) |
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. :-) |
OK, let's try this again now that I've found a proper computer instead of my old iPhone:
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 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 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 |
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! |
The practical reason not to flip it is we provide it flipped in the Functor Typeclass, and we provide the combinator |
@refried Any further input, or has this thread run its course? |
Oh hello :) Well, to be honest, I was left with the feeling that I wasn't able to get
|
Then let's start over. Restate the question and I'll try to reply more in-depth this time. |
Thanks -- I guess to start, could you show a working Swift example of the
|
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 |
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. |
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. |
I understand that, but it doesn't help. My question was about Swiftz, which
|
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 ( 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>
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.
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. |
I think I must not be smart enough to know how to ask a question that you |
I'm a chronic overthinker :( If you think of a way to reformulate this question, reopen this issue. |
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! |
If you flip the types around, you get:
How would you do that instead now? Thanks! |
You has previously pointed out that
but is there an example in swift? |
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!
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 (
Yes! I'll show you with the Identity Functor (Box) out of laziness. Throw this in a playground
|
Does swiftz include an fmap implementation for every <^> implementation?
|
(Unfortunately for my poor fingers) Yes. We have to write an fmap for every Functor. |
:)
|
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!
The text was updated successfully, but these errors were encountered: