ADT views Re: [Haskell] Views in Haskell

David Roundy droundy at darcs.net
Thu Feb 1 12:12:02 EST 2007


On Wed, Jan 31, 2007 at 09:28:30PM +0300, Bulat Ziganshin wrote:
> Wednesday, January 31, 2007, 7:12:05 PM, you wrote:
> >> data Coord = Coord Float Float
> >> view of Coord = Polar Float Float where
> >>   Polar r d                    =   Coord (r*d) (r+d)    -- construction
> >>   Coord x y   | x/=0 || y/=0   =   Polar (x*y) (x+y)    -- matching
> 
> > This is somewhat pretty, but in spite of your desire to avoid creating new
> > syntax, you have just done so, and in the process made views more limited.
> > Pattern matching sytax remains the same, but a new declaration syntax has
> > been added.  And now in order to pattern match on a function it needs to
> > explicitely be declared as a "view".
> 
> yes. among the possible uses for views i clearly prefers the following:
> definition of abstract data views that may differ from actual type
> representation. moreover, i think that this facility should be
> syntactically indistinguishable from ordinary data constructor patterns
> in order to simplify learning and using of language. *defining* view is a
> rare operation, using it - very common so my first point is that views
> should be used in just the same way as ordinary constructors, both on
> left and right side:
>
> f (Polar r a) = Polar (r*2) a

I guess your assumption that views will rarely be defined is rather in
conflict with the proposal of Simon, which was to create considerably more
powerful views, I presume.

> Next, i don't think that ability to use any functions in view buy
> something important. pattern guards can be used for arbitrary
> functions, or such function can be used in view definition. view,
> imho, is not a function - it's a two-way conversion between abstract
> and real data representation which has one or more alternative
> variants - just like Algebraic Data Types. so, when defining a view, i
> want to have ability to define exactly all variants alternative to
> each other. for another representation, another view should be
> created. so

But you *are* using functions in views, that's what they are.  And the
two-way conversion, while pretty, is likely to be a fiction.  It'll be too
easy (and useful) for someone to define

view RegexpMatch String of String where
    string | matchesRegexp regexp string = RegexpMatch regexp
    RegexpMatch regexp = undefined

f (RegexpMatch "foo.+bar") = "It has foo bar in it"
f s@(RegexpMatch "baz.+bar") = s ++ " has baz bar in it"

You can pretend that noone will do this, but it's a nice syntax for pattern
guards, which allows us to stick the guard right next to the data being
guarded, which is often handy.

So I guess you can see this as a promise to subvert your two-way conversion
views immediately after they're created.  It's worth considering whether
one should try to make the syntax friendlier to such uses.  One option
which is sort of in between would be something like:

view regexpMatch String of String where
    string | matchesRegexp regexp string = regexpMatch regexp

f (regexpMatch "foo.+bar") = "It has foo bar in it"
f s@(regexpMatch "baz.+bar") = s ++ " has baz bar in it"

where the lowercaseness of "regexpMatch" indicates that this is a one-way
matching function.  I believe this would work just fine, and then we'd have
a bit of new syntax for "function-like" views, and your constructor-like
syntax for "constructor-like" views.  And noone would be tempted to subvert
your constructor-like views.  And good programmers would have a policy that
constructor-like views would really be invertible, for some definition of
invertible, analogous to the monad laws, which aren't enforced, but
reasonable programmers obey.

> view Polar Float Float of Coord where
>   constructor (Polar r a) means (Coord (r*sin a) (r*cos a))
>   match pattern (Polar (sqrt(x*x+y*y)) (atan(y/x))) for (Coord x y) where x/=0
>                 (Polar y (pi/2))                    for (Coord x y) where y>0
>                 (Polar (-y) (-pi/2))                for (Coord x y) where y<0
> 
> of course, my syntax is cumbersome. that is important is that view
> definition should be explicit (no arbitrary functions), it should
> mention all possible alternatives and provide a way to use the same
> constructor name both for construction of new values and matching
> existing ones. this all together should allow to transparently use ADT
> views instead of plain ADTs

I definitely agree that being able to transparently switch a library
between views and exported constructors would be handy, but don't think
it's necesary, provided the view syntax is sufficiently elegant (which I'm
not convinced Simon's proposed syntax is).  If views have a distinct--but
pretty--syntax, people can just move to always using views, and that's
that.

> > And unless you are planning to allow one-way views (you don't give any
> > examples of that), "view functions" must be invertible, which greatly
> > weakens their power.  If you choose to allow one-way views (non-invertible
> > functions), then I'd vote for not allowing two-way views, as it adds
> > complexity without adding any appreciable gain.
> 
> > I don't like your use of capital letters for ordinary functions, I enjoy
> > having the syntax tell me whether (Foo 1) might or might not be an
> > expensive operation.
> 
> the whole idea of abstraction is to not give users any knowledge aside
> from algorithmic specifications. when you write (x+y) you don't know
> whether this (+) will end in ADD instruction or sending expedition to
> Mars :)  why you need low-level control over data matchers exported by
> library but not over its functions?

Granted.  It's not necesary, but I find that it can be handy to have a bit
of syntactic information about cost.

> > Finally, you've replaced Simon's explicit incomplete function using Maybe
> > with an implicit incomplete function that returns _|_ when the view doesn't
> > match.
> 
> it's an independent idea that can be used for Simon's syntax or don't
> used at all. really, we need Prolog-like backtracking mechanism, i.e.
> way to say "this pattern don't match input value, please try the next
> alternative". Simon emulated backtracking with Maybe, one can does the
> same with return/fail, i figured out one more way - just allow
> recursive use of function guards.

I am less troubled about this than I was before, but I still don't like the
implied inequivalence.  I like to assume that pattern matching and guards
can be translated into if statements with error, and with your syntax I'm
not sure whether this is true.  i.e. what happens if I wrote

data Coord = Coord Float Float
view of Coord = Polar Float Float where
  Polar r d  =   Coord (r*d) (r+d)    -- construction
  Coord x y  =   if x /= 0 && y /= 0
                 then Polar (x*y) (x+y)    -- matching
                 else undefined            -- not matching

Is this an invalid bit of code, i.e. for your views syntax, are you reusing
only a subset of the function syntax? Or is this valid code that causes an
error when you match

f (Polar r a) = ...

if the argument is Coord 0 0? If we don't allow this syntax (if statement
on the RHS of the matching definition), why not? The syntax you propose can
be used to decribe arbitrary functions, so why not allow us coders to use
the ordinary Haskell syntax to define these functions, rather than a subset
thereof?

I have a feeling that with complicated views, coding the entire function on
the LHS could get cumbersome pretty quickly.  Of course, with pattern
guards, one can always get around this by defining a helper function, but
I'd prefer to avoid syntax constraints that require that I define a helper
function that's only used once.
-- 
David Roundy
Department of Physics
Oregon State University


More information about the Haskell-prime mailing list