How do I organize "inheritance" between my data types?

666 views
Skip to first unread message

Leonardo Sá

unread,
Jul 17, 2016, 7:06:32 PM7/17/16
to Elm Discuss
Short question:

What is the best way to create relationships between my types so it is easy to access data present in both types?

Long question:

Suppose I have the following types:

type alias Person =
  { name : String
  , address : String
  , personType : PersonType 
  }

type alias Employee =
  { department : String }

type alias Customer =
  { itemsPurchased : Int }

type PersonType = EmployeeType Employee | CustomerType Customer

Then I think it's not straight forward to write a function that retrieves me the department and name for an employee:

nameAndDepartment : Person -> (String, String)

It seems to me this function would be a Maybe (String, String) and return Nothing if the Person is not an Employee. But in that case, I am relying on the runtime to type check things for me, which tells me there is probably a better way to structure this.

Aaron VonderHaar

unread,
Jul 17, 2016, 8:13:33 PM7/17/16
to elm-d...@googlegroups.com
Can you give more details about what you are trying to do that requires a Person type that can be either an employee or a customer?  As you noted, `nameAndDepartment` would only apply to Employees, so why do you need to use it on Customers?  Can you simply deal with employees and customers independently?

If you do really need to have a Person type, my first thought would be to have Person be an independent module, and have Employee.toPerson and Customer.toPerson.  That way all the modules can be independent w/r to the way the data is modeled and will have more stable interfaces than if you try to have a Person type that depends on both the Employee and Customer types.

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Leonardo Sá

unread,
Jul 17, 2016, 8:37:31 PM7/17/16
to Elm Discuss
I think the idea is that both Employees and Customers are Persons - as in, they have shared fields such as name and address. So potentially we could write functions that operate directly on Person without caring whether the person is an employee or a customer, and we'd write functions that operate directly on Employee or Customers without losing the context that they are in fact Persons. Just as you'd have in OO, where Person is the base class for Employee and Customer.

Or maybe I am just too stuck on my OO ways.

Sorry I can't provide more details (the lawyers would kill me)! That, and the schema I'm trying to work with is really convoluted.

But what it boils down to is: how does one emulates a traditional OO inheritance with ADTs, in such a way that I can write functions that operate on the base class, and functions that operate on child classes, and it's all type safe?

Aaron PS

unread,
Jul 17, 2016, 9:21:14 PM7/17/16
to Elm Discuss
Hello, you might be looking for "extensible records", check this out http://elm-lang.org/docs/records

import Html exposing (Html, div, ul, li, text)

type
alias Person a =
   
{ a | name : String
   
, address : String
   
}

type
alias Employee a =
   
{ a | department : String }

type
alias Customer a =
   
{ a | itemsPurchased : Int }

changeName
: String -> Person a -> Person a
changeName newName person
=
   
{ person | name = newName }

changeDepartment
: String -> Employee a -> Employee a
changeDepartment newDepartment employee
=
   
{ employee | department = newDepartment }

changeItemsPurchased
: Int -> Customer a -> Customer a
changeItemsPurchased newItemsPurchased customer
=
   
{ customer | itemsPurchased = newItemsPurchased }

main
=
    let
        person
= { name="person name", address="person address"}
        employee
= { name="employee name", address="employee address", department = "employee department" }
        customer
= { name="customer name", address="customer address", itemsPurchased = 42 }
   
in
    div
[]
       
[ ul []
           
[ renderIt person
           
, renderIt employee
           
, renderIt customer
           
, renderIt (changeName "name changed" person)
           
, renderIt (changeName "name changed" employee)
           
, renderIt (changeName "name changed" customer)
           
           
-- , renderIt (changchangeDepartmenteName "department changed" person)
           
, renderIt (changeDepartment "department changed" employee)
           
-- , renderIt (changeDepartment "department changed" customer)

           
-- , renderIt (changeItemsPurchased 55 person)
           
-- , renderIt (changeItemsPurchased 55 employee)
           
, renderIt (changeItemsPurchased 55 customer)
           
]
       
]


renderIt
: a -> Html b
renderIt r
=
    li
[] [text (toString r)]


Max Goldstein

unread,
Jul 17, 2016, 9:40:52 PM7/17/16
to Elm Discuss
Aaron's solution is a good one, but since customers and employees are both people, you may want:

type alias Person a =
    { a | name : String
    , address : String
    }

type alias Employee =
    Person { department : String }

type alias Customer =
    Person { itemsPurchased : Int }

Aaron VonderHaar

unread,
Jul 17, 2016, 10:07:46 PM7/17/16
to elm-d...@googlegroups.com
To restate my previous post, I think you should spend some time considering whether you actually have a case where you need to write code that deals with both Customers and Employees, or whether you are just asking because you would use inheritance if you were using an OO language.  Making a "generic" Person type won't necessarily help you because you still have to create all the values.  If they are coming from a database, then it's likely that employees and customer will be coming from different tables and possibly from different backend APIs.  I'd also be curious about what UI you are trying to implement that needs customers and employees to be handled in the same way.  At NoRedInk, we have several different user types: teachers, students and admins, but most pages in our app treat teachers and students completely differently in the UI, so there's no need for a "Person" type.  We do have an admin page that shows a list of all users, but for that page we have a "User" model and there's no need for the Teacher and Student types on that page.


But if you do really need a Person type, if you had independent Person, Customer, and Employee modules with `toPerson` functions as I suggested before, then you can reuse any functions in the Person module with Customers and Employees by using function composition.  If you are still concerned about duplicating code, you may also choose to implement Customer and Employee such that `toPerson` is as simple as `toPerson customer = customer.person`.  I believe having small, composable functions and independent modules is preferable to having an interconnected system of types.


Another way to consider is this:

type alias Person =
    { name : String
    , address : String
    , employeeInfo : Maybe EmployeeInfo
    , customerInfo : Maybe CustomerInfo
    }

(Note that modeling this way allows for a person to be both a customer and an employee at the same time, which may be something you need.)  This is similar to what you originally proposed, but I think it will probably lead to a bit cleaner code because you don't need `PersonType`.


You can also use extensible records as others noted, but it's unlikely that you actually need that much complexity for whatever you are trying to do with Employees and Customers.

Leonardo Sá

unread,
Jul 17, 2016, 10:12:22 PM7/17/16
to elm-d...@googlegroups.com
Those are some great suggestions. Thank you everyone!

--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/DSXGikv-9ic/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Leonardo Sá
816-703-8353
This week on www.leo-sa.com:
Developing JavaFX with NetBeans on Linux

Leroy Campbell

unread,
Jul 18, 2016, 5:43:29 PM7/18/16
to Elm Discuss
It may help to watch this talk on modeling from a functional programming perspective (features F#, but applicable to Elm since they both are ML-based):

Evan

unread,
Jul 18, 2016, 6:21:39 PM7/18/16
to Elm Discuss
Both Max and Aaron PS suggested using extensible records for this scenario, but I don't think that is the right way to go. It is particularly tempting for folks who want Elm to just work like an OO language, but it isn't like that. I think Tessa put it very nicely in her post: extensible records are for making functions flexible, not for modeling your data. I highly recommend OP and the folks who suggested extensible records read that post!

I believe Tessa's and Aaron Vonderhaar's advice will get you to better code that is easier to understand and refactor.
Reply all
Reply to author
Forward
0 new messages