| | 1 | = Field Projections = |
| | 2 | [http://trac.haskell.org/ddc/wiki/Language/Overview < Overview] |
| | 3 | |
| | 4 | When data types are defined using field names, we can use the projection operator `(.)` to select the fields. |
| | 5 | |
| | 6 | {{{ |
| | 7 | data Vector |
| | 8 | = Vector { x :: Float; y :: Float; } |
| | 9 | |
| | 10 | main () |
| | 11 | = do vec = Vector 3.0 4.0 |
| | 12 | putStrLn $ show vec.x -- prints '3.0' |
| | 13 | putStrLn $ show vec.y -- prints '4.0' |
| | 14 | }}} |
| | 15 | |
| | 16 | == Custom projections == |
| | 17 | We can also define our own, custom projections and use `(.)` to select them. A `project` definition is similar to Haskell style `instance` definition in that it defines a set of functions associated with a particular type (in this case, `Vector`). When we use `(.)`, its first argument is passed as the first argument to our projection function - and so on. |
| | 18 | |
| | 19 | {{{ |
| | 20 | project Vector where |
| | 21 | magnitude :: Vector -> Float |
| | 22 | magnitude (Vector x y) |
| | 23 | = sqrt (x * x + y * y) |
| | 24 | |
| | 25 | dot :: Vector -> Vector -> Float |
| | 26 | dot (Vector x1 y1) (Vector x2 y2) |
| | 27 | = x1 * x2 + y1 * y2 |
| | 28 | |
| | 29 | main () |
| | 30 | = do ... |
| | 31 | putStrLn $ show vec.magnitude -- prints '5.0' |
| | 32 | putStrLn vec.dot (Vector 5.0 6.0) -- prints '39.0' |
| | 33 | }}} |
| | 34 | |
| | 35 | == Projections are type directed == |
| | 36 | In Disciple we can re-use the same field names in multiple data types, each with different field types. The type system uses the type of the first argument of `(.)` to determine what projection function to use. Alternatively, we can use the `(&)` operator to specify the projection type manually. |
| | 37 | |
| | 38 | {{{ |
| | 39 | data Location |
| | 40 | = Location { x :: String; y :: String; } |
| | 41 | |
| | 42 | main () |
| | 43 | = do ... |
| | 44 | loc = Location "over" "there" |
| | 45 | putStr $ show loc.x -- prints 'over' |
| | 46 | |
| | 47 | putStr $ show $ magnitude&{Vector} vec -- prints '5.0' |
| | 48 | }}} |
| | 49 | |
| | 50 | Using `(&)`, we can also define "projection" functions who's first argument is not of the projection type. |
| | 51 | |
| | 52 | {{{ |
| | 53 | project Vector where |
| | 54 | ... |
| | 55 | new :: Float -> Float -> Vector |
| | 56 | new posX posY = Vector posX posY |
| | 57 | |
| | 58 | main () |
| | 59 | = do ... |
| | 60 | vec2 = new&{Vector} 5.0 6.0 |
| | 61 | ... |
| | 62 | }}} |
| | 63 | |
| | 64 | == Ambiguous projections == |
| | 65 | As Disciple uses the type of the first argument of `(.)` to decide what projection to use, it needs to be constrained to a data type (not just a type variable). Usually a top level type signature is enough. |
| | 66 | |
| | 67 | Compilation of this function: |
| | 68 | {{{ |
| | 69 | getX thing = thing.x |
| | 70 | }}} |
| | 71 | |
| | 72 | will fail with |
| | 73 | {{{ |
| | 74 | ./Main.ds: ... |
| | 75 | Ambiguous projection: .x |
| | 76 | }}} |
| | 77 | |
| | 78 | as there is no way of knowing what version of `.x` to use, are we talking about the `.x` from `Vector` or `Location`? |
| | 79 | |
| | 80 | Providing a type signature adds the required constraint. |
| | 81 | |
| | 82 | {{{ |
| | 83 | getx :: Vector -> Float |
| | 84 | getX thing = thing.x |
| | 85 | }}} |