Typesafe APIs for the browser
A new feature in Ceylon 1.1, that I’ve not blogged about before, is dynamic interfaces. This was something that Enrique and I worked on together with Corbin Uselton, one of our GSoC students.
Ordinarily, when we interact with JavaScript objects, we do it from within a dynamic
block, where Ceylon’s usual scrupulous typechecking is suppressed. The problem with this approach is that if it’s an API I use regularly, my IDE can’t help me get remember the names and signatures of all the operations of the API.
Dynamic interfaces make it possible to ascribe static types to an untyped JavaScript API. For example, we could write a dynamic interface for the HTML 5 CanvasRenderingContext2D
like this:
dynamic CanvasRenderingContext2D { shared formal variable String|CanvasGradient|CanvasPattern fillStyle; shared formal variable String font; shared formal void beginPath(); shared formal void closePath(); shared formal void moveTo(Integer x, Integer y); shared formal void lineTo(Integer x, Integer y); shared formal void fill(); shared formal void stroke(); shared formal void fillText(String text, Integer x, Integer y, Integer maxWidth=-1); shared formal void arc(Integer x, Integer y, Integer radius, Float startAngle, Float endAngle, Boolean anticlockwise); shared formal void arcTo(Integer x1, Integer y1, Integer x2, Float y2, Integer radius); shared formal void bezierCurveTo(Integer cp1x, Integer cp1y, Integer cp2x, Float cp2y, Integer x, Integer y); shared formal void strokeRect(Integer x, Integer y, Integer width, Integer height); shared formal void fillRect(Integer x, Integer y, Integer width, Integer height); shared formal void clearRect(Integer x, Integer y, Integer width, Integer height); shared formal CanvasGradient createLinearGradient(Integer x0, Integer y0, Integer x1, Integer y1); shared formal CanvasGradient createRadialGradient(Integer x0, Integer y0, Integer r0, Integer x1, Integer y1, Integer r1); shared formal CanvasPattern createPattern(dynamic image, String repetition); //TODO: more operations!! } dynamic CanvasGradient { shared formal void addColorStop(Integer offset, String color); } dynamic CanvasPattern { //todo }
Now, if we assign an instance of JavaScript’s CanvasRenderingContext2D
to this interface type, we won’t need to be inside a dynamic
block when we call its methods. You can try it out in your own browser by clicking the “TRY ONLINE” button!
CanvasRenderingContext2D ctx; dynamic { //get the CanvasRenderingContext2D from the //canvas element using dynamically typed code ctx = ... ; } //typesafe code, checked at compile time ctx.fillStyle = "navy"; ctx.fillRect(50, 50, 235, 60); ctx.beginPath(); ctx.moveTo(100,50); ctx.lineTo(60,5); ctx.lineTo(75,75); ctx.fill(); ctx.fillStyle = "orange"; ctx.font = "40px PT Sans"; ctx.fillText("Hello world!", 60, 95);
Notice that we don’t need to ascribe an explicit type to every operation of the interface. We can leave some methods, or even just some parameters of a method untyped, by declaring them dynamic
. Such operations may only be called from within a dynamic
block, however.
A word of caution: dynamic interfaces are a convenient fiction. They can help make it easier to work with an API in your IDE, but at runtime there is nothing Ceylon can do to ensure that the object you assign to the dynamic interface type actually implements the operations you’ve ascribed to it.
Reference: | Typesafe APIs for the browser from our JCG partner Gavin King at the Ceylon Team blog blog. |