my blog lives here now
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

304 lines
9.0 KiB

6 years ago
  1. ---
  2. title: Amulet updates
  3. date: August 11, 2018
  4. maths: true
  5. ---
  6. Jesus, it's been a while. Though my last post was almost 6 months ago
  7. (give or take a few), I've been busy working on
  8. [Amulet](https://github.com/tmpim/amulet), which continues to grow,
  9. almost an eldritch abomination you try desperately, but fail, to kill.
  10. Since my last post, Amulet has changed a ton, in noticeable and
  11. not-so-noticeable ways. Here are the major changes to the compiler since
  12. then.
  13. Parser improvements
  14. ===================
  15. No language is good to use if it's inconvenient. So, in an effort to
  16. make writing code more convenient, we've removed the need for `;;` after
  17. top-level declarations, and added a _bunch_ of indentation sensitivity,
  18. thus making several keywords optional: `begin`{.ocaml} and `end`{.ocaml}
  19. are implicit in the body of a `fun`{.ocaml}, `match`{.ocaml}, or
  20. `let`{.ocaml}, which has made those keywords almost entirely obsolete.
  21. The body of a `let`{.ocaml} also need not be preceded by `in`{.ocaml} if
  22. meaning is clear from indentation.
  23. To demonstrate, where you would have
  24. ```ocaml
  25. let foo =
  26. let bar = fun x -> begin
  27. a;
  28. b;
  29. c
  30. end in begin
  31. bar ();
  32. bar 1;
  33. end ;;
  34. ```
  35. One can now write
  36. ```ocaml
  37. let foo =
  38. let bar = fun x ->
  39. a
  40. b
  41. c
  42. bar ()
  43. bar 1
  44. ```
  45. Moreover, we've added shorthand syntax for building and destructuring
  46. records: `{ x, y, z }`{.ocaml} is equivalent to `{ x = x, y = y, z = z
  47. }`{.ocaml} in both pattern and expression position.
  48. Changes to record typing
  49. ========================
  50. Whereas `{ x with a = b }` would extend the record `x` to contain a new
  51. field `a` (with value `b`), it's now _monomorphic update_ of the record
  52. `x`. That is: `x` must _already_ contain a field called `a`, with the
  53. same type as `b`.
  54. This lets you write a function for updating a field in a record, such as
  55. the one below, which would previously be impossible. Supporting
  56. polymorphic update is not a priority, but it'd be nice to have. The way
  57. PureScript, another language with row-polymorphic records, implements
  58. polymorphic update does not fit in with our constraint based type
  59. system. A new type of constraint would have to be introduced
  60. specifically for this, which while not impossible, is certainly
  61. annoying.
  62. ```ocaml
  63. let foo : forall 'row. { 'row | x : int } -> { 'row | x : int } =
  64. fun r -> { r with x = r.x + 1 }
  65. ```
  66. The impossibility of supporting polymorphic update with regular
  67. subsumption constraints $a \le b$ stems from the fact that, when faced
  68. with such a constraint, the compiler must produce a coercion function
  69. that turns _any_ $a$ into a $b$ _given the types alone_. This is
  70. possible for, say, field narrowing---just pick out the fields you want
  71. out of a bigger record---but not for update, since the compiler has no
  72. way of turning, for instance, an `int`{.ocaml} into a `string`{.ocaml}.
  73. Stronger support for Rank-N Types
  74. =================================
  75. Changes to how we handle subsumption have made it possible to store
  76. polymorphic values in not only tuples, but also general records. For
  77. instance:
  78. ```ocaml
  79. let foo = {
  80. apply : forall 'a 'b. ('a -> 'b) -> 'a -> 'b =
  81. fun x -> x
  82. } (* : { apply : forall 'a 'b. ('a -> 'b) -> 'a -> 'b } *)
  83. ```
  84. `foo`{.ocaml} is a record containing a single polymorphic application
  85. function. It can be used like so:
  86. ```ocaml
  87. let () =
  88. let { apply } = foo
  89. apply (+ 1) 1
  90. apply (fun x -> x) ()
  91. ```
  92. Pattern-matching Let
  93. ====================
  94. A feature I've desired for a while now, `let` expressions (and
  95. declarations!) can have a pattern as their left-hand sides, as
  96. demonstrated above. These can be used for any ol' type, including for
  97. cases where pattern matching would be refutable. I haven't gotten around
  98. to actually implementing this yet, but in the future, pattern matching
  99. in `let`s will be restricted to (arbitrary) product types only.
  100. ```ocaml
  101. type option 'a = Some of 'a | None
  102. type foo = Foo of { x : int }
  103. let _ =
  104. let Some x = ... (* forbidden *)
  105. let Foo { x } = ... (* allowed *)
  106. ```
  107. Even more "in-the-future", if we ever get around to adding attributes
  108. like OCaml's, the check for this could be bypassed by annotating the
  109. declaration with (say) a `[@partial]`{.ocaml} attribute.
  110. Unfortunately, since Amulet _is_ a strict language, these are a bit
  111. limited: They can not be recursive in _any_ way, neither directly nor
  112. indirectly.
  113. ```ocaml
  114. (* type error *)
  115. let (a, b) = (b, a)
  116. (* similarly *)
  117. let (a, b) = x
  118. and x = (a, b)
  119. ```
  120. Cycle detection and warnings
  121. ============================
  122. A verification pass is run over the AST if type-checking succeeds, to
  123. forbid illegal uses of recursion (strict language) and, as an additional
  124. convenience, warn when local variables go unused.
  125. For instance, this is [forbidden](/static/verify_error.png):
  126. ```ocaml
  127. let f = (fun x -> x) g
  128. and g = (fun x -> x) f
  129. ```
  130. And this gives a [warning](/static/verify_warn.png):
  131. ```ocaml
  132. let _ =
  133. let { a, b } = { a = 1, b = 2 }
  134. ()
  135. ```
  136. Plans for this include termination (and/or productivity) (as a
  137. warning) and exhaustiveness checks (as an error).
  138. No more `main`
  139. ============
  140. Since pattern-matching `let`{.ocaml}s are allowed at top-level, there's no more
  141. need for `main`. Instead of
  142. ```ocaml
  143. let main () =
  144. ...
  145. ```
  146. Just match on `()`{.ocaml} at top-level:
  147. ```ocaml
  148. let () =
  149. ...
  150. ```
  151. This gets rid of the (not so) subtle unsoundness introduced by the code
  152. generator having to figure out how to invoke `main`, and the type
  153. checker not checking that `main` has type `unit -> 'a`{.ocaml}, and also
  154. allows us to remove much of the silly special-casing around variables
  155. called `main`.
  156. ```ocaml
  157. let main x = x + 1
  158. (* attempt to perform arithmetic on a nil value *)
  159. ```
  160. Implicit Parameters
  161. ===================
  162. A bit like Scala's, these allow marking a function's parameter as
  163. implicit and having the type checker find out what argument you meant
  164. automatically. Their design is based on a bit of reading other compiler
  165. code, and also the paper on modular implicits for OCaml. However, we do
  166. not have a ML-style module system at all (much to my dismay, but it's
  167. being worked on), much less first class modules.
  168. Implicit parameters allow ad-hoc overloading based on dictionary passing
  169. (like type classes, but with less inference).
  170. ```ocaml
  171. type show 'a = Show of 'a -> string
  172. let show ?(Show f) = f
  173. let implicit show_string =
  174. Show (fun x -> x)
  175. let "foo" = show "foo"
  176. ```
  177. Here, unification makes it known that `show` is looking for an implicit
  178. argument of type `show string`{.ocaml}, and the only possibility is
  179. `show_string`{.ocaml}, which is what gets used.
  180. Implicit laziness
  181. =================
  182. There is a built-in type `lazy : type -> type`{.ocaml}, a function
  183. `force : forall 'a. lazy 'a -> 'a` for turning a thunk back into a
  184. value, and a keyword `lazy` that makes a thunk out of any expression.
  185. `lazy 'a`{.ocaml} and `'a` are mutual subtypes of eachother, and the
  186. compiler inserts thunks/`force`{.ocaml}s where appropriate to make code
  187. type-check.
  188. ```ocaml
  189. let x && y = if x then force y else false
  190. let () =
  191. false && launch_the_missiles ()
  192. (* no missiles were launched in the execution of this program *)
  193. ```
  194. General refactoring
  195. ===================
  196. - Literal patterns are allowed for all types, and they're tested of
  197. using `(==)`.
  198. - Amulet only has one type constructor in its AST for all its kinds of
  199. functions: `forall 'a. 'a -> int`{.ocaml}, `int -> string`{.ocaml} and `show string =>
  200. unit`{.ocaml} are all represented the same internally and disambiguated
  201. by dependency/visibility flags.
  202. - Polymorphic recursion is checked using "outline types", computed
  203. before fully-fledged inference kicks in based solely on the shape of
  204. values. This lets us handle the function below without an annotation on
  205. its return type by computing that `{ count = 1 }` _must_ have type `{
  206. count : int }` beforehand.
  207. Combined with the annotation on `x`, this gives us a "full" type
  208. signature, which lets us use checking for `size`, allowing polymorphic
  209. recursion to happen.
  210. ~~~{.ocaml}
  211. type nested 'a = Nested of nested ('a * 'a) * nested ('a * 'a) | One of 'a
  212. let size (x : nested 'a) =
  213. match x with
  214. | One _ -> { count = 1 }
  215. | Nested (a, _) -> { count = 2 * (size a).count }
  216. ~~~
  217. - The newtype elimination pass was rewritten once and, unfortunately,
  218. disabled, since it was broken with some touchy code.
  219. - Operator declarations like `let 2 + 2 = 5 in 2 + 2` are admissible.
  220. - Sanity of optimisations is checked at runtime by running a type
  221. checker over the intermediate language programs after all optimisations
  222. - Local opens are allowed, with two syntaxes: Either
  223. `M.( x + y)`{.ocaml} (or `M.{ a, b }`{.ocaml}) or `let open M in x +
  224. y`{.ocaml}.
  225. - Amulet is inconsistent in some more ways, such as `type : type`
  226. holding.
  227. - There are no more kinds.
  228. Conclusion
  229. ==========
  230. This post was a bit short, and also a bit hurried. Some of the features
  231. here deserve better explanations, but I felt like giving them an
  232. _outline_ (haha) would be better than leaving the blag to rot (yet
  233. again).
  234. Watch out for a blog post regarding (at _least_) implicit parameters,
  235. which will definitely involve the changes to subtyping involving
  236. records/tuples.