In the comments of last turtle post, Joel pointed out a way to get commands composable. I didn't quite get it to work, probably because of that Command is a type synonym and not a concrete type. The first time I saw the proposed solution, to combine commands with <|>, I got a little afraid. I'll explain why..
<Update>
Joel pointed out (in a mail) that Haskell98 doesn't permit instances of type synonyms or lists of types. If the flag "fglasgow-exts" is used, then the problem is solved.
</Update>
The biggest reason I got afraid was that it is still up to the consumer of the code to combine commands in a "good" way, i.e., to log intermediate positions. But, It would be perfectly legal to combine commands with (.) instead of (<|>), making the API quite risky. I now think a bit different..
It would be possible to kind of wrap the commands into a data structure of their own (as the Command pattern in OO):
> data C = C { unC :: Command }
In some way, this is very type-safe, since it will be cumbersome to un-wrap the commands to combine them in a "bad" way..
> go2 = C { (unC go) . (unC go) }
..thus, users are forced to use our special operator for combining commands. But there something I don't like about this. We're kind of forcing the users into a datastructure, just to restrict them. I think we loose a lot of nice functions (i.e. from the prelude) this way. Of course, we could define functions that makes it easier for the user, but the user still has to learn those functions. What I'm trying to say is that it sometimes is bad to invent a whole new API style from scratch, when for example a monadic style API has the benefit that more people know about it and that we can "piggyback" on all the tutorials on monads. Or maybe it's just me that is too focused on monads at the moment. ;)
I guess this could be a nice discussion? What do you think?
Right now, my biggest reason to be afraid of Joels solution is that it uses type classes, which is a very exotic feature, compared to other functional languages. There are obviously nice, but it would be hard to see how a "nice" Turtle Graphics API could be ported to i.e. F#, if we based the API on type classes.
Next: Changing interface on primitive commands
Wednesday, October 15, 2008
Subscribe to:
Post Comments (Atom)
4 comments:
Hi again, I totally agree on your point that the possibility of mixing (.) with (<|>) is "risky".
Therefore I don't really see why you would avoid using a wrapper data type like
newtype C = {unC :: Command}
Aren't the "nice" functions we loose from Prelude exactly the ones we need to be careful with?
Regarding the the type class issue. I see your point here as well. That is, if you build some Haskell code that later needs to be straight forwardly ported to F# or OCaml then you should avoid Haskell specific techniques.
However, in the case of (<|>) I dont think this applies. Actually, I managed to write the Command class and the (<|>) operator without to much of a hazzle in F#. Well, the Command type had to be turned into a class rather than an ADT but thats just a minor detail...
Sorry, my post was a bit ambigious. I meant that the solution with type classes is a hard port to another language. Of course, if the unC-solution is chosen, things get a lot easier.. :)
"Aren't the 'nice' functions we loose from Prelude exactly the ones we need to be careful with?"
Yep. But, to clarify, a user or "extender" of the language should never use unC, because it's potentially unsafe. If we ever saw such code, we would need to replace it with with something safe, thus extending our own language and limit the base language. Maybe it's a long-stretcher, but can you see what I mean?
Also, it could be interesting to see where the unC-solution is "going". I.e., you might recognize the type of |>| in the post after this..
[Turtle] -> (Turtle -> [Turtle]) -> [Turtle]
..Looks a bit like bind, doesn't it? ;)
You should start a blog Joel! You have so many good ideas! :)
"Sorry, my post was a bit ambigious. I meant that the solution with type classes is a hard port to another language. Of course, if the unC-solution is chosen, things get a lot easier.. :)"
I think I understood you correctly, so probably me who were "ambigious" in my answer:-)
The solution i ported to F# was the actually the one with the 'Command' type class. Sorry for spamming this thread with ugly F# code, but here is just to clarify:
type ToCommands =
abstract ToCommands : Command List
and Command =
{Cmd : Turtle -> Turtle}
member c.ToList = [{Cmd = c.Cmd}]
interface ToCommands with
member c.ToCommands = [c]
type Commands =
{Cmds : Command List }
interface ToCommands with
member c.ToCommands = c.Cmds
let (|>|) (c1 : #ToCommands) (c2 : #ToCommands) =
let cmds1 = c1.ToCommands
let cmds2 = c2.ToCommands
{Cmds = cmds1 @ cmds2}
So, my point is that while some type class techniques are not easily converted to the OO world others are. Heres an intresting article on the subject!
See section 4 for a very succint explanation.
Post a Comment