Getting Started with Shards

Introduction to Shards

Shards is a data flow programming language where data moves through pipelines of operations called "shards". Unlike traditional programming languages, Shards focuses on data transformation through pipes (|) rather than function calls.

Data Flow Programming

In traditional programming, you might write:

result = process(validate(parse(input)))

In Shards, data flows left to right through pipes:

input | Parse | Validate | Process = result

This flow-based approach makes data transformations clear and intuitive:

; Multiple operations in a flow
"5" | FromJson | Math.Add(3) | Log    ; "5" -> 5 -> 8 -> print 8

; Multiple operations on same input using blocks
input | {
  Math.Add(1) | Log         ; First operation
  Math.Multiply(2) | Log    ; Second operation on original input
}

Core Concepts

1. Wires

Wires are the basic building blocks in Shards. They define a pipeline of operations:

; Basic wire definition
@wire(example {
  "Hello" | Log              ; Data flows through pipes
  5 | Math.Add(3) = result   ; Store results in variables
})

2. Variables & Assignment

Shards has three types of assignment:

; Immutable assignment (=)
5 = constant                  ; Can't be changed
"data" = immutable-string

; Mutable declaration (>=)
0 >= counter                  ; Can be updated
"" >= message

; Update mutable (>)
counter | Math.Add(1) > counter    ; Increment counter
"new text" > message               ; Update message

3. Basic Types

; Numbers
42 = integer
3.14 = float
0xFF = hex-number

; Strings
"Hello World" = text

; Vectors
@f3(1.0 2.0 3.0) = float-vec3    ; 3D float vector
@i2(640 480) = int-vec2          ; 2D integer vector

; Sequences (lists)
[1 2 3 4] = numbers              ; Number sequence
["a" "b" "c"] = strings          ; String sequence

; Tables (dictionaries)
{
  name: "Alice"
  age: 30
  scores: [85 92 78]
} = user-data

4. Control Flow

; Conditionals
value | If(IsMore(10)
  {"Greater than 10" | Log}
  {"Less or equal to 10" | Log}
)

; When condition
input | When(IsEmpty {
  "Input is empty" | Error
})

; ForEach loop
[1 2 3] | ForEach({
  Math.Multiply(2) | Log    ; Prints: 2, 4, 6
})

5. Error Handling

; Try-catch pattern
Maybe({
  "bad-json" | FromJson     ; Try this
} {
  "Parse failed" | Log      ; Handle error
})

; Validation with error
input | When(Not(IsString) {
  "Expected string input" | Error
})

Using Shards in edge talk

In edge talk, we use Shards to create tools that process data and interact with external services. A typical tool has this structure:

; Inner wire that does the work
@wire(tool-inner {
  ; Get input parameter
  {Take("input") | ExpectString = input}
  
  ; Process the input
  input | Process | Transform
} Pure: true)    ; Pure ensures clean state

; Main wire that runs our tool
@wire(tool-name {
  Do(tool-inner)
})

; Tool definition for edge talk
{
  definition: {
    name: "my_tool"
    description: "What this tool does"
    parameters: {
      type: "object"
      properties: {
        input: {
          type: "string"
          description: "The input to process"
        }
      }
      required: ["input"]
    }
  }
  
  use: tool-name
}

With these fundamentals in mind, let's look at how to create practical tools with Shards.

Key Concepts

Data Flow Programming

In traditional programming, you might write:

result = process(validate(parse(input)))

In Shards, data flows left to right through pipes:

input | Parse | Validate | Process = result

Tool Structure

Edge talk tools typically have three parts:

; Inner wire that does the actual work
@wire(tool-inner {
  ; Process inputs and return result
} Pure: true)

; Main wire that executes our tool
@wire(tool-name {
  Do(tool-i)
})

; Tool definition for edge talk
{
  definition: {
    name: "tool_name"
    description: "What the tool does"
    parameters: {
      type: "object"
      properties: {
        param1: {
          type: "string"
          description: "Parameter description"
        }
      }
      required: ["param1"]
    }
  }
  
  use: tool-name
}

Your First Tool

Let's create a simple tool that processes and validates user input:

; Process user data
@wire(process-user-inner {
  ; Extract and validate input
  {Take("name") | ExpectString = name}
  {Take("email") | ExpectString = email}
  
  ; Validate email format
  email | When(Not(Match("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")) {
    "Invalid email format" | Error
  })
  
  ; Format output
  {
    name: name
    email: email
    timestamp: Time.Now
    validated: true
  } | ToJson
} Pure: true)

; Main wire
@wire(process-user {
  Do(process-user-inner)
})

; Tool definition
{
  definition: {
    name: "process_user"
    description: "Process and validate user information"
    parameters: {
      type: "object"
      properties: {
        name: {
          type: "string"
          description: "User's full name"
        }
        email: {
          type: "string"
          description: "User's email address"
        }
      }
      required: ["name" "email"]
    }
  }
  
  use: process-user
}

Data Types & Validation

Input Validation

; Common validation patterns
{Take("input") | ExpectString = str-input}
{Take("count") | ExpectInt = int-input}
{Take("data") | ExpectTable = table-input}
{Take("items") | ExpectSeq = seq-input}

; Optional inputs with defaults
{Take("limit") | Default("10") | ExpectString = limit}
{Take("offset") | Default("0") | ExpectString = offset}

String Operations

; String formatting with mixed types
["User " name " created at " timestamp] | String.Format = message

; String manipulation
input | Match("pattern") = matched
input | Split(" ") = words
["a" "b" "c"] | String.Join = joined

Tables & JSON

; Create table
{
  id: "123"
  name: "Test"
  values: [1 2 3]
} = data-table

; Convert to/from JSON
data-table | ToJson = json-str
json-str | FromJson | ExpectTable = parsed-table

Error Handling

; Validation and errors
input | When(IsEmpty {
  "Input cannot be empty" | Error
})

; Try-catch pattern
Maybe({
  json-str | FromJson | Process
} {
  "Failed to process JSON" | Error
})

; Conditional processing
value | If(IsMore(10)
  {Process.Large(value)}
  {Process.Small(value)}
)

HTTP Tools

@wire(fetch-data-inner {
  ; Setup headers
  {
    "Authorization": (["Bearer " token] | String.Join)
    "Content-Type": "application/json"
  } = headers
  
  ; Make request
  {
    limit: "10"
    offset: "0"
  } | Http.Get("https://api.example.com/data" Headers: headers)
  
  ; Process response
  FromJson | ExpectTable
  Take("items") | ExpectSeq
  ForEach({
    ExpectTable
    Process.Item
  })
} Pure: true)

Best Practices

  • Always validate inputs with Expect* shards
  • Use Pure: true for tool wires to ensure clean state
  • Provide clear error messages
  • Document parameters thoroughly
  • Break complex operations into smaller wires
  • Use consistent naming conventions