4

In Haskell, I'm used to doing stuff like this.

data Foo = Foo { foo :: String, bar :: String }

mFoo :: (Monad m) => m String -> m String -> m Foo
mFoo foo bar = Foo <$> foo <*> bar

The same does not work in purescript, however. Is there a way to achieve the same outcome, ie keep the record syntax while allowing partial application via applicative functors when building the instance?

Thanks!

Mike
  • 716
  • 4
  • 10
  • 4
    The type signature doesn't match the implementation. You mean `m String -> m String -> m Foo`, right? – amalloy Jun 19 '20 at 17:30
  • Mike could you please fix the signature in your question as it is already suggested by @amalloy above so other readers won't get confused ;-) – paluh Jun 19 '20 at 21:16
  • 2
    Also, such type definition is actually illegal in Haskell (unlike PureScript). You're missing a constructor. – Fyodor Soikin Jun 20 '20 at 03:39
  • 1
    Sorry about the typos! Thanks for spotting them and apologies for my carelessness. They're now fixed! – Mike Jun 20 '20 at 17:37
  • 1
    Mike... I think that `m` "wrapping" in the arguments in the function signature is still missing ;-) – paluh Jun 20 '20 at 18:21
  • I had changed the implementation to make it gel with the function signature, but now this question has been edited and I believe it is incorrect again in a different way. @biran-mckenna could you please change it back? – Mike Jun 27 '20 at 20:19
  • Mike the signatures are ok now thanks to @Brian McKenna fixes. Mike I think we don't want these `pure` calls. If we have just values we can apply function / constructor directly and not use pure and applicative api at all! I was able to fix this myself. – paluh Jun 29 '20 at 00:30
  • @il_raffa could you please explain what is wrong with my "edit" before rejecting it another time? Could you please also check if the question type checks too before you move it back to the previous form? – paluh Jun 29 '20 at 18:25
  • Either there needs to be no m in the function signature or no pure in front of foo and bar. As it stands now, I believe it is incorrect. – Mike Jun 29 '20 at 19:13
  • I think that we have correct signature now (`m` everywhere in the signature and no `pure` usage). Let's check the types: `<$> :: (a -> b) -> m a -> m b` `<*> :: m (a -> b) -> m a -> m b`, `Foo :: String -> String -> Foo`. Could you please check my answer below and explore the types using link to the try.purescript.org from there? – paluh Jun 29 '20 at 23:58
  • @Mike my `pure` removal was rejected second time. Could you please tell me what is the reason behind putting values into `Applicative` context by using `pure` and later using `lift2` or `<$>` and `<*>` if you could directly apply this function on arguments? Could you please fix the question and drop these `pure` calls? Currently this code just doesn't compile. – paluh Jul 01 '20 at 16:22
  • Hey! I'm not sure why it was rejected - it is correct either to remove the pure or to remove the monadic context from the incoming arguments. As the code stands it does not compile, and I'm not sure who edited it to be in this state. If it's ok, I can delete the question, as it is too much of a distraction from the main thing, which is the answer below. If that answer can be in the purescript cookbook, there's no reason to have this question on Stackoverflow. – Mike Jul 01 '20 at 19:02
  • @Mike, please don't delete the question please just fix it by removing `pure`. I've dedicated some time to create the answer, put it on try.purescript.org, edit the question and make all the comments above. Please respect this effort a bit. Going back to the code fix. There is nearly NO sens in using `pure` in this context and it can only confuse people. Without `pure` you could just do: `Foo bar baz` if they are not in `m` :-P – paluh Jul 01 '20 at 19:27
  • 1
    Done, and thanks for your time! Hopefully it won't get changed back again. – Mike Jul 02 '20 at 02:39

1 Answers1

9

In PureScript you don't have to define such a record type upfront. Polymorphic version:

mXY :: forall a b m. Apply m => m a -> m b -> m { x :: a, y :: b }
mXY foo bar = { x: _, y: _ } <$> foo <*> bar

-- | which can be rewritten using lift2:
mXY' = Control.Apply.lift2 { x: _, y: _ }

And a monomoprhic equivalent:

type Foo = { x :: String, y :: String }

mFoo :: forall m. Apply m => m String -> m String -> m Foo
mFoo = Control.Apply.lift2 {x: _, y: _ }

A "live demo" using wonderful try.purescript.org + custom gist:

https://try.purescript.org/?gist=a37f5f0c50e0640e34ea5a4788c0c999

Of course there is a practical value in using a newtype around Record too like:

newtype Foo' = Foo' { x :: String, y :: String }

and in such a case I would propose something like:

mFoo'' foo bar = map Foo' $ { x: _ , y: _ } <$> foo <*> bar

but I think that this could be expressed in a more elegant and shorter way which I don't know :-)

EDIT:

This is probably even nicer syntax using ado:

ado
  x <- foo
  y <- bar
  in Foo { x, y }
paluh
  • 2,171
  • 20
  • 14