[Ur] Function `queryL1` returns type `transaction (list $fs)` - How can I get the `list $fs` part and pass it to `List.mapX` - which expects type `list`?
Istvan Chung
istvan at vivatropolis.org
Mon Jul 20 01:07:00 EDT 2015
On 07/20/2015 12:15 AM, Stefan Scott Alexander wrote:
> One last desperate plea for a shred of information: I do know that when
> something returns an Option, you have to use a case statement on Some or
> None to basically "unbox" it to get the non-monadic value out. Option is a
> monad. Here I'm dealing with a differnent Monad: a Transaction. I
> understand conceptually that I'm getting a `transaction (list $fs)` but I
> need a `list $fs`. Is there some way to "unbox" the transaction to get the
> non-monadic value out of it - the way we "unbox" an Option?
>
TLDR: It's not possible to "unbox a transaction", and that's not what
you want to do.
The reason you can "unbox" an option is because the insides of an option
are exposed to you, and you can pattern-match on them with a case
statement. However, this is just something nice about options, but it
doesn't apply to monads in general. In Ur/Web, you aren't given
anything else to do with a transaction except what you can do with any
monad.
So, what can we do with *any* monad? Well, we're in fact given only two
operations: [return] and [bind]. We can use [return] to do what you
would call "boxing": going from a pure value to a transaction. So the
value [3] is of type [int], but the value [return 3] is of type
[transaction int]. [return] is hopefully pretty simple to understand.
The only other thing we can do is [bind]. In order to figure out what
[bind] does, let's take a look at its type. (Note: I've simplified the
signature of [bind] to make it easier to understand. In this context,
[t1] and [t2] are type variables. You don't really have to worry about
them since the compiler infers them for you. They could be [int] or
[list float] or anything.)
bind : transaction t1 -> (t1 -> transaction t2) -> transaction t2
So what does bind do? Well, it's a function that takes as input both:
* A value of type [transaction t1]
* A function which takes a [t1] as input and returns a [transaction t2]
and as output returns a [transaction t2].
What does this mean? Well, essentially what [bind] does is create a
transaction which will do this when "run":
* "Run" the value of type [transaction t1] to get a [t1] (this is the
"unboxing" step)
* Feed that [t1] into the function which takes a [t1] and returns a
[transaction t2]
* "Run" the [transaction t2] that we got from the function.
You'll notice that I put "run" and "unboxing" in quotes. The reason for
this is that the actual "running" is behind-the-scenes: it happens
off-stage, in compiled C code (or javascript on the client). Why does
this matter? It means that there's no such thing as "running" from
inside the Ur language. Ur has no knowledge of "running". All it's
doing is combining a transaction and a function returning a transaction
into a single transaction.
You should think of a transaction as an object; a noun, not a verb. You
can create a transaction returning a single value (that's [return]).
You can create a transaction by piping the output of one transaction
into the input of a function returning a second transaction ([bind]).
What you can't actually do, in Ur/Web, is run a transaction. So there's
no such thing as "unboxing a transaction" inside Ur/Web.
So what you want to do is this:
Take the transaction that you get from the call to SQL. Use its output
to create a second transaction which will output some XML.
That sounds just like [bind]! So instead of trying to use the
transactions directly to build XML, since that doesn't make sense (what
would XML with a transaction inside look like?), use [bind] to make a
new transaction by "wrapping" the SQL transaction to return XML. Let's
say we have some value [sql_query] of type [transaction string] (say it
reads a string from the database). ([sql_query] would probably be the
result of a call to one of the [query*] family of functions) We can write:
bind sql_query
(fn s => return <xml><body>Your string is {[s]}</body></xml>)
What is this code really doing? It's just a call to [bind], returning a
pure value of type [transaction xml]. What happens when we "run" this
transaction? Behind the scenes, [sql_query] will be run, accessing the
database and giving us a string. Then, our lambda will be applied to
that string, and it will return the XML value with our string from the
database included.
Note that in Ur/Web, instead of writing [bind t (fn x => y)] we just
write [x <- t; y]. This looks like procedural code "assign the output
of [t] to [x], execute y", but it's actually a purely-functional call to
[bind]. So the example above can be rewritten:
s <- sql_query;
return <xml><body>Your string is {[s]}</body></xml>
This code is exactly identical to the code above. It looks prettier in
the second form, but the first form is more important to understanding it.
I'd recommend looking at the use of [bind] (aka "<-") in the "Sql" demo
at http://www.impredicative.com/ur/demo/ and seeing if you can recognize
this concept being applied.
Hopefully some of this will be helpful to understanding transactional
code. I think trying to understand the Ur/Web demos will probably be
helpful, and if you can find a good general description of Haskell's IO
monad, that could really go a long way (Note that Haskell's syntax is
different---but the idea is the same). Good luck!
--
Istvan Chung
More information about the Ur
mailing list