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:
MatchFunction: Create a functionMatch(value interface{}, patterns ...Pattern)that accepts aninterface{}as the value to be matched and a variable number ofPatternstructs.PatternStruct: Define aPatternstruct that holds:- A condition function (
Condition func(interface{}) bool). - An action function (
Action func(interface{}) interface{}).
- A condition function (
- Matching Logic: Iterate through the provided
patterns. For each pattern, call itsConditionfunction with thevalue. If theConditionreturnstrue, execute the correspondingActionfunction with thevalueand return its result. - No Match: If none of the provided
patternsmatch, theMatchfunction should returnnil. - 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
Matchfunction. - The input
valueisnil. - 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
patternsprovided to theMatchfunction can be zero or more. - The
valuecan be of any type that can be assigned tointerface{}. - The
ConditionandActionfunctions 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
Patternstruct. - 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
Conditionfunctions for common checks, like checking for a specific type or a range of values. - The
Actionfunction should return a value of typeinterface{}, which can then be type-asserted by the caller if needed.