Jonathan Geddes | 2 Aug 07:34 2012
Picon

What Haskell Records Need

Greetings,

tl;dr - What Haskell Records need are
semantic editor combinators for free.

I know this is yet another Record proposal
among many, but none of them out there
strike me as being exactly what I want in
Haskell.

Take the following types from a contrived
example.

>type Salary = Integer
>
>data Job = Job
>  { title  :: String
>  , salary :: Salary
>  }
>
>data Person = Person
>  { name :: String
>  , job  :: Job
>  }

Since I've used record syntax, I get
getter/accessor functions (title, salary,
name, job) for free. Now suppose I want to
create an aggregate getter function: return
the salary of a given person. Piece of cake,
it's just function composition

>getSalary :: Person -> Salary
>getSalary = salary . job

Done! Now suppose I want to write a
setter/mutator function for the same nested
field

>setSalaryMessy :: Salary -> Person -> Person
>setSalaryMessy newSalary person =
>  person {
>    job = (job person) {
>      salary = newSalary
>    }
>  }

Ouch! And that's not even very deeply nested.
Imagine 4 or 5 levels deep. It really makes
Haskell feel clunky next to `a.b.c.d = val`
that you see in other languages. Of course
immutability means that the semantics of
Haskell are quite different (we're creating
new values here, not updating old ones) but
it's still common to model change using these
kinds of updates.

What if along with the free getters that
the compiler generates when we use record
syntax, we also got semantic editor
combinator (SEC) functions[0] that could be
used as follows?

>setSalary newSalary = job' $ salary' (const newSalary)
>
>giveRaise amount = job' $ salary' (+amount)
>
>givePercentRaise percent = job' $ salary' (*(1+percent))

For each field x, the compiler generates a
function x' (the tic is mnemonic for change).
These little functions aren't hard to write,
but they're classic boilerplate.

>job' :: (Job -> Job) -> Person -> Person
>job' f person = person {job = f $ job person}

>salary' :: (Salary -> Salary) -> Job -> Job
>salary' f job = job { salary = f $ salary job}

These type of utility functions are a dream
when working with any reference type or
State Monad.

> modify $ givePercentRaise 0.25

The compiler could also generate polymorphic
SEC functions for polymorphic fields.
Further, the compiler could disallow using
old-style update syntax for fields whose SEC
update function is not in scope, giving us
fine-grained control over access and update.
On the other hand we currently have to create
new functions to achieve this (exporting the
getter means exporting the ability to update
as well, currently).

Of course this doesn't address the
namespacing issues with records, but it is
likely nicely orthogonal to other proposals
which do.

Also note that there's a package on hackage [1]
that will generate SEC functions using TH.
It's nice, but I prefer the style of field
names used above for updaters (field' vs
editField).

Let me know what you think. I'll write up an
official proposal if there's a bit of
general interest around this.

Thanks for reading,

--Jonathan




_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe <at> haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe
Richard O'Keefe | 2 Aug 09:08 2012
Picon

Re: What Haskell Records Need


On 2/08/2012, at 5:34 PM, Jonathan Geddes wrote:
> Ouch! And that's not even very deeply nested.
> Imagine 4 or 5 levels deep. It really makes
> Haskell feel clunky next to `a.b.c.d = val`
> that you see in other languages.

I was taught that this kind of thing violates the Law of Demeter
and that an object should not be mutating the parts of an
acquaintance's parts, but should ask the acquaintance to do so.
I'd say that a.b.c.d = val is at the very least a sign that
some encapsulation did not happen.

Semantic editor combinators are ingenious, but they make
me feel somewhat uneasy, in that they really are in some
sense about the *syntax* (or maybe the concrete representation)
of things rather than their *semantics* (or maybe I mean the
abstract value).
Erik Hesselink | 2 Aug 10:25 2012
Picon

Re: What Haskell Records Need

Isn't this exactly the problem solved by all the lens packages?
Current popular ones are fclabels [0] and data-lens [1].

[0] http://hackage.haskell.org/package/fclabels
[1] http://hackage.haskell.org/package/data-lens

On Thu, Aug 2, 2012 at 7:34 AM, Jonathan Geddes
<geddes.jonathan <at> gmail.com> wrote:
> Greetings,
>
> tl;dr - What Haskell Records need are
> semantic editor combinators for free.
>
> I know this is yet another Record proposal
> among many, but none of them out there
> strike me as being exactly what I want in
> Haskell.
>
> Take the following types from a contrived
> example.
>
>>type Salary = Integer
>>
>>data Job = Job
>>  { title  :: String
>>  , salary :: Salary
>>  }
>>
>>data Person = Person
>>  { name :: String
>>  , job  :: Job
>>  }
>
> Since I've used record syntax, I get
> getter/accessor functions (title, salary,
> name, job) for free. Now suppose I want to
> create an aggregate getter function: return
> the salary of a given person. Piece of cake,
> it's just function composition
>
>>getSalary :: Person -> Salary
>>getSalary = salary . job
>
> Done! Now suppose I want to write a
> setter/mutator function for the same nested
> field
>
>>setSalaryMessy :: Salary -> Person -> Person
>>setSalaryMessy newSalary person =
>>  person {
>>    job = (job person) {
>>      salary = newSalary
>>    }
>>  }
>
> Ouch! And that's not even very deeply nested.
> Imagine 4 or 5 levels deep. It really makes
> Haskell feel clunky next to `a.b.c.d = val`
> that you see in other languages. Of course
> immutability means that the semantics of
> Haskell are quite different (we're creating
> new values here, not updating old ones) but
> it's still common to model change using these
> kinds of updates.
>
> What if along with the free getters that
> the compiler generates when we use record
> syntax, we also got semantic editor
> combinator (SEC) functions[0] that could be
> used as follows?
>
>>setSalary newSalary = job' $ salary' (const newSalary)
>>
>>giveRaise amount = job' $ salary' (+amount)
>>
>>givePercentRaise percent = job' $ salary' (*(1+percent))
>
> For each field x, the compiler generates a
> function x' (the tic is mnemonic for change).
> These little functions aren't hard to write,
> but they're classic boilerplate.
>
>>job' :: (Job -> Job) -> Person -> Person
>>job' f person = person {job = f $ job person}
>
>>salary' :: (Salary -> Salary) -> Job -> Job
>>salary' f job = job { salary = f $ salary job}
>
> These type of utility functions are a dream
> when working with any reference type or
> State Monad.
>
>> modify $ givePercentRaise 0.25
>
> The compiler could also generate polymorphic
> SEC functions for polymorphic fields.
> Further, the compiler could disallow using
> old-style update syntax for fields whose SEC
> update function is not in scope, giving us
> fine-grained control over access and update.
> On the other hand we currently have to create
> new functions to achieve this (exporting the
> getter means exporting the ability to update
> as well, currently).
>
> Of course this doesn't address the
> namespacing issues with records, but it is
> likely nicely orthogonal to other proposals
> which do.
>
> Also note that there's a package on hackage [1]
> that will generate SEC functions using TH.
> It's nice, but I prefer the style of field
> names used above for updaters (field' vs
> editField).
>
> Let me know what you think. I'll write up an
> official proposal if there's a bit of
> general interest around this.
>
> Thanks for reading,
>
> --Jonathan
>
> [0] - http://conal.net/blog/posts/semantic-editor-combinators
> [1] -
> http://hackage.haskell.org/packages/archive/sec/0.0.1/doc/html/Data-SemanticEditors.html
>
>
>
>
> _______________________________________________
> Haskell-Cafe mailing list
> Haskell-Cafe <at> haskell.org
> http://www.haskell.org/mailman/listinfo/haskell-cafe
>

Gmane