Free Sections

Haskell Syntax Extension

by Andrew Seniuk

f 4 True

Char

Float

f :: Int -> Bool -> Char -> Float -> String f 4 True -- Char -> Float -> String

__

_[

…

]_

Philosophically, use of this sort of syntax promotes "higher-order programming", since any expression can so easily be made into a function, in numerous ways, simply by replacing parts of it with freesect wildcards. That this is worthwhile is demonstrated by the frequent usefulness of sections.

The complete table of Haskell expression translations probably forms the best set of examples I have to offer. Some other examples, including alternatives using context inferencing, and showing the translation to straight Haskell performed by FreeSect:

map _ [ div __ 2 ] _ [3,4,5] = map ( div __ 2 ) [3,4,5] = map (\ x -> div x 2 ) [3,4,5] -- FreeSect preprocessor output zipWith _ [ f __ $ g __ z ] _ xs ys = zipWith ( f __ $ g __ z ) xs ys = zipWith (\ x y -> f x $ g y z ) xs ys -- preproc. output

(`div` 2)

dreadme

__

map3 ( dreadme tableA __ (repeat True, __ ) (D __ [(+),(*)]) ) [tableA,tableB] [0..] [-1,1]

fs0

…

fs2

map3 (\ fs0 fs1 fs2 -> dreadme tableA fs0 (repeat True,fs1) (D fs2 [(+),(*)]) ) [tableA,tableB] [0..] [-1,1]

{-# LANGUAGE FreeSections #-} -- use "{- #" if compiling with ghc -F module Main where tableA = [ [1,2,3], [4,5], [6,7,8,9] ] tableB = [ [9,8,7], [6,5], [4,3,2,1] ] data D a = D Int [a] main = print v v = map3 ( dreadme tableA __ (repeat True, __ ) (D __ [(+),(*)]) ) [tableA,tableB] [0..] [-1,1] dreadme :: [[Int]] -> [[Int]] -> ([Bool],Int) -> D (Int->Int->Int) -> String dreadme tabA tabB (ps,q) (D wt flst) = show $ map (!!q) $ map2 f tabA tabB where f rowA rowB = map2 (<) (repeat rowA) $ map2' (filter' ps flst) rowB (map ((q+).(wt*)) rowB) map2 :: (a->b->c) -> [a] -> [b] -> [c] map2 f (x:xs) (y:ys) = (f x y) : map2 f xs ys map2 _ _ _ = [] map2' :: [a->a->a] -> [a] -> [a] -> [[a]] map2' (f:fs) xs ys = (map2 f xs ys) : map2' fs xs ys map2' _ _ _ = [] map3 :: (a->b->c->d) -> [a] -> [b] -> [c] -> [d] map3 f (x:xs) (y:ys) (z:zs) = (f x y z) : map3 f xs ys zs map3 _ _ _ _ = [] filter' :: [Bool] -> [a] -> [a] filter' (p:ps) (x:xs) = if p then x:filter' ps xs else filter' ps xs filter' _ _ = []

Some virtues of the new syntax

lambda forces the programmer to invent names for the wildcards

lambda forces the programmer to repeat those names, and place them correctly

freesect wildcards stand out vividly, indicating where the awaited expressions will go

reading the lambda requires visual pattern-matching between left and right sides of the lambda

lambda is longer overall, and prefaces the expression of interest with something like micro-boilerplate

it can achieve arbitrary permutations without further ado; but wildcards preserve their lexical order

it is more expressive when nesting is involved, because the variables are not anonymous

Implementation

freesect

cabal-install

Default context inferencing

Note: The following policy is flawed, as pointed out by <ski> on #haskell , since

<ski> this clearly means that `(f __) a' (meaning `(\x -> f x) a') is different from `f __ a' (meaning `\x -> f x a') If we could prove that these two interpretations cannot both be given consistent types in the same context, things wouldn't be so bad, but unfortunately taking f=(-) is enough to see there is no hope for that, as

(\x -> (-) x) 3 2 = (-) 3 2 = 1 (\x -> (-) x 3) 2 = (-) 2 3 = -1 Rum luck! As <ski> kindly observed, people don't often write (f x) y because application already associates left, but still, the policy is flawed because the Haskell semantics are not respected 100%. There may be other lurking problems which testing will eventually expose… : The following policy is flawed, as pointed out byon, sinceIf we could prove that these two interpretations cannot both be given consistent types in the same context, things wouldn't be so bad, but unfortunately takingis enough to see there is no hope for that, asRum luck! Askindly observed, people don't often writebecause application already associates left, but still, the policy is flawed because the Haskell semantics are not respected 100%. There may be other lurking problems which testing will eventually expose… To deal with this, since v0.8 the preprocessor issues a warning (and possible remedy) when freesect wildcards occur within redundantly-parenthesised applications.

A default context is automatically applied when the _[ … ]_ grouping syntax is omitted. Defining this default can be tricky, and a bit arbitrary, so for robust code it's best to use explicit bracketing. When the _[ … ]_ are omitted, the defaulting rules are as follows: The set of all unbracketed wildcards in a RHS are given context of their collective innermost enclosing parentheses, infix $ operator, or list brackets (whichever is tightest). If none of these delimiters is present, the whole RHS becomes the default context. Some of the examples demonstrate these rules.

One hopes that the default will result in a type error, should the inferred context differ from the intended. I haven't thought of any situation where more than one bracketing of freesect wildcards yield typeable expressions, but neither have I ruled it out. A context inference policy based on typeability tests among the candidate scopes would also be possible, although probably ill-advised.

Background

#haskell

<eyebloom>

<eyebloom>

<dolio> map (foo (g _) x) (h _) <dolio> Matter of fact, what does "map (foo (g _) x) (h _)" mean?

map (foo (g __ ) x) (h __ ) = \ a b -> map (foo (g a) x) (h b)

$

map $ (foo (g __ ) x) (h __ ) = map $ \ a b -> (foo (g a) x) (h b)

_[

…

]_

map _ [foo (g __ ) x] _ _ [h __ ] _ = map _ [foo (g __ ) x] _ (h __ ) = map _ [foo (g __ ) x] _ $ h __

map (\a->foo (g a) x) (\b->h b)

\b -> h b

Other details

Fresh identifiers generated by FreeSect are guaranteed to be unique among names referenced within a module. There is no danger of exporting these names inadvertently, as they never have top-level scope. Imported names which are in scope but never used will not incur conflicts.

Originally, the wildcard syntax was to be a single underscore, since it has no meaning in a RHS context in Haskell including any extension to date (with the exception of John Meacham's "magic underscore" in JHC, where it is a synonym for undefined ). However, I could not get rid of reduce/reduce conflicts in the HSE parser with single-underscore. Unfortunately, double-underscore is a legal identifier in Haskell, so it really would be better to get single-underscore working for freesect wildcards!

A few things don't work quite right yet, such as wildcards in list comprehension guards. Work continues…

How to run it

ghc -F

{-# LANGUAGE FreeSections #-}

Feedback

Kind Reg'ds,

Andrew Seniuk

<rasfar> on #haskell

Feb. 29, 2012