Skip to content

Instantly share code, notes, and snippets.

@evancz
Last active August 29, 2015 14:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save evancz/eea10d282e0e5cdad538 to your computer and use it in GitHub Desktop.
Save evancz/eea10d282e0e5cdad538 to your computer and use it in GitHub Desktop.

Macro Syntax

I think there are two key parts to the syntax. How let! looks and how := looks. This proposal defends those choices and goes a bit farther with them.

Defense of let!

I like let! because:

  1. It is minimally invasive — when you have a let-macro nested in a case expression, it is going to be a pain to write out something like with Task.task let ... every single time you want this. Imagine replacing all occurrences of do in Haskell code with 10+ characters.
  2. The connection to let is obvious — it is obviously some weird kind of let, no extra concepts at all.
  3. It encourages two space indent — now that I see it, I am thinking about using 2-space indent in all of my let expressions. This is a very OCaml thing to do (common practice there) and I think it looks quite nice.
  4. It guides how we specify which macro to use — another variation of this syntax that I considered was let{maybe} or let(maybe) or let[maybe] and all of these (1) overlap with existing syntax in some way and (2) make it feel nicer to give something besides a variable or qualified variable.

On point 4, I want to show an example:

add maybeLeft maybeRight =
  let! (Macro.simple Maybe.succeed Maybe.andThen)
    x := maybeLeft
    y := maybeRight
  in
    Just (x + y)

add maybeLeft maybeRight =
  let(Macro.simple Maybe.succeed Maybe.andThen)
    x := maybeLeft
    y := maybeRight
  in
    Just (x + y)

The second one feels nicer to me, which I think is a bad thing. Writing super crazy macros inline does not seem too nice to me, and I think the let! syntax makes it feel dirtier. Maybe if we can find good/valid examples of inline macro definitions this changes.

Defense of :=

I like := because:

  • I think there are already too many arrows in Elm. Types go -> and records go <- and cases go -> and it ends up looking a bit wild. In a moment I'll share a proposal to get rid of more arrows :)
  • Overall the cultural meaning seems to be "do something special" which I think is as specific as we want to be for a macro system. It is used in OCaml for working with references. It reminds me of old school languages that use it for assignment. It is used in Go for "special kind of assignment". Overall, it feels imperative and indicates something odd is happening.

I think it kind of sucks that := kind of looks like : but that's the biggest complaint I have.

Aligning Record Syntax

What if we rename record update to = and record extension to := to be parallel with let macros? Here are some examples:

point =
  { x = 3, y = 4 }

point' =
  { point | x = 5 }   -- { x = 5, y = 4 }

point3D =
  { point | z := 5 }   -- { x = 3, y = 4, z = 5 }

The idea is that we use record update all the time but we have this kind of weird update symbol. We basically never ever use record extension, but it has the super nice = symbol.

I would interpret things here as := is for when you want to update the record in a crazy special way. Same as with let macros, "I want to get this value in some special way".

getImage : (Int,Int) -> String -> Task Http.Error String
getImage dimensions tag =
let! task
photos :=
Http.get photoList <|
flickrUrl "search"
[ ("sort", "random")
, ("per_page", "10")
, ("tags", tag)
]
photo :=
Task.fromMaybe photoErr (List.head photos)
sizes :=
Http.get sizeList (flickrUrl "getSizes" [ ("photo_id", photo.id) ])
in
sizes
|> List.sortBy (sizeRating dimensions)
|> List.head
|> Task.fromMaybe sizeErr
json : Macro JS.Decoder
field : String -> JS.Decoder a -> JS.Decoder a
index : Int -> JS.Decoder a -> JS.Decoder a
person : Decoder Person
person =
let! json
name := field "name" JS.string
age := field "age" JS.int
position :=
field "position" <|
let! json
x := index 0 float
y := index 1 float
in
JS.succeed (x,y)
in
JS.succeed (Person name age position)
add : Maybe Int -> Maybe Int -> Maybe Int
add maybeLeft maybeRight =
let! maybe
x := maybeLeft
y := maybeRight
in
Just (x + y)
gen : Macro Random.Generator
Random.generate : a -> Random.Generator a
person : Random.Generator Person
person =
let! gen
fullname := string
age := int
location :=
let! gen
longitude := int
latitude := int
in
Random.generate (Location longitude latitude)
in
Random.generate (Person fullname age location)
type alias Person =
{ fullname : String
, age : Int
, location : Location
}
type alias Location =
{ longitude : Int
, latitude : Int
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment