Re: [stack] typing higher-order concatenative languages with standard hindley-milner
"Christopher Diggins" <[email protected]>
| Newsgroups | gmane.comp.lang.concatenative |
|---|---|
| Message-ID | <[email protected]> |
It can't infer much, and as Okasaki points out the whole system doesn't work in practice for even tiny non-trivial programs. On Sat, May 31, 2008 at 1:28 AM, John Nowak <[email protected]> wrote: > Okasaki's paper "Techniques for Embedding Postfix Languages in > Haskell" shows how to implement a crude Forth-like language in > Haskell. I won't go over how it works here, but you should be able to > gather what's happening just by looking at the examples below. > > Unfortunately, the language discussed is a first-order language, not a > higher-order language like Joy, Cat, Factor, etc. It turns out that > extending the language is trivial. For example, here are quite a few > common functions, some of which are higher order ('swap' and 'dup' are > taken from the paper): > > swap :: ((s, a), b) -> ((s, b), a) > swap ((s, a), b) = ((s, b), a) > > dup :: (s, a) -> ((s, a), a) > dup (s, a) = ((s, a), a) > > quote :: (s, a) -> (s, s1 -> (s1, a)) > quote (s, a) = (s, \s1 -> (s1, a)) > > i :: (s, s -> s1) -> s1 > i (s, f) = f s > > dip :: ((s, a), s -> s1) -> (s1, a) > dip ((s, a), f) = (f s, a) > > keep :: ((s, a), (s, a) -> s1) -> (s1, a) > keep ((s, a), f) = (f (s, a), a) > > compose :: ((s, a -> b), b -> c) -> (s, a -> c) > compose ((s, f), g) = (s, g . f) > > Additionally, we need a 'push' function that will let us put values > (including quotations) on the stack directly: > > push :: a -> s -> (s, a) > push a s = (s, a) > > We can then use these functions via the composition operator (as > expected perhaps). For example, we can write Cat and Factor's > (unfortunately named?) 'curry' function as such: > > -- equivalent to '[quote] dip compose' > curry :: ((s, a), (s1, a) -> b) -> (s, s1 -> b) > curry = compose . dip . push quote > > This all seems to work fairly well. What's interesting is that it > breaks down in the same places that Fifth's current system breaks > down. For example, here's our 'bi@' combinator once again: > > -- equivalent to Factor's '[dip] keep call' > bi_at = i . keep . push dip > > This gets the unfortunate type '(((s, a), a), (s, a) -> s) -> s'. This > is equivalent to the Fifth type 'S a a (S a -> S) -> S'! It's the > exact same problem. We can try my backwards 'bi@' combinator (which > calls the quotation on the top element first, unlike Factor) as well: > > -- equivalent to 'dup dip dip' > bi_at_backwards = dip . dip . dup > > This gets the type '(s, s -> (s, a)) -> ((s, a), a)'. This is > equivalent to the Fifth type 'S (S -> S a) -> S a a'! Again, the exact > same problem. > > What's very interesting to note here is that we're getting the same > results as Fifth *without* the need for row variables. Perhaps row > variables are not necessary? > > What about fixed-arity combinators? We know that we need these in > order to handle callbacks, infra, things like 'bi@', and so on. For > example, here's a version of 'dip' that only works with quotations of > arity 1->1: > > -- used to represent the bottom of the stack > data Bot = Bot > > -- the type signature is *required* here otherwise we get this > overly > -- general type: (((s, a), b), (Bot, a) -> (c, d)) -> ((s, d), b) > -- the type below is equivalent to 'S a b (a -> c) -> S c b' > dip1_1 :: (((s, a), b), (Bot, a) -> (Bot, c)) -> ((s, c), b) > dip1_1 (((s, a), b), f) = ((s, snd (f (Bot, a))), b) > > We can then use this to write my backwards 'bi@' combinator such that > you can actually use it properly: > > bi_at_backwards_yet_useful = dip1_1 . dip1_1 . dup > > This gets the type '(((s, a), a), (Bot, a) -> (Bot, b)) -> ((s, b), > b)'. This type is equivalent to the Fifth type 'S a a (a -> b) -> b > b', and hence, this version of 'bi@' is useful! > > There's something very important to note here: It is impossible to > write 'dip1_1' simply by restricting the type of 'dip' as it is in > Fifth. This is because Haskell is based on Hindley-Milner and has > principal types. Since 'dip1_1 dip1_1 dup' and 'dip dip dup' yield > types such that neither is more general than the other, it roughly > follows that neither the type of 'dip1_1' or 'dip' is more general > than the other, and hence restricting 'dip' to yield 'dip1_1' is not > possible. > > In short, it seems standard Hindley-Milner alone is enough to do > everything we want! As a downside, we need several versions of fixed- > arity higher order combinators like 'dip' and 'i'. This is not a > problem as the general versions can be used in cases where there's no > need for fixed arities (i.e. most of them). This gives us a > concatenative language that's truly useful and never requires type > annotations. > > Down with row polymorphism! Well, maybe not quite yet. I still have a > few more things to look into. This does, at least, seem to be very > promising. > > - John > >