The core types in my geometry library 
<http://package.elm-lang.org/packages/opensolid/geometry/latest/OpenSolid-Geometry-Types>
 
use what is in some sense "the worst of both worlds" - I wrap values 
implemented as tuples or records, but only to provide type safety, not to 
keep internal details private (the constructors are exposed):

type Vector3d
    = Vector3d ( Float, Float, Float )

type Point3d
    = Point3d ( Float, Float, Float )

type Direction3d
    = Direction3d ( Float, Float, Float )

type Axis3d
    = Axis3d { originPoint : Point3d, direction : Direction3d }


This allows client code to directly construct a Vector3d ( 1, 2, 3 ) or a 
Point3d 
( 1, 2, 3 ) or an Axis3d { originPoint = ..., direction = ... }, but once 
they are constructed you can't pass a Vector3d where a Point3d is expected. 
You could destructure/unwrap the constructed values if you wanted to, but 
there are enough accessor functions (Axis3d.originPoint, Point3d.coordinates, 
Point3d.xCoordinate etc.) that normally you don't need to. I'm not sure 
this is a good pattern in general (I don't think I'll continue to use it 
for more complex future types like parametric surfaces or solid bodies 
where the representation is much more complex) but I think it works well 
for fairly low-level, primitive types.

On Thursday, 23 March 2017 12:21:10 UTC-4, Mark Hamburg wrote:
>
> Should one wrap a model implemented as a record in a type to keep the 
> details private? In other words:
>
> type alias Model = { foo : Foo, baz : Baz }
>
> - or -
>
> type Model = M { foo : Foo, baz : Baz }
>
> The former is much easier to work with because one isn't constantly 
> unwrapping and rewrapping.
>
> The latter keeps code better encapsulated.
>
> I had this discussion with a fellow programmer and he asked whether 
> immutability makes the encapsulation irrelevant. I argued no because the 
> exposure of internals means that client code can adopt dependencies on 
> internal choices — "Just because I have a field named 'foo' doesn't mean I 
> want a client to know about it" — and more significantly it allows 
> arbitrary code to create values purporting to be valid Model values. If one 
> can achieve "make invalid states impossible", that's obviously great but in 
> practice we rely on limiting construction to impose further invariants. 
> (For example, if the structures implementing an AVL tree were exposed, the 
> type system would ensure that we had a DAG but it could do nothing to 
> ensure anything about ordering or the height values.)
>
> The balance we were left with was that "we're all programmers of good 
> character and we will consider other modules Models to be opaque even if 
> they aren't." (I'd have said "we're all gentlemen" but that's sexist.) 
> This, of course, works until we hire a programmer whose character isn't 
> quite as good in this regard.
>
> How do others choose in this regard?
>
> Mark
>
> P.S. What I would like — though it would break a lot of existing code — is 
> for the fields of a record to only be exposed on export if explicitly 
> exposed a la exposing constructors for 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 [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to