Lesser known syntax

Please share your knowledge of any lesser known Elm syntax that is not mentioned in the official overview (http://elm-lang.org/docs/syntax) and is possibly buried somewhere else and easy to miss.

I was pleased to find out that for function parameters that are records or tuples, you do not need to choose between either destructuring that parameter OR giving a name to it as a whole. You can do both at the same time using the wrapping (… as foo) syntax!

f a b c =
  …

f a {x, y} c =
  …

:point_down:

f a ({x, y} as b) c =
  …
22 Likes

I was also excited when I discovered this syntax. It should really be mentioned in the guide!

Here is another useful thing: you can group several imports under the same name.

import List.Extra as List
import MyProject.Support.List as List

Now all functions from these two modules as well as the official List module will be available under the name List.

9 Likes

Be careful with this as you will get a compiler error if the packages export values of the same name.

2 Likes

Union types with only one constructor can be destructured without case ... of:

type MyId = MyIdConst Int

exists : MyId -> List Int -> Bool
exists (MyIdConst id) ids =
    -- id is Int

let
    (MyIdConst id) = wrappedId
in
    -- id is Int
17 Likes

_ is a throwaway variable that you can assign to multiple times with unrelated types;

main : Html msg
main =
    let
        _ =
            5
            
        _ =
            "five"
    in
    text "Hello, World!"

compiles but

main : Html msg
main =
    let
        a =
            5
            
        a =
            "five"
    in
    text "Hello, World!"

gives a compile error. (This is typically only useful when making Debug.log calls.)

10 Likes

I found this gist very helpful when initially learned Elm: https://gist.github.com/yang-wei/4f563fbf81ff843e8b1e

6 Likes

Has anyone described the rules for infix symbols available for function names? I know this is discouraged in practice, but nevertheless, it’s interesting

It’s in the guide, but easy to miss: .foo is an alias for \record -> record.foo. Similar “dot accessors” are in scope for any identifier.

4 Likes

Record types can contain a type variable to say “I don’t care what else is in this record as long as these fields are”. It’s really useful for passing your model to your view function, but using the type system to show that the view doesn’t rely on most of the fields, and it lets you fuzz smaller inputs.

Example:

distance : { a | x : Float, y : Float } -> { b | x : Float, y : Float } -> Float
distance p1 p2 = ...

dist = distance {x=2, y=3} {x=4, y=5, z=6} 

By using different type variables, we say that the other fields in the two records don’t need to be the same. That allows us to pass two different types and get a sensible answer out.

(Unfortunately, there’s not a way to take either 2D or 3D coordinates and default aTwoDimensionalPoint.z to zero. Define your 3D type and then have a function that takes two arguments and fill in the zero.)

6 Likes

Yes, you will get an error about ambiguity, but only if you try to use such values. But in this case you can always refactor so it’s safe!

Moreover, it’s even possible to import a module more than one time with different names:

import List.Extra 
import List.Extra as List

Now you can use List.Extra when there is an ambiguity and just List in other cases. Although it’s probably not worth it since it will make the code less clear (also elm-format doesn’t allow this).

3 Likes

This actually doesn’t sound like a desirable behavior, as it would force anyone unfamiliar with either module to search through both to find details about an unfamiliar function call.

1 Like

Yes. For a language and community that generally seems to value being explicit, having a mechanism that slams two modules together seems to cut against that. Of course, so does using (..) in imports which slams everything into the module namespace, so maybe this is just more of the same.

Mark

Mark, can you find less hostile ways to express your views from now on?

8 Likes

Didn’t know about this until the Rubiks cube thread:

Elm can also parse GLSL shaders and has special syntax for handling them:

fragmentShader : Shader {} Uniforms {}
fragmentShader =
    [glsl|
        precision mediump float;
        uniform vec3 color;

        void main () {
          gl_FragColor = vec4(color, 1.0);
        }
    |]

http://package.elm-lang.org/packages/elm-community/webgl/2.0.5/

2 Likes

There’s probably something for you here, if you want to understand why it works this way from a design perspective - rhetoric aside, it also seems like undesireable behavior from my perspective.

This sounds like something I would find useful, but as a beginner I’m struggling to appreciate the detail. Are you able to expand on this at all, please?

Here is a pretty neat write-up on extensible records:

5 Likes

A word of warning, as a beginner I really overused extensible records and got myself into some painful situations. In particular, don’t try to replicate an object-oriented-style inheritance system with them :grimacing:

I’ve found them most useful in function signatures rather than as part of my models:

-- THIS

filterByName : String -> List { name : String } -> List { name : String }
filterByName name items =
  List.filter (\item -> item.name == name) items

-- NOT THIS

type alias Nameable a = { a | name : String }

type alias User = Nameable { age : Int }
type alias Building = Nameable { address : String }
13 Likes