Skip to content

Instantly share code, notes, and snippets.

@jspahrsummers
Created July 15, 2012 01:15
Show Gist options
  • Save jspahrsummers/3114225 to your computer and use it in GitHub Desktop.
Save jspahrsummers/3114225 to your computer and use it in GitHub Desktop.
Hypothetical messaging primitive for Objective-Haskell
mutableStringWithString :: Id -> IO Id
mutableStringWithString str =
getClass "NSMutableString" <.> stringWithString str
arrayWithObjects :: [Id] -> IO Id
arrayWithObjects objs = do
getClass "NSArray" <.> arrayWithObjectsAndCount objs (length objs)
@jonsterling
Copy link

Cool. I assume stringWithString :: Id -> IO Id?

@jspahrsummers
Copy link
Author

@jonsterling No, it has to take a receiver and the argument, so stringWithString :: Id -> Id -> IO Id.

Though perhaps I erred in my choice of arrayWithObjectsAndCount. Something that could just take a function with any number of Ids would be good enough.

@jonsterling
Copy link

Oh, of course. My bad.

@jonsterling
Copy link

K. Just a minute.

@jonsterling
Copy link

So, the question of variadic functions is probably a distraction here, since currying will do what you want. The only problem you will fall into is argument ordering. If the receiver parameter came last, for instance, then (>>=) would be sufficient!

getClass :: String -> IO Id
arrayWithObjectsAndCount :: Num a => [Id] -> a -> Id -> IO Id

arrayWithObjects :: [Id] -> IO Id
arrayWithObjects xs =
    getClass "NSArray" >>= arrayWithObjectsAndCount xs (length xs)

@jonsterling
Copy link

This approach also has further advantages: you should be able to compose message expressions in a point-free style using Kleisli composition: (>=>).

@jspahrsummers
Copy link
Author

@jonsterling It's true that it would solve a lot of problems, but I think it'll be harder to understand for simpler cases, or imperative-style code in a do, which right now can do infix message sends like:

mutableStringWithString str = do
    nsstring <- getClass "NSString"
    nsstring `stringWithString` str

@jspahrsummers
Copy link
Author

@jonsterling Now, maybe declMethod could define two functions – one with the receiver at the beginning, and one with it at the end. That might just exacerbate the confusion, though.

@jonsterling
Copy link

Yeah, you have a point with do-notation. Don't think generating two functions is the right solution. What we really want is a kind of long-distance flip combinator which makes a function take its first argument at the end. But I'm not sure that that is possible in the general case.

@jspahrsummers
Copy link
Author

Alternatively, maybe we could do receiver at the end, along with a simple alias for return:

concatNSStrings :: Id -> Id -> IO Id
concatNSStrings a b =
    @ a >>= stringByAppendingString b

@jonsterling
Copy link

Hmmmmm. Maybe; not totally sure I dig it. I'm coming up with something hopefully and will be back in a few minutes.

@jonsterling
Copy link

OK. I sort of came up with such a combinator as I mentioned, but it's basically useless, since infix operators bind looser than function application.

(Additionally, your @ won't work, because symbols can only be infix operators.)

@jonsterling
Copy link

Furthermore, return x >>= blah is usually a pretty clear sign that you shouldn't be using monadic binding in the first place! In fact, what you want for that instance is just flip ($). So, dig this:

(@.) = flip ($)

concatNSStrings :: Id -> Id -> IO Id
concatNSStrings a b = 
    a @. stringByAppendingString b

@jspahrsummers
Copy link
Author

Right, but the @ was to solve the problem of a variadic application. Also, it could be written (@) if need be (to get around the infix restriction).

@jonsterling
Copy link

Yeah, still not necessary if you're going to put the receiver at the end. So, you could do

concatNSStrings :: Id -> Id -> IO Id
concatNSStrings a b = (@) a stringByAppendingString b

if you want, but it doesn't really make a difference. (@) is still just flip ($), with no bind in site. With currying, we have stopped caring about variadic nonsense.

@jspahrsummers
Copy link
Author

I'm talking about this case:

f obj a b =
    (@) obj >>= somethingWithTwoArguments a b

Unless I'm missing something, flip ($) can't do that.

@jonsterling
Copy link

Ah, gotcha. You still won't be able to use (@) as an alias for return, since infix operators have to take two arguments.

And flip ($) will work fine, because its function argument is partially applied; remember that function application binds tighter than infix operators. So, the following works:

(@.) = flip ($)

putFourStrings :: String -> String -> String -> String -> IO ()
putFourStrings a b c d = putStrLn a >> putStrLn b >> putStrLn c >> putStrLn d

"fourth" @. putFourStrings "first" "second" "third"
-- first
-- second
-- third
-- fourth

(In addition: the very fact that return x >>= f a b c can do what you want it to indicates that x @. f a b c can do the same. In fact, the two are provably equivalent).

@jspahrsummers
Copy link
Author

Hmm, interesting. I'll make sure to add that. Thanks!

@jspahrsummers
Copy link
Author

So, this works for the example you gave, and for any Id -> … function, but it doesn't seem to work for IO Id -> …:

mutableStringWithString :: Id -> IO Id
mutableStringWithString str = (getClass "NSMutableString") @. stringWithString str

Is there some way to make an <@.> operator for lifting + applying + joining?

@jonsterling
Copy link

Yep! But you don't need to make a new operator. It's called (>>=). ;-)

mutableStringWithString str = 
    getClass "NSMutableString" >>= stringWithString str

@jspahrsummers
Copy link
Author

That appears to drop str into the self position of stringWithString, though.

@jonsterling
Copy link

Nope. Remember, we're putting the receiver last.

@jspahrsummers
Copy link
Author

You're right. Sorry, I've been hopping back and forth between a few different mental models today.

@jonsterling
Copy link

;) no worries

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment