Hone logo
Hone
Problems

Go Pattern Matching

This challenge involves implementing a simplified version of pattern matching, a powerful feature found in many functional programming languages. Pattern matching allows you to conditionally execute code based on the structure and values of data. This exercise will deepen your understanding of Go's type system, interfaces, and control flow.

Problem Description

You need to implement a Match function that takes a value and a series of patterns, and returns the result of the first pattern that successfully matches the value. A pattern will consist of a condition (e.g., checking type, checking a specific value, or checking a range) and an associated action to perform if the condition is met.

Key Requirements:

  1. Match Function: Create a function Match(value interface{}, patterns ...Pattern) that accepts an interface{} as the value to be matched and a variable number of Pattern structs.
  2. Pattern Struct: Define a Pattern struct that holds:
    • A condition function (Condition func(interface{}) bool).
    • An action function (Action func(interface{}) interface{}).
  3. Matching Logic: Iterate through the provided patterns. For each pattern, call its Condition function with the value. If the Condition returns true, execute the corresponding Action function with the value and return its result.
  4. No Match: If none of the provided patterns match, the Match function should return nil.
  5. Order Matters: Patterns are evaluated in the order they are provided. The first matching pattern's action is executed.

Expected Behavior:

The Match function should elegantly handle various data types and conditions, providing a flexible way to perform conditional logic.

Edge Cases to Consider:

  • No patterns provided to the Match function.
  • The input value is nil.
  • A pattern's condition function panics. (While not explicitly handled for robust error recovery in this challenge, be aware of this possibility).

Examples

Example 1:

Input:
value := 42
patterns := []Pattern{
    {Condition: func(v interface{}) bool { _, ok := v.(int); return ok && v.(int) == 42 }, Action: func(v interface{}) interface{} { return "Answer to the ultimate question" }},
    {Condition: func(v interface{}) bool { _, ok := v.(int); return ok && v.(int) > 0 }, Action: func(v interface{}) interface{} { return "A positive integer" }},
}
Output: "Answer to the ultimate question"
Explanation: The first pattern's condition matches the integer 42, so its action is executed and returned.

Example 2:

Input:
value := "hello"
patterns := []Pattern{
    {Condition: func(v interface{}) bool { s, ok := v.(string); return ok && s == "hello" }, Action: func(v interface{}) interface{} { return "Greeting received" }},
    {Condition: func(v interface{}) bool { _, ok := v.(string); return ok }, Action: func(v interface{}) interface{} { return "Some string" }},
}
Output: "Greeting received"
Explanation: The first pattern exactly matches "hello", so its action is executed.

Example 3:

Input:
value := []int{1, 2, 3}
patterns := []Pattern{
    {Condition: func(v interface{}) bool { _, ok := v.(int); return ok }, Action: func(v interface{}) interface{} { return "An integer" }},
    {Condition: func(v interface{}) bool { _, ok := v.([]int); return ok }, Action: func(v interface{}) interface{} { return "A slice of integers" }},
}
Output: "A slice of integers"
Explanation: The first pattern checks if the value is an `int`, which it is not. The second pattern checks if it's a `[]int`, which it is, so its action is executed.

Example 4:

Input:
value := 100.5
patterns := []Pattern{
    {Condition: func(v interface{}) bool { _, ok := v.(int); return ok }, Action: func(v interface{}) interface{} { return "An integer" }},
    {Condition: func(v interface{}) bool { _, ok := v.(string); return ok }, Action: func(v interface{}) interface{} { return "A string" }},
}
Output: nil
Explanation: None of the provided patterns' conditions match the float64 value.

Constraints

  • The number of patterns provided to the Match function can be zero or more.
  • The value can be of any type that can be assigned to interface{}.
  • The Condition and Action functions should be well-behaved and not introduce infinite loops or excessive computation that would violate practical performance expectations for typical use cases.

Notes

  • Consider how you will define the Pattern struct.
  • The interface{} type in Go is analogous to "any" in other languages, allowing you to work with values of unknown types. You'll frequently use type assertions (e.g., v.(int)) or type switches to determine the underlying concrete type.
  • Think about how to create reusable Condition functions for common checks, like checking for a specific type or a range of values.
  • The Action function should return a value of type interface{}, which can then be type-asserted by the caller if needed.
Loading editor...
go