I've been working on a monadic macro processor the last few days and
would like to release the first version. Check it out at
http://code.google.com/p/hxmonads/
Most I have to say about it is posted on the intro and example pages, so
I won't repeat everyhing here. Try it if you have use for stuff like
this and check the asynchronous possibilities in particular. Also,
please report any bugs you encounter! I extensively tested it with
arrays on the platforms supported by munit, but there might still be
issues on other platforms and with other monadic types.
Regards
Simon
I must say the wiki examples are really helpful. I'm really curious about the handling of the asynchronous methods.
--
To post to this group haxe...@googlegroups.com
http://groups.google.com/group/haxelang?hl=en
I just committed the FlashIO class I used for the Flash Console example,
check it our here:
http://code.google.com/p/hxmonads/source/browse/trunk/misc/FlashIO.hx
You should be able to run the wiki example with it. getLine()'s job is
it to return a Future (which is currently initialized with null, I will
change that to Option later) and connect that Future's complete() method
to whatever event completes it. Here it's just pushed onto an Array and
each enter keyboard event pops the latest future and completes it with
the input.
It should be easy to write connector methods like getLine in this
example. Candidates are functions like queryDatabase(), loadPage(),
delayMe() and any other async interface you may encounter.
Regards
Simon
What do you mean? Integrate into the compiler proper? If so, that was my
first thought. I like how the hxmonad future works, but I'd prefer to
have a standard lib Future, being used by a standard monad impl. Anyway
great to see all this effort, and the benefits macros bring to the table.
R
--
Simplicity is the ultimate sophistication. ~ Leonardo da Vinci
I want standard Future (and Option) too. My Future implementation is
little more than a proof of concept and could easily be replaced by a
proper implementation. I considered using the Stax Future, but I didn't
want to add a dependency just for that.
Integrating monads into the compiler would be great, but I don't see
that happening anytime soon. I actually wanted to give it a try, but I
wouldn't even get as far as setting up a proper incremental OCaml
compilation for haxe.
Regards
Simon
What do you mean by extensibility? Making it work for new types is as
easy as writing the monadic class and either put it into hxm.monads
folder or use Monad.addMonadPath("mymonads") to let the processor know
where to look. Then, if the name relationship to the base class is not
simply BaseName + "Monad", you can use Monad.addAlias("FastList",
"Iterable").
There's even a hxm.core.Resolver.defineFunc method to define own monadic
functions, which will be inlined as expressions into your code. That's
how the guard function is currently implemented, although I don't really
like this approach anymore because it's impossible to get code completion.
Simon
You should consider using git. Satisfying dependencies on project import is as simple as:
git submodule initgit submodule update
There is a version here which has most of the principle improvements I was looking for, and some explanation. Feedback would be appreciated, I think a wee bit more discussion is to be had before pushing to master.
regards,Laurence
--
--
Ah thanks, it works just fine after renaming complete to deliver and
addListener to deliverTo. I consider basing my monadic classes on the
Stax classes. It makes sense for Option and Future, but what other
classes could use a monadic behaviour?
Simon
Do you really propagate this kind of "poor mans dependency management"?
Exapmle:
lib-a has git submodule tink_macros (hash e784e )
lib-b has git submodule tink_macros (hash 45434 )
your app depends on both..
How could it look like instead?
lib-a has a file "{depneds-on: [ "tink_macros > 2.0" ]}"
lib-b has a file "{depneds-on: [ "tink_macros > 1.0" ]}"
haxe automatically understands that either version will suffice and pick
the latest.
This would scale much better IMHO.
Does something like this already exist?
> > [ ..] but I wouldn't
> > even get as far as setting up a proper incremental OCaml compilation for
> > haxe.
Can you explain little more?
Marc Weber
I just checked your version of Stax more thoroughly. While certainly
much better than what I remember it to be, there are still scents of
dependency hell. For example, Future uses OptionOps, which happens to
throw a Stax.error in one place, which causes the evil empire of Stax.hx
to be included, which pretty much includes everything. However, the
binary size penalty is not that bad, around 40kb for a .swf. It just
feels rather unclean having to include all that stuff when all you want
is a simple Future/Option implementation.
I think the basic classes (Option, Future, Either... I might be missing
some) need even more separation from some of their fancy methods that
require inclusion of other modules. I would actually release them and
their Ops classes as a separate core library that might have prospects
of making it to the real std. This should be within reason if we focus
on keeping it small and simple. What do you think?
Simon
Simon
Using ocamlbuild, it would _always_ recompile the whole project, even if
I changed one of the gen-classes. I couldn't figure out what ocamldep's
problem was and eventually just gave up because no one else on the
internet seemed to have that problem. Or explaining in German puns: I
guess I was too much of an Ocamldepp to use ocamldep.
Simon
Unfortunately, that's the best argument against a std inclusion and the
reason we have to find a lowest common denominator, which might not be
much more than the pure data definitions and very few extension methods.
It's easy to make an argument for OptionOps.get() and
OptionOps.toOption(), but OptionOps.zipWith() and OptionOps.getOrElse()
are not std material, as I'm sure even the hardcore Lambda crowd will
agree on.
We really need a std committee for this kind of stuff.
Simon
See here:
http://lists.motion-twin.com/pipermail/haxe/2010-July/036911.html
and http://lists.motion-twin.com/pipermail/haxe/2010-August/037532.html
I have to admit that bin/haxe occasionally segfaults when using the
makefile. Because I didn't do any additional hacking (except the short
lamdas patches) I didn't investigate..
I would like to fix all those 'documentation issues' - but I'm blocked
:(
There have been no replies to "in source documentation" either.
About the same issue I asked Franco. He told me he just wrote his own
script recompiling the backend and link that.
In anyway the compilation of haxe is not friendly to devs.
Yes - I do "scream" - but only because it hurts seeing improvements
rejected - and then seeing other devs spending time on it again.
Most up to date version can be found here and should apply to current
HaXe version: http://mawercer.de/~marc/install-ml.patch
Marc Weber
Yes, and that is the basic use of an option: you separate having some
value from having no value. That's what normal people do with Option,
they conditionally return it from some function and switch on it in
order to branch, and that's in my opinion what the std should cater to.
I'd even accept no functionality at all, which might not make much sense
in the context of Option. Consider Future though, I just made an
implementation compatible by renaming some method calls. It would be so
much better if the std had a typedef defining what a Future has to have
_in a minimal implementation_. You can then still use
UltraLambdaFutureLib and it will be compatible as long as it upholds to
the laws set be the std.
So yes, I think the first step is to define a set of interfaces (which
would be typedefs) in the std for commonly used data structures. Only
then can the second step be taken, which is providing implementations
for the commonly used operations on these commonly used data structures.
Also, we need some naming conventions. OptionExtension, OptionExt,
OptionEx, OptionOps, OptionFuncs... it's pointless arguing here because
none is better than the other. So Nicolas should just state his
favorite, we hide that information somewhere in the docs and point
anyone not employing it there.
Simon
hxmonads:
+ completion
+ determines type (by using Context.type and some "unification" magic)
Thus you can get rid of the MonadType.do({ ..) and Monad.do is enough
+ definitely supports "nested monads" by simple syntax.
- implements the yield operator the HaXe way - thus if you would
implement State or parser like structures it may not be as optimezd
as teh map like interafce for monax?
Stephane, Simon: Am I correct about what I say that those are the main
differences ? Did I misse anything else?
If thats correct how do you feel about merging?
Marc Weber
The yield operator has been a big issue for me because returning [a] is
totally impractical from a performance point of view. I'm not sure how
to optimize this and any input is very welcome! Are there any examples
of Monax working on iterables that clearly show the difference?
Stephane really seems to be into optimizing this kind of stuff, which is
definitely hxmonad's weakness at the moment. So I see plenty of room for
collaboration too.
Simon
There's a difference though: a nested monad is bound to an identifier:
y <= [1];
x <= { ... } // Nested
y <= [1];
{ ... } // Normal code block
Simon
Ah, I see your point. In that case I will change it to the following
behaviour:
y <= [1];
x <= { ... } // block is expected to return a monadic value (Array)
z <= return { ... } // Nested, can return anything. Value will be lifted.
That doesn't compile for me:
com.mindrocks.monads.Option<Array<Int>> has no field monad
Simon
Well that's subtle... It compiles now, but I get an exception:
VerifyError: Error #1014: Class com.mindrocks.monads::MonadOp could
not be found.
Removing the #if macro ... #end part within that enum makes it work.
Simon
What about introducing "do" ?
Monad.do{
x <= do({
...
})
}
which is then replaced by Monad.do?
I'm not sure its good to create many additional "HaXe specific" rules
you have to learn.
your proposal return {} behaving like {} would .. is making things
counter intuitive.
Whatever you do - I'll be happy.
And having such "do" => "Monad.do" rewriting could be used for more
purposes. Eg if you have a parser and if you want your own "extensions"
you could tell a "monad builder" to rewrite spaces by MyParser.spaces
etc.
Marc Weber
I had that idea too at some point, but the syntax is rejected by the
compiler with do being a keyword that expects a while.
> your proposal return {} behaving like {} would .. is making things
> counter intuitive.
Yes, I already agreed to drop that behaviour. I also might have to drop
the usage of return because with normal code blocks, there are pretty
much two kinds of return: the normal haxe return and the monadic yield.
I'm not sure yet how to go on about this.
Simon
If we want shorter syntax we should rewrite the parser so that we can
have ruby or scala like syntax for objects and methods, ...
We could also make HaXe allow "do" in that context.
Marc Weber
It might be Flash target related. Seems like the empty enum is
completely omitted from the output swf. Might be default behaviour for
swf output, I'm not sure. You can also avoid the problem by putting your
#if enum around the whole macro-only functions, not into their
definitions like you do with _dO.
Simon
I don't get why this requires a different syntax. You should be able to
infer the type of block by checking its content: if there's a <= bind,
it's automatically a nested monad, else it's a normal block. This should
even be sufficient to determine the type of return to use.
What am I missing?
Simon
or {{ as you told if that works. We sohuld not overload {} (HaXe blocks) with monadic
behaviour.
Having one "key word" which means "attention! this is a monad" such as
"dO" will be more helpful than having to remember <{ and Monad.do.
I'd vote for
Monad.dO({
x <= return dO {
}
})
Then all people knowing monads understand what's happening without
looking up documentation.
Marc Weber
instead of
do {
a <= foo()
b <= bar()
return a + b;
}
Marc Weber
I see, that makes sense. So we really need a dinstinguishable syntax for
both cases I guess. I'm a little afraid of this making the whole thing
hard to grasp for "normal" users, as you can see I'm already having
trouble recognizing the difference.
I might stick with the infer-it-approach, but think of a syntactic way
to mark blocks where inference fails as "monadic".
Simon
You need a use case?
do {
expect_text "["; // future monad : send "[" to server, parser monad: consume "["
expect_many (one_of "012356");
expect_text "]";
}
In that case the monadic version differs from a non monadic because it
processes input.
Without monad sugar, what would happen?
future case: all requests would be sent, but order would not be
guaranteed (because waiting for replies would not happen)
monad case: nothing because expect_text would return a function
processing parser state.
There is a difference :)
Marc Weber
Simon