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

    F#

    F# is a functional-first programming language that runs on .NET. It combines functional programming with object-oriented and imperative programming paradigms, offering strong type inference, data immutability, and pattern matching.

    F#, despite being a well-designed functional language, has several common anti-patterns that can lead to code that’s difficult to maintain, inefficient, or doesn’t follow the language’s idioms. Here are the most important anti-patterns to avoid when writing F# code.

    Copy
    // Anti-pattern: Excessive mutation
    let processItems items =
        let mutable sum = 0
        let mutable count = 0
        
        for item in items do
            sum <- sum + item
            count <- count + 1
            
        if count > 0 then sum / count else 0
    
    // Better approach: Use functional constructs
    let processItems items =
        let items = Seq.toList items
        match items with
        | [] -> 0
        | _ -> items |> List.sum |> fun sum -> sum / List.length items
        
    // Even better
    let processItems items =
        items 
        |> Seq.fold (fun (sum, count) item -> (sum + item, count + 1)) (0, 0)
        |> function 
           | (_, 0) -> 0
           | (sum, count) -> sum / count
    

    Avoid excessive use of mutable variables and imperative loops. F# is a functional-first language, so prefer immutable data and functional constructs like pattern matching, recursion, and higher-order functions.

    Copy
    // Anti-pattern: Not leveraging the type system
    type User = {
        Name: string
        Email: string
        Status: string  // Could be "active", "inactive", "pending"
    }
    
    let isUserActive user =
        user.Status = "active"
    
    // Better approach: Use discriminated unions
    type UserStatus =
        | Active
        | Inactive
        | Pending
    
    type User = {
        Name: string
        Email: string
        Status: UserStatus
    }
    
    let isUserActive user =
        match user.Status with
        | Active -> true
        | _ -> false
    

    Leverage F#‘s powerful type system, especially discriminated unions, to model your domain accurately. This provides compile-time safety and makes your code more expressive and self-documenting.

    Copy
    // Anti-pattern: Ignoring option types
    let findUser (users: User list) (id: string) =
        users |> List.find (fun u -> u.Id = id)  // Throws if not found
    
    // Usage that might throw
    let user = findUser users "123"
    printfn "Found user: %s" user.Name
    
    // Better approach: Use option types
    let findUser (users: User list) (id: string) =
        users |> List.tryFind (fun u -> u.Id = id)
    
    // Safe usage with pattern matching
    match findUser users "123" with
    | Some user -> printfn "Found user: %s" user.Name
    | None -> printfn "User not found"
    
    // Or using Option.map and defaultValue
    findUser users "123"
    |> Option.map (fun user -> user.Name)
    |> Option.defaultValue "Unknown user"
    |> printfn "User: %s"
    

    Use option types ('T option) to represent values that might not exist, rather than throwing exceptions or using null. This makes the possibility of missing values explicit in your type signatures and forces clients to handle both cases.

    Copy
    // Anti-pattern: Excessive OO style
    type Customer() =
        let mutable name = ""
        let mutable email = ""
        
        member this.Name
            with get() = name
            and set(value) = name <- value
            
        member this.Email
            with get() = email
            and set(value) = email <- value
            
        member this.IsValid() =
            not (String.IsNullOrEmpty(name)) && email.Contains("@")
    
    // Usage
    let customer = Customer()
    customer.Name <- "John"
    customer.Email <- "john@example.com"
    if customer.IsValid() then
        // Do something
    
    // Better approach: Use immutable records
    type Customer = {
        Name: string
        Email: string
    }
    
    let isValid customer =
        not (String.IsNullOrEmpty(customer.Name)) && customer.Email.Contains("@")
    
    // Usage
    let customer = { Name = "John"; Email = "john@example.com" }
    if isValid customer then
        // Do something
    

    Avoid excessive object-oriented style with mutable classes and properties. Instead, prefer immutable records and standalone functions. This leads to code that’s easier to reason about and test.

    Copy
    // Anti-pattern: Not using pattern matching
    let describe x =
        if x = 0 then
            "Zero"
        else if x > 0 then
            "Positive"
        else
            "Negative"
    
    let processShape shape =
        if shape.GetType() = typeof<Circle> then
            let circle = shape :?> Circle
            Math.PI * circle.Radius * circle.Radius
        else if shape.GetType() = typeof<Rectangle> then
            let rect = shape :?> Rectangle
            rect.Width * rect.Height
        else
            0.0
    
    // Better approach: Use pattern matching
    let describe x =
        match x with
        | 0 -> "Zero"
        | x when x > 0 -> "Positive"
        | _ -> "Negative"
    
    // With discriminated unions
    type Shape =
        | Circle of radius: float
        | Rectangle of width: float * height: float
        | Triangle of base': float * height: float
    
    let area shape =
        match shape with
        | Circle radius -> Math.PI * radius * radius
        | Rectangle(width, height) -> width * height
        | Triangle(base', height) -> base' * height / 2.0
    

    Use pattern matching extensively, especially with discriminated unions. Pattern matching makes your code more concise, readable, and helps ensure you handle all possible cases.

    Copy
    // Anti-pattern: Excessive type annotations
    let add (x: int) (y: int) : int =
        x + y
    
    let process (items: int list) : float =
        let sum: int = List.sum items
        let count: int = List.length items
        float sum / float count
    
    // Better approach: Let type inference work
    let add x y = x + y
    
    let process items =
        let sum = List.sum items
        let count = List.length items
        float sum / float count
    

    Avoid excessive type annotations. F# has powerful type inference, so you usually don’t need to specify types explicitly. Add type annotations only when necessary for clarity, to resolve ambiguities, or at API boundaries.

    Copy
    // Anti-pattern: Not using the pipe operator
    let result = List.map (fun x -> x * 2) (List.filter (fun x -> x % 2 = 0) [1..10])
    
    // Better approach: Use the pipe operator
    let result =
        [1..10]
        |> List.filter (fun x -> x % 2 = 0)
        |> List.map (fun x -> x * 2)
    

    Use the pipe operator (|>) to create readable data processing pipelines. The pipe operator makes the flow of data through transformations clear and reduces the need for nested function calls or intermediate variables.

    Copy
    // Anti-pattern: Ignoring partial application
    let filter predicate list =
        List.filter predicate list
    
    let map mapper list =
        List.map mapper list
    
    // Better approach: Leverage partial application
    let filter predicate =
        List.filter predicate
    
    let map mapper =
        List.map mapper
    
    // Usage
    let evenNumbers =
        [1..10]
        |> filter (fun x -> x % 2 = 0)
        |> map (fun x -> x * 2)
    

    Leverage partial application to create specialized functions from more general ones. This allows for more concise and composable code.

    Copy
    // Anti-pattern: Complex pattern matching without active patterns
    let categorizeNumber x =
        match x with
        | x when x = 0 -> "Zero"
        | x when x > 0 && x % 2 = 0 -> "Positive even"
        | x when x > 0 -> "Positive odd"
        | x when x % 2 = 0 -> "Negative even"
        | _ -> "Negative odd"
    
    // Better approach: Use active patterns
    let (|Zero|Positive|Negative|) x =
        if x = 0 then Zero
        elif x > 0 then Positive x
        else Negative x
    
    let (|Even|Odd|) x =
        if x % 2 = 0 then Even x else Odd x
    
    let categorizeNumber x =
        match x with
        | Zero -> "Zero"
        | Positive(Even _) -> "Positive even"
        | Positive(Odd _) -> "Positive odd"
        | Negative(Even _) -> "Negative even"
        | Negative(Odd _) -> "Negative odd"
    

    Use active patterns to encapsulate complex matching logic and make pattern matching more readable and maintainable. Active patterns allow you to abstract away the details of how values are matched.

    Copy
    // Anti-pattern: Not using units of measure
    let distance = 5.0  // Is this meters, feet, or miles?
    let time = 2.0      // Is this hours, minutes, or seconds?
    let speed = distance / time
    
    // Better approach: Use units of measure
    [<Measure>] type m
    [<Measure>] type s
    [<Measure>] type h
    
    let distance = 5.0<m>
    let time = 2.0<s>
    let speed = distance / time  // Type is float<m/s>
    
    // Convert to km/h
    [<Measure>] type km
    let kmPerHour = (speed * 3600.0<s/h>) * (1.0<km/1000.0<m>>)
    

    Use units of measure to add physical units to your numeric types. This prevents mixing incompatible quantities and makes your code more self-documenting and less prone to errors.

    Copy
    // Anti-pattern: Nested monadic operations without computation expressions
    let getUserData userId =
        match tryFindUser userId with
        | None -> Error "User not found"
        | Some user ->
            match tryGetUserPreferences user.Id with
            | None -> Error "Preferences not found"
            | Some prefs ->
                match tryGetRecommendations prefs with
                | None -> Error "No recommendations"
                | Some recs -> Ok (user, prefs, recs)
    
    // Better approach: Use computation expressions
    let getUserData userId = result {
        let! user = tryFindUser userId
        let! prefs = tryGetUserPreferences user.Id
        let! recs = tryGetRecommendations prefs
        return (user, prefs, recs)
    }
    
    // Another example with async
    let fetchData url = async {
        let! response = httpClient.GetAsync(url) |> Async.AwaitTask
        let! content = response.Content.ReadAsStringAsync() |> Async.AwaitTask
        return content
    }
    

    Use computation expressions (like async, task, result, option, etc.) to simplify working with monadic types. They make your code more readable by eliminating nested pattern matching and allowing a more imperative-like syntax for functional concepts.

    Copy
    // Anti-pattern: Manual data access code
    let getCustomers() =
        use connection = new SqlConnection(connectionString)
        connection.Open()
        use command = new SqlCommand("SELECT Id, Name, Email FROM Customers", connection)
        use reader = command.ExecuteReader()
        
        let customers = ResizeArray<Customer>()
        while reader.Read() do
            let customer = {
                Id = reader.GetInt32(0)
                Name = reader.GetString(1)
                Email = reader.GetString(2)
            }
            customers.Add(customer)
            
        customers |> Seq.toList
    
    // Better approach: Use type providers
    type Sql = SqlDataProvider<...>  // Configuration omitted for brevity
    
    let getCustomers() =
        use context = Sql.GetDataContext()
        query {
            for customer in context.Customers do
            select { 
                Id = customer.Id
                Name = customer.Name
                Email = customer.Email
            }
        }
        |> Seq.toList
    

    Use type providers to access external data sources like databases, web services, or file formats. Type providers generate types based on the schema of the data source, providing compile-time checking and IntelliSense support.

    Copy
    // Anti-pattern: Recursion without tail calls
    let rec factorial n =
        if n <= 1 then 1
        else n * factorial (n - 1)  // Not tail-recursive
    
    // Better approach: Use tail recursion
    let factorial n =
        let rec loop n acc =
            if n <= 1 then acc
            else loop (n - 1) (n * acc)  // Tail-recursive
        loop n 1
    

    When using recursion, make your functions tail-recursive to avoid stack overflow errors with large inputs. In a tail-recursive function, the recursive call is the last operation in the function.

    Copy
    // Anti-pattern: Everything in one file without organization
    // MyApp.fs
    module MyApp
    
    // Types, functions, and values all mixed together
    
    // Better approach: Organize code into modules and namespaces
    // Domain.fs
    namespace MyApp.Domain
    
    type User = { /* ... */ }
    type Order = { /* ... */ }
    
    // Services.fs
    namespace MyApp.Services
    
    open MyApp.Domain
    
    module UserService =
        let findUser userId = // ...
        let updateUser user = // ...
    
    module OrderService =
        let placeOrder userId items = // ...
        let cancelOrder orderId = // ...
    

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

    Copy
    // Anti-pattern: Exception-based error handling
    let validateEmail email =
        if String.IsNullOrEmpty(email) then
            raise (ArgumentException("Email cannot be empty"))
        elif not (email.Contains("@")) then
            raise (ArgumentException("Email must contain @"))
        else
            email
    
    let validatePassword password =
        if String.IsNullOrEmpty(password) then
            raise (ArgumentException("Password cannot be empty"))
        elif password.Length < 8 then
            raise (ArgumentException("Password must be at least 8 characters"))
        else
            password
    
    // Better approach: Railway-oriented programming
    type ValidationResult<'T> = Result<'T, string>
    
    let validateEmail email : ValidationResult<string> =
        if String.IsNullOrEmpty(email) then
            Error "Email cannot be empty"
        elif not (email.Contains("@")) then
            Error "Email must contain @"
        else
            Ok email
    
    let validatePassword password : ValidationResult<string> =
        if String.IsNullOrEmpty(password) then
            Error "Password cannot be empty"
        elif password.Length < 8 then
            Error "Password must be at least 8 characters"
        else
            Ok password
    
    // Compose validations
    let validateCredentials email password =
        result {
            let! validEmail = validateEmail email
            let! validPassword = validatePassword password
            return (validEmail, validPassword)
        }
    

    Use railway-oriented programming (using Result<'T, 'Error> or similar types) for error handling instead of exceptions. This makes error paths explicit in your code and allows for better composition of functions that might fail.

    Copy
    // Anti-pattern: Blocking async code
    let fetchData url =
        async {
            let! response = httpClient.GetAsync(url) |> Async.AwaitTask
            return response
        } |> Async.RunSynchronously  // Blocks the thread
    
    // Better approach: Keep async workflows async
    let fetchData url =
        async {
            let! response = httpClient.GetAsync(url) |> Async.AwaitTask
            return response
        }
    
    // Usage
    let processDataAsync() = async {
        let! data = fetchData "https://example.com/api/data"
        let! moreData = fetchData "https://example.com/api/more-data"
        return processResults data moreData
    }
    
    // Only at the top level or entry point
    let main args =
        processDataAsync() |> Async.RunSynchronously
    

    Use Async and Task properly for asynchronous operations. Don’t block async workflows with Async.RunSynchronously except at the top level of your application. Compose async operations using computation expressions.

    Copy
    // Anti-pattern: Using mutable collections
    let processItems items =
        let results = ResizeArray<int>()
        for item in items do
            if item % 2 = 0 then
                results.Add(item * 2)
        results |> Seq.toList
    
    // Better approach: Use immutable collections and transformations
    let processItems items =
        items
        |> List.filter (fun item -> item % 2 = 0)
        |> List.map (fun item -> item * 2)
    

    Prefer immutable collections (list, seq, array, etc.) and functional transformations over mutable collections and in-place modifications. This leads to code that’s easier to reason about and less prone to bugs.

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