Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>You're on your way to some great code. Let me push it a few more steps toward a Haskell-like solution.</p> <p>You've successfully modeled each <code>Piece</code> as an independent entity. This looks completely fine as is, but you want to be able to work with collections of pieces. The most immediate way to do this is to describe a type which can be any of the pieces desired.</p> <pre><code>data Piece = PipePiece Pipe | TankPiece Tank | BowlPiece Bowl | CrossPiece Cross | SourcePiece Source </code></pre> <p>which would let you write a list of pieces like</p> <pre><code>type Kit = [Piece] </code></pre> <p>but requires that when you consume your <code>Kit</code> that you pattern match on the different kinds of <code>Piece</code>s</p> <pre><code>instance Show Piece where show (PipePiece Pipe) = "Pipe" show (TankPiece Tank) = "Tank" show (BowlPiece Bowl) = "Bowl" show (CrossPiece Cross) = "Cross" show (SourcePiece Source) = "Source" showKit :: Kit -&gt; String showKit = concat . map show </code></pre> <p>There's also a strong argument for reducing the complexity of the <code>Piece</code> type by "flattening" out some redundant information</p> <pre><code>type Dimension = (Int, Int) type Position = (Int, Int) data Orientation = OrienLeft | OrienRight data Direction = Vertical | Horizontal | UpLeft | UpRight | DownLeft | DownRight data Piece = Pipe Direction | Tank Dimension Orientation | Bowl Dimension | Cross | Source Dimension </code></pre> <p>which eliminates many redundant type constructors at the expense of no longer being able to reflect what kind of piece you have in the type of a function—no longer can we write</p> <pre><code>rotateBowl :: Bowl -&gt; Bowl rotateBowl (Bowl orientation) = Bowl (rotate orientation) </code></pre> <p>but instead</p> <pre><code>rotateBowl :: Piece -&gt; Piece rotateBowl (Bowl orientation) = Bowl (rotate orientation) rotateBowl somethingElse = somethingElse </code></pre> <p>which is pretty annoying.</p> <p>Hopefully that highlights some of the tradeoffs between those two models. There's at least one "more exotic" solution which uses type classes and <code>ExistentialQuantification</code> to "forget" about everything besides an interface. This is worth exploring as it's pretty tempting to do but is considered to be a Haskell anti-pattern. I'll describe it first then talk about the better solution.</p> <p>To use <code>ExistentialQuantification</code> we remove the sum type <code>Piece</code> and create a type class for pieces.</p> <pre><code>{-# LANGUAGE ExistentialQuantification #-} class Piece p where melt :: p -&gt; ScrapMetal instance Piece Pipe instance Piece Bowl instance ... data SomePiece = forall p . Piece p =&gt; SomePiece p instance Piece SomePiece where melt (SomePiece p) = melt p forgetPiece :: Piece p =&gt; p -&gt; SomePiece forgetPiece = SomePiece type Kit = [SomePiece] meltKit :: Kit -&gt; SomePiece meltKit = combineScraps . map melt </code></pre> <p>This is an antipattern because <code>ExistentialQuantification</code> leads to more complex type errors and the erasure of lots of interesting information. The usual argument goes that if you're going to erase all information besides the ability to <code>melt</code> the <code>Piece</code>, you ought to have just melted it to begin with.</p> <pre><code>myScrapMetal :: [ScrapMetal] myScrapMetal = [melt Cross, melt Source Vertical] </code></pre> <p>And if your typeclass has multiple functions then perhaps your real functionality is stored in that class. For instance, let's say we can <code>melt</code> a <code>piece</code> and also <code>sell</code> it, perhaps the better abstraction would be the following</p> <pre><code>data Piece = { melt :: ScrapMetal , sell :: Int } pipe :: Direction -&gt; Piece pipe _ = Piece someScrap 2.50 myKit :: [Piece] myKit = [pipe UpLeft, pipe UpRight] </code></pre> <p>In all honesty, this is almost exactly what you're getting via the <code>ExistentialQuantification</code> method, but much more directly. When you erase the type information via <code>forgetPiece</code> you leave only the typeclass dictionary for <code>class Piece</code>---this is exactly a product of the functions in the typeclass which is what we're explicitly modeling with the <code>data Piece</code> type just described.</p> <hr> <p>The one reason I can think of to use <code>ExistentialQuantification</code> is best exemplified by Haskell's <code>Exception</code> system—if you're interested, take a look at how it's implemented. The short of it being that <code>Exception</code> had to be designed such that anyone could add a new <code>Exception</code> in any code and have it be routable through the <em>shared</em> <code>Control.Exception</code> machinery while maintaining enough identity for the user to catch it as well. This required the <code>Typeable</code> machinery as well... but it's almost certainly overkill.</p> <hr> <p>The takeaway should be that the model you use will depend a lot on how you end up consuming your data type. Initial encodings where you represent everything as an abstract ADT like the <code>data Piece</code> solution are nice in that they throw away little information... but can also be both unwieldy and slow. Final encodings like the <code>melt</code>/<code>sell</code> dictionary are often more efficient, but require deeper knowledge about what a <code>Piece</code> "means" and how it will be used.</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload