Matter AI | Code Reviewer Documentation home pagelight logodark logo
  • Contact
  • Github
  • Sign in
  • Sign in
  • Documentation
  • Blog
  • Discord
  • Github
  • Introduction
    • What is Matter AI?
    Getting Started
    • QuickStart
    Product
    • Security Analysis
    • Code Quality
    • Agentic Chat
    • RuleSets
    • Memories
    • Analytics
    • Command List
    • Configurations
    Patterns
    • Languages
      • Supported Languages
      • Python
      • Java
      • JavaScript
      • TypeScript
      • Node.js
      • React
      • Fastify
      • Next.js
      • Terraform
      • C#
      • C++
      • C
      • Go
      • Rust
      • Swift
      • React Native
      • Spring Boot
      • Kotlin
      • Flutter
      • Ruby
      • PHP
      • Scala
      • Perl
      • R
      • Dart
      • Elixir
      • Erlang
      • Haskell
      • Lua
      • Julia
      • Clojure
      • Groovy
      • Fortran
      • COBOL
      • Pascal
      • Assembly
      • Bash
      • PowerShell
      • SQL
      • PL/SQL
      • T-SQL
      • MATLAB
      • Objective-C
      • VBA
      • ABAP
      • Apex
      • Apache Camel
      • Crystal
      • D
      • Delphi
      • Elm
      • F#
      • Hack
      • Lisp
      • OCaml
      • Prolog
      • Racket
      • Scheme
      • Solidity
      • Verilog
      • VHDL
      • Zig
      • MongoDB
      • ClickHouse
      • MySQL
      • GraphQL
      • Redis
      • Cassandra
      • Elasticsearch
    • Security
    • Performance
    Integrations
    • Code Repositories
    • Team Messengers
    • Ticketing
    Enterprise
    • Enterprise Deployment Overview
    • Enterprise Configurations
    • Observability and Fallbacks
    • Create Your Own GitHub App
    • Self-Hosting Options
    • RBAC
    Patterns
    Languages

    Elm

    Elm is a functional programming language that compiles to JavaScript. It is known for its emphasis on simplicity, ease-of-use, and its ability to generate code with no runtime exceptions.

    Elm, despite being designed to prevent many common programming errors, still has several anti-patterns that can lead to maintainability issues, performance problems, and code that doesn’t follow the language’s idioms. Here are the most important anti-patterns to avoid when writing Elm code.

    -- Anti-pattern: Excessive use of Maybe.withDefault
    viewUser : Maybe User -> Html msg
    viewUser maybeUser =
        div []
            [ h1 [] [ text (Maybe.withDefault "Unknown" (Maybe.map .name maybeUser)) ]
            , p [] [ text (Maybe.withDefault "No email" (Maybe.map .email maybeUser)) ]
            , p [] [ text (Maybe.withDefault "No bio" (Maybe.map .bio maybeUser)) ]
            ]
    
    -- Better approach: Use case expressions
    viewUser : Maybe User -> Html msg
    viewUser maybeUser =
        case maybeUser of
            Just user ->
                div []
                    [ h1 [] [ text user.name ]
                    , p [] [ text user.email ]
                    , p [] [ text user.bio ]
                    ]
            
            Nothing ->
                div []
                    [ h1 [] [ text "Unknown" ]
                    , p [] [ text "No email" ]
                    , p [] [ text "No bio" ]
                    ]

    Avoid excessive use of Maybe.withDefault. While it’s convenient for simple cases, using case expressions provides more control and clarity, especially when dealing with multiple fields from the same Maybe value.

    -- Anti-pattern: Stringly-typed code
    type alias User =
        { status : String  -- Could be "active", "inactive", "pending"
        }
    
    isUserActive : User -> Bool
    isUserActive user =
        user.status == "active"
    
    -- Better approach: Use custom types
    type UserStatus
        = Active
        | Inactive
        | Pending
    
    type alias User =
        { status : UserStatus
        }
    
    isUserActive : User -> Bool
    isUserActive user =
        case user.status of
            Active ->
                True
            
            _ ->
                False

    Avoid using strings to represent a fixed set of values. Use custom types instead, which provide type safety, better documentation, and exhaustive pattern matching.

    -- Anti-pattern: Deeply nested record updates
    type alias Address =
        { street : String
        , city : String
        , zipCode : String
        }
    
    type alias User =
        { name : String
        , address : Address
        }
    
    updateZipCode : String -> User -> User
    updateZipCode newZipCode user =
        { user | address = { street = user.address.street
                          , city = user.address.city
                          , zipCode = newZipCode
                          }
        }
    
    -- Better approach: Use lenses or helper functions
    updateAddress : (Address -> Address) -> User -> User
    updateAddress updater user =
        { user | address = updater user.address }
    
    updateZipCode : String -> User -> User
    updateZipCode newZipCode =
        updateAddress (\address -> { address | zipCode = newZipCode })

    Avoid deeply nested record updates, which are verbose and error-prone. Instead, create helper functions that focus on updating specific parts of your data structure.

    -- Anti-pattern: Using List for everything
    type alias Model =
        { users : List User
        , selectedUserId : Maybe String
        }
    
    getUserById : String -> Model -> Maybe User
    getUserById id model =
        List.filter (\user -> user.id == id) model.users
            |> List.head
    
    -- Better approach: Use Dict for lookups
    import Dict exposing (Dict)
    
    type alias Model =
        { users : Dict String User
        , selectedUserId : Maybe String
        }
    
    getUserById : String -> Model -> Maybe User
    getUserById id model =
        Dict.get id model.users

    Don’t use List for everything. Use appropriate data structures like Dict for lookups, Set for unique collections, and Array for indexed access. This improves performance and makes your code more expressive.

    -- Anti-pattern: Overusing flags for configuration
    type alias Flags =
        { apiUrl : String
        , debugMode : Bool
        , theme : String
        , timeout : Int
        , maxRetries : Int
        , features : List String
        -- Many more configuration options...
        }
    
    init : Flags -> (Model, Cmd Msg)
    init flags =
        -- Initialize with all flag values
    
    -- Better approach: Use a combination of flags and remote configuration
    type alias Flags =
        { apiUrl : String
        , initialTheme : String
        }
    
    init : Flags -> (Model, Cmd Msg)
    init flags =
        ( initialModel flags
        , fetchConfiguration flags.apiUrl
        )
    
    -- Then load the rest of the configuration from the server

    Avoid overusing flags for application configuration. Flags should be used for essential startup information, but excessive flags make initialization complex and brittle. Consider loading configuration from a server or using reasonable defaults.

    -- Anti-pattern: Violating The Elm Architecture
    type alias Model =
        { counter : Int
        , message : String
        }
    
    type Msg
        = Increment
        | Decrement
    
    update : Msg -> Model -> Model  -- No Cmd here!
    update msg model =
        case msg of
            Increment ->
                { model | counter = model.counter + 1, message = "Incremented" }
            
            Decrement ->
                { model | counter = model.counter - 1, message = "Decremented" }
    
    -- Better approach: Follow The Elm Architecture
    update : Msg -> Model -> (Model, Cmd Msg)
    update msg model =
        case msg of
            Increment ->
                ( { model | counter = model.counter + 1 }
                , Cmd.none
                )
            
            Decrement ->
                ( { model | counter = model.counter - 1 }
                , Cmd.none
                )

    Always follow The Elm Architecture. Don’t try to work around it by storing state outside the model, using ports for things that could be modeled in Elm, or ignoring the Cmd part of the update function.

    -- Anti-pattern: Missing type annotations
    add a b =
        a + b
    
    view model =
        div [] [ text model.message ]
    
    -- Better approach: Include type annotations
    add : Int -> Int -> Int
    add a b =
        a + b
    
    view : Model -> Html Msg
    view model =
        div [] [ text model.message ]

    Always include type annotations for top-level functions. Type annotations serve as documentation, help catch errors earlier, and make refactoring safer.

    -- Anti-pattern: Excessive nesting
    view : Model -> Html Msg
    view model =
        div [ class "container" ]
            [ div [ class "row" ]
                [ div [ class "col" ]
                    [ div [ class "card" ]
                        [ div [ class "card-header" ]
                            [ h2 [] [ text "User Profile" ]
                            ]
                        , div [ class "card-body" ]
                            [ p [] [ text model.name ]
                            , p [] [ text model.email ]
                            ]
                        ]
                    ]
                ]
            ]
    
    -- Better approach: Break into smaller functions
    view : Model -> Html Msg
    view model =
        div [ class "container" ]
            [ div [ class "row" ]
                [ div [ class "col" ]
                    [ viewCard model ]
                ]
            ]
    
    viewCard : Model -> Html Msg
    viewCard model =
        div [ class "card" ]
            [ viewCardHeader
            , viewCardBody model
            ]
    
    viewCardHeader : Html Msg
    viewCardHeader =
        div [ class "card-header" ]
            [ h2 [] [ text "User Profile" ] ]
    
    viewCardBody : Model -> Html Msg
    viewCardBody model =
        div [ class "card-body" ]
            [ p [] [ text model.name ]
            , p [] [ text model.email ]
            ]

    Avoid excessive nesting of HTML elements. Break your view into smaller, focused functions to improve readability and maintainability.

    -- Anti-pattern: Using type aliases for domain concepts
    type alias UserId = String
    type alias Email = String
    
    sendEmail : Email -> UserId -> Cmd Msg
    sendEmail email userId =
        -- Implementation
    
    -- Usage (can easily mix up parameters)
    sendEmail userId email  -- Oops, parameters swapped!
    
    -- Better approach: Use opaque types
    module UserId exposing (UserId, toString, fromString)
    
    type UserId = UserId String
    
    toString : UserId -> String
    toString (UserId id) = id
    
    fromString : String -> UserId
    fromString = UserId
    
    -- In another module
    module Email exposing (Email, toString, fromString)
    
    type Email = Email String
    
    toString : Email -> String
    toString (Email email) = email
    
    fromString : String -> Email
    fromString = Email
    
    -- Usage (now type-safe)
    sendEmail : Email -> UserId -> Cmd Msg
    sendEmail email userId =
        -- Implementation
    
    -- This would now be a compile error:
    -- sendEmail userId email

    Use opaque types to represent domain concepts instead of type aliases. Opaque types provide better type safety and prevent accidental misuse of values.

    -- Anti-pattern: Excessive use of tuples
    type alias Model =
        { user : (String, String, Int)  -- (name, email, age)
        , position : (Float, Float)    -- (x, y)
        }
    
    updateUserAge : Int -> Model -> Model
    updateUserAge newAge model =
        let
            (name, email, _) = model.user
        in
        { model | user = (name, email, newAge) }
    
    -- Better approach: Use records
    type alias User =
        { name : String
        , email : String
        , age : Int
        }
    
    type alias Position =
        { x : Float
        , y : Float
        }
    
    type alias Model =
        { user : User
        , position : Position
        }
    
    updateUserAge : Int -> Model -> Model
    updateUserAge newAge model =
        { model | user = { model.user | age = newAge } }

    Avoid excessive use of tuples, especially for domain concepts with more than two values. Use records instead, which provide named fields and better documentation.

    -- Anti-pattern: Using strings or tuples for messages
    type Msg
        = UserAction String  -- "edit", "delete", "view"
        | ItemSelected (Int, String)  -- (id, name)
    
    update : Msg -> Model -> (Model, Cmd Msg)
    update msg model =
        case msg of
            UserAction action ->
                case action of
                    "edit" ->
                        -- Handle edit
                    "delete" ->
                        -- Handle delete
                    "view" ->
                        -- Handle view
                    _ ->
                        (model, Cmd.none)  -- Unknown action
            
            ItemSelected (id, name) ->
                -- Handle item selection
    
    -- Better approach: Use nested custom types
    type UserAction
        = Edit
        | Delete
        | View
    
    type Msg
        = UserMsg UserAction
        | ItemSelected { id : Int, name : String }
    
    update : Msg -> Model -> (Model, Cmd Msg)
    update msg model =
        case msg of
            UserMsg action ->
                case action of
                    Edit ->
                        -- Handle edit
                    Delete ->
                        -- Handle delete
                    View ->
                        -- Handle view
            
            ItemSelected item ->
                -- Handle item selection with named fields

    Use custom types for messages instead of strings or tuples. Custom types provide better type safety, documentation, and exhaustive pattern matching.

    -- Anti-pattern: Everything in one module
    module Main exposing (..)  -- Exposing everything
    
    -- Hundreds of types, functions, and helpers all in one file
    
    -- Better approach: Organize code into modules
    -- Main.elm
    module Main exposing (main)
    
    import Model exposing (Model, Msg, init, update)
    import View exposing (view)
    
    main : Program Flags Model Msg
    main =
        Browser.element
            { init = init
            , update = update
            , view = view
            , subscriptions = subscriptions
            }
    
    -- Model.elm
    module Model exposing (Model, Msg(..), init, update)
    
    -- Types and logic
    
    -- View.elm
    module View exposing (view)
    
    -- View functions
    
    -- Types.elm
    module Types exposing (User, UserId, UserStatus(..))
    
    -- Domain types

    Organize your code into modules based on functionality. This improves maintainability, compilation times, and allows for better encapsulation of implementation details.

    -- Anti-pattern: Not using Maybe for optional values
    type alias User =
        { name : String
        , email : String
        , phone : String  -- Empty string means no phone
        }
    
    displayPhone : User -> Html msg
    displayPhone user =
        if String.isEmpty user.phone then
            text "No phone number"
        else
            text user.phone
    
    -- Better approach: Use Maybe
    type alias User =
        { name : String
        , email : String
        , phone : Maybe String
        }
    
    displayPhone : User -> Html msg
    displayPhone user =
        case user.phone of
            Just phone ->
                text phone
            
            Nothing ->
                text "No phone number"
    
    -- Anti-pattern: Not using Result for operations that can fail
    parseAge : String -> Int
    parseAge input =
        String.toInt input |> Maybe.withDefault 0  -- Default to 0 on failure
    
    -- Better approach: Use Result
    parseAge : String -> Result String Int
    parseAge input =
        case String.toInt input of
            Just age ->
                if age >= 0 then
                    Ok age
                else
                    Err "Age cannot be negative"
            
            Nothing ->
                Err "Invalid age format"

    Use Maybe for optional values and Result for operations that can fail. These types make your code more expressive and force you to handle all possible cases.

    -- Anti-pattern: Manual JSON parsing
    type alias User =
        { id : Int
        , name : String
        , email : String
        }
    
    decodeUser : Json.Decode.Value -> Maybe User
    decodeUser json =
        let
            idMaybe = Json.Decode.decodeValue (Json.Decode.field "id" Json.Decode.int) json
            nameMaybe = Json.Decode.decodeValue (Json.Decode.field "name" Json.Decode.string) json
            emailMaybe = Json.Decode.decodeValue (Json.Decode.field "email" Json.Decode.string) json
        in
        case (idMaybe, nameMaybe, emailMaybe) of
            (Ok id, Ok name, Ok email) ->
                Just { id = id, name = name, email = email }
            
            _ ->
                Nothing
    
    -- Better approach: Use the decoder pipeline
    import Json.Decode as Decode exposing (Decoder)
    import Json.Decode.Pipeline exposing (required, optional)
    
    userDecoder : Decoder User
    userDecoder =
        Decode.succeed User
            |> required "id" Decode.int
            |> required "name" Decode.string
            |> required "email" Decode.string
    
    decodeUser : Json.Decode.Value -> Result Decode.Error User
    decodeUser json =
        Decode.decodeValue userDecoder json

    Use Elm’s JSON decoders properly. Don’t try to manually parse JSON; instead, build composable decoders using the decoder library. This approach is more maintainable and handles errors better.

    -- Anti-pattern: Using ports for things that could be subscriptions
    port module Main exposing (..)
    
    port receiveTime : (Int -> msg) -> Sub msg
    
    -- JavaScript side:
    // app.ports.receiveTime.send(Date.now());
    // setInterval(() => app.ports.receiveTime.send(Date.now()), 1000);
    
    -- Better approach: Use built-in subscriptions
    subscriptions : Model -> Sub Msg
    subscriptions model =
        Time.every 1000 Tick

    Use Elm’s built-in subscriptions for things like time, animation frames, and keyboard events. Only use ports for functionality that truly can’t be handled within Elm.

    -- Anti-pattern: Debug statements left in production code
    update : Msg -> Model -> (Model, Cmd Msg)
    update msg model =
        case Debug.log "msg" msg of
            Increment ->
                ( { model | counter = Debug.log "new counter" (model.counter + 1) }
                , Cmd.none
                )
    
    -- Better approach: Use the Elm debugger during development
    -- main =
    --     Browser.element
    --         { init = init
    --         , update = update
    --         , view = view
    --         , subscriptions = subscriptions
    --         }
    
    -- During development:
    main =
        Browser.document
            { init = init
            , update = updateWithLogging
            , view = view
            , subscriptions = subscriptions
            }
    
    updateWithLogging : Msg -> Model -> (Model, Cmd Msg)
    updateWithLogging msg model =
        let
            _ = Debug.log "msg" msg
            (newModel, cmd) = update msg model
            _ = Debug.log "new model" newModel
        in
        (newModel, cmd)
    
    -- For production, use a flag to disable logging

    Use the Elm debugger and time-traveling debugger during development instead of littering your code with Debug.log statements. For production, ensure all debug statements are removed.

    -- Anti-pattern: No tests
    
    -- Better approach: Write tests
    module Tests exposing (..)
    
    import Expect
    import Fuzz exposing (int, string)
    import Test exposing (Test, describe, fuzz, test)
    
    import MyModule exposing (addOne, validateEmail)
    
    suite : Test
    suite =
        describe "MyModule"
            [ describe "addOne"
                [ test "adds 1 to a positive number" <|
                    \_ ->
                        Expect.equal 43 (addOne 42)
                , test "adds 1 to a negative number" <|
                    \_ ->
                        Expect.equal 0 (addOne -1)
                ]
            , describe "validateEmail"
                [ fuzz string "rejects empty strings" <|
                    \randomString ->
                        if String.isEmpty (String.trim randomString) then
                            Expect.equal False (validateEmail randomString)
                        else
                            Expect.pass
                , test "accepts valid email" <|
                    \_ ->
                        Expect.equal True (validateEmail "user@example.com")
                ]
            ]

    Write tests for your Elm code. Elm’s type system catches many errors, but tests are still important for verifying business logic, edge cases, and integration between components.

    -- Anti-pattern: Inconsistent formatting
    type Msg = Increment|Decrement|Reset
    
    update msg model=
      case msg of
        Increment->{ model | count = model.count + 1 }
        Decrement->{ model | count = model.count - 1 }
        Reset->{ model | count = 0 }
    
    -- Better approach: Use elm-format
    type Msg
        = Increment
        | Decrement
        | Reset
    
    update : Msg -> Model -> Model
    update msg model =
        case msg of
            Increment ->
                { model | count = model.count + 1 }
            
            Decrement ->
                { model | count = model.count - 1 }
            
            Reset ->
                { model | count = 0 }

    Follow Elm’s formatting conventions by using elm-format. Consistent formatting makes your code more readable and helps avoid trivial discussions about style in code reviews.

    DelphiF#
    websitexgithublinkedin
    Powered by Mintlify
    Assistant
    Responses are generated using AI and may contain mistakes.