Have J style adverbs, forks etc been emulated via libraries in mainstream functional languages?
Asked Answered
W

2

25

Has an emulation of J style of super condensed tacit programming via verbs, adverbs, forks, etc., ever been attempted via libraries for mainstream functional languages?

If so, how successful was the result?

If not, is there a technical issue that makes this impossible, or is it just not worth doing?

I'm particularly interested in constructs like forks that don't appear to correspond directly to basic concepts in functional programming.

Whiteeye answered 3/8, 2010 at 17:3 Comment(1)
I have no idea but I love the question!Desiderate
W
8

Camccann's discussion is pretty good. But note that this style now results in two traversals.

You can write a combinator library that merges the traversals. See here: http://squing.blogspot.com/2008/11/beautiful-folding.html

The post offers the following example for writing a mean:

meanF :: Fractional a => Fold a a
meanF = bothWith (/) sumF (after lengthF fromIntegral)

mean :: Fractional a => [a] -> a
mean = cfoldl' meanF

Also, Conal Eliott's followup posts generalize this much further: http://conal.net/blog/posts/enhancing-a-zip/

He pulled the code from his posts into a library available on hackage: http://hackage.haskell.org/package/ZipFold

Westcott answered 3/8, 2010 at 18:38 Comment(3)
+1 -- I was indeed talking only about style, not implementation. There are a host of issues associated with actually making it practical, most of which could be built as a library of recursive combinators. See also "stream fusion" for a related concept.Hereditable
Very interesting - this really does seem to emulate some of the kind of things that a J interpreter does. If used in a restrained way they could lead to quite elegant code. Any idea how much these ideas are catching on in practice?Whiteeye
mean lst = lst |> dup (sum, length) |> uncurry (div) where dup (f1, f2) = \x -> (f1 x, f2 x) and a |> f = f aItinerate
H
12

Doesn't tacit programming correspond pretty closely to combinator logic or pointless point-free style in Haskell? For instance, while I don't know J from what I gather a "fork" translates three functions f, g, and h and an argument x into an expression g (f x) (h x). The operation of "apply multiple functions to a single argument, then apply the results to each other in sequence" is a generalization of Curry's Schönfinkel's S combinator and in Haskell corresponds to the Applicative instance of the Reader monad.

A fork combinator in Haskell such that fork f g h x matches the result specified above would have the type (t -> a) -> (a -> b -> c) -> (t -> b) -> t -> c. Interpreting this as using the Reader functor ((->) t) and rewriting it for an arbitrary functor, the type becomes f a -> (a -> b -> c) -> f b -> f c. Swapping the first two arguments gives us (a -> b -> c) -> f a -> f b -> f c, which is the type of liftA2/liftM2.

So for the common example of computing the average, the fork +/ % # can be translated directly as flip liftA2 sum (/) (fromIntegral . length) or, if one prefers the infix Applicative combinators, as (/) <$> sum <*> fromIntegral . length.

If not, is there a technical issue that makes this impossible, or is it just not worth doing?

In Haskell at least, I think the main issue is that extremely point-free style is considered obfuscated and unreadable, particularly when using the Reader monad to split arguments.

Hereditable answered 3/8, 2010 at 17:46 Comment(5)
As a slightly depressing aside, this answer, in which the second sentence says "...while I don't know J...", has given me the second-highest number of upvotes for answers in the j tag. SO needs more J programmers!Hereditable
In addition this question now has the most upvotes in the J tag. And, I've never actually written and executed a J program. I guess I know about it though - in fact I stayed at Ken Inverson's place in Toronto for a weekend in 1995 (my dad was visiting to Ken work on J stuff, and I was doing my PhD in CMU - yeah, I guess I'm second generation PLer).Whiteeye
BTW - nice answer. I was thinking along similar lines, but you've put it better. One difference is that in J the fork is syntactically implicit, as are hooks. I doubt that could be emulated, and personally implicit forks and hooks seem like they are going to far to me. The other difference is the implementation which sclv covered - and J actually does handle large (and even huge) arrays quite efficiently.Whiteeye
@RD1: Some stuff could be made more implicit using scary type metaprogramming hacks, along the lines of the tricks for writing polyvariadic functions, but it still wouldn't be the same and would make actually writing "verbs" more painful. Capturing that authentic Iverson-Backus flavor might be possible with some Template Haskell/quasiquotation magic, and as a bonus the TH could also perform some fusion optimizations and such to improve efficiency. But at that point you're barely writing Haskell anymore...Hereditable
For the applicative style, see the idiom brackets of mcbride's she preprocessor: personal.cis.strath.ac.uk/~conor/pub/she/idiom.html (This should let you write really pretty parsers, by the way).Westcott
W
8

Camccann's discussion is pretty good. But note that this style now results in two traversals.

You can write a combinator library that merges the traversals. See here: http://squing.blogspot.com/2008/11/beautiful-folding.html

The post offers the following example for writing a mean:

meanF :: Fractional a => Fold a a
meanF = bothWith (/) sumF (after lengthF fromIntegral)

mean :: Fractional a => [a] -> a
mean = cfoldl' meanF

Also, Conal Eliott's followup posts generalize this much further: http://conal.net/blog/posts/enhancing-a-zip/

He pulled the code from his posts into a library available on hackage: http://hackage.haskell.org/package/ZipFold

Westcott answered 3/8, 2010 at 18:38 Comment(3)
+1 -- I was indeed talking only about style, not implementation. There are a host of issues associated with actually making it practical, most of which could be built as a library of recursive combinators. See also "stream fusion" for a related concept.Hereditable
Very interesting - this really does seem to emulate some of the kind of things that a J interpreter does. If used in a restrained way they could lead to quite elegant code. Any idea how much these ideas are catching on in practice?Whiteeye
mean lst = lst |> dup (sum, length) |> uncurry (div) where dup (f1, f2) = \x -> (f1 x, f2 x) and a |> f = f aItinerate

© 2022 - 2024 — McMap. All rights reserved.