Skip to content

Instantly share code, notes, and snippets.

@gampleman
Last active October 13, 2016 14:09
Show Gist options
  • Save gampleman/dafea7bc4f6afe0f184e1332d799e85f to your computer and use it in GitHub Desktop.
Save gampleman/dafea7bc4f6afe0f184e1332d799e85f to your computer and use it in GitHub Desktop.
Elm embedded language proposal

Embedded language proposal

Add syntax to elm to allow for safe type checked embedded languages.

Motivation

Currently Elm embeds GLSL as a special case for type safe webgl programs. This is somewhat odd as a special case, but prooves to be a really nice feature, that other DSLs could benefit from. The idea here is to implement this in user land, allowing library authors to create functions that expose DSLs to their callers, but allow compile time error checking and reporting.

I believe that using strings as data is often a source of remaining bugs in Elm programs. (See Stringly Typed). Having a richer, type and syntax checked semantic for these could easily improve the reliability and maintainability of these programs.

Current problems this solves

Solution

Allow the definition of special functions that take the form [|name|] : String -> Result ParseError AST, which the compiler understands as macros, rather than runtime functions. Once these are defined (and imported), the compiler allows the use of string-like blocks of the form of [name| text |]. When any of these are identified the compiler will evaluate the special function with the matching name. If this results in the Error case, then the compiler will fail compilation with an error message suplied by the function and the source location of the original string. (Perhaps some more detail about the error should be supplied in the error type?)

In the Ok case, the resulting type will be some representation of valid Elm code, that will be (hygienically?) put instead of the call site.

The examples in this gist provide some illustration into an implementation of such a macro, but there is probably a nicer way to do this.

Prior art

GLSL in Elm

This is an embedded language in elm, but is special cased by the compiler.

Hygienic macros

  • Racket and other lisps
  • Elixir
  • Template Haskell

Paper worth reading. They associate a parser with a type and then allow values to be parsed based on that, so no sigils necessary. Also have the nice idea that the parsers themselves are defined in an embedded language. Pretty neat.

Questions, problems, limitations

Additional arguments

Should these functions be able to accept additional arguments? It's not clear how this would work, since currently the only argument is an actual literal, so there is no actual computation at the macro call site.

Effects

GraphQL should be validated against a server-side schema again prefferably at compile time. It could easily be made the responsibility of the user to actually download it, etc. but the macro would still need access to this external source of truth. It's not clear how this could be handled in this proposal.

Syntax highlighting

This seems to always be tricky if the number of embedded languages isn't bound, but the sigil would usually be the name of the embedded language, so hopefully this shouldn't be too bad.

macro module ExampleImplementation exposing ([|glsl|], [|r|], [|s|], [|css|])
-- ^ not sure this is necessary
import Elm exposing (AST, ParseError, [|quote|])
import Result
import Regex.Parser -- hypothetical module
[|r|] : String -> Result ParseError AST
[|r|] s =
Regex.Parser.parse s
|> Result.mapError ParseError
|> Result.map (\res -> Elm.withImport "Regex" [quote| Regex.regex "#{Regex.Parser.escapeForString res}"|]
module ExampleUsage exposing (..)
-- The expansions are printed underneath with {- -} style comments
import ExampleImplementation exposing ([|glsl|], [|r|], [|s|], [|css|])
-- This works currently, but is special cased in the compiler
vertexShader : Shader { position:Vec3, coord:Vec3 } { u | view:Mat4 } { vcoord:Vec2 }
vertexShader = [glsl|
attribute vec3 position;
attribute vec3 coord;
uniform mat4 view;
varying vec2 vcoord;
void main () {
gl_Position = view * vec4(position, 1.0);
vcoord = coord.xy;
}
|]
{-
vertexShader =
let
shader : Shader { position:Vec3, coord:Vec3 } { u | view:Mat4 } { vcoord:Vec2 }
shader = Webgl.unsafeShader "attribute vec3 position;\n ..."
in
shader
-}
-- This is a simple regex literal
emailRegex : Regex
emailRegex = [r|[\w\.]+@[\w\.]+|]
{-
emailRegex = Regex.regex "[\\w\\.]+@[\\w\\.]+"
-}
-- This currently crashes at runtime
broken = Regex.regex " (av+"
-- This would cause a parse error at compile time
broken' = [r| (av+]
-- This is like the proposal for string interpolation, but in user space!
greet : { name: String, messages: List Message } -> String
greet user =
[s|Hello #{user.name}, you have #{List.count user.messages} new messages!|]
{-
greet user =
"Hello " + user.name + ", you have " + (toString (List.count user.messages)) + " new messages!"
-}
-- Not totally sure this is actually possible
style : StyleSheet { baseColor: Color, fontSize: SizeUnit }
style = [css|
body {
color: var(--base-color);
font-size: var(--font-size);
}
h1 {
font-size: calc(var(--font-size) * 2);
}
|]
{-
style =
let
stylesheet : StyleSheet { baseColor: Color, fontSize: SizeUnit }
stylesheet = CSS.unsafeStyleSheet "body {\n..."
in
stylesheet
-}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment