Сопоставление с шаблоном F #: как сопоставить набор возможных типов с одинаковыми параметрами? - PullRequest
3 голосов
/ 30 августа 2011

Я новичок в F # и не совсем знаком с идеей сопоставления с образцом.Я пытался найти лучшее решение своей проблемы, но боюсь, что даже не могу выразить проблему должным образом - надеюсь, заголовок вопроса хотя бы несколько точен.

Что я хочу сделать, это извлечь 2 "параметры "от listMethod.listMethod относится к одному из нескольких типов, которые имеют строку и Expression «параметр» (я подозреваю, что параметр является неправильным термином):

    let (varDecl, listExpr) =
        match listMethod with 
        | Select (var, expr)  -> (var, expr)
        | Where (var, expr)   -> (var, expr)
        | Sum (var, expr)     -> (var, expr)
        | Concat (var, expr)  -> (var, expr)

Затем я продолжаю работать с varDecl ив конце получим аналогичное выражение соответствия с фактическим кодом listMethod, который использует несколько временных переменных, которые я создал на основе varDecl.

Теперь у меня вопрос: как я могу сделать приведенный выше код более компактным?

Я хочу сопоставить все те типы, которые имеют 2 параметра (типа string и Expression), не перечисляя их всех самостоятельно, что довольно уродливо и сложно поддерживать.

Тип ListMethod объявлен следующим образом (все это проект FsLex / FsYacc):

type ListMethod =
    | Select of string * Expr
    | Where of string * Expr
    | Sum of string * Expr
    | Concat of string * Expr
    | ...
    | somethingElse of Expr

(на данный момент у меня есть только типы формы string * Expr, но это изменится).

Я считаю, что это довольно глупый вопрос для любого, кто имеет некоторый опыт, но, как я уже сказал, я новичок в F # и не смог найти решение самостоятельно.

Спасибо ваванс!

Редактировать: Я бы очень хотел избежать перечисления всех возможных типов listMethod дважды.Если я не могу использовать подстановочные знаки или заполнители в выражениях match, возможно, я смогу изменить тип listMethod, чтобы сделать вещи чище.

Один вариант, который приходит на ум, - это создание только 1 типаlistMethod и создать третий параметр для конкретного типа (Выбрать, Где, Сумма).Или есть лучший подход?

Ответы [ 4 ]

7 голосов
/ 30 августа 2011

Это, вероятно, стандартный способ:

let (varDecl, listExpr) =
    match listMethod with 
    | Select (var, expr)
    | Where (var, expr)
    | Sum (var, expr)
    | Concat (var, expr) -> (var, expr)

Знак | означает or, поэтому, если один из этих совпадений, результат будет возвращен.Просто убедитесь, что у каждого дела одинаковые имена (и типы).

Как прокомментировал Чак, это еще лучшее решение:

let (Select (varDecl, expr)
    | Where (varDecl, expr)
    | Sum (varDecl, expr)
    | Concat (varDecl, expr)) = listMethod
5 голосов
/ 30 августа 2011

Я считаю, что это довольно глупый вопрос для любого, кто имеет некоторый опыт, но, как я уже сказал, я новичок в F # и не смог найти решение сам.

Напротив, это очень хороший вопрос и на самом деле относительно нетронутая почва, потому что F # отличается от других языков в этом отношении (например, вы могли бы решить эту проблему, используя полиморфные варианты в OCaml).

Как писал Анкур,Лучшее решение - всегда менять структуру данных, чтобы было проще делать то, что вам нужно, если это возможно.Решение KVB об использовании активных шаблонов является не только ценным, но и новым, потому что эта языковая особенность редко встречается в других языках.Предложение Рамона комбинировать ваши случаи совпадений с использованием or-шаблонов также хорошо, но вы не хотите писать неполные совпадения шаблонов.

Возможно, наиболее распространенный пример этой проблемы, возникающей на практике, - операторы:

type expr =
  | Add of expr * expr
  | Sub of expr * expr
  | Mul of expr * expr
  | Div of expr * expr
  | Pow of expr * expr
  | ...

, где вы можете реструктурировать свой тип следующим образом:

type binOp = Add | Sub | Mul | Div | Pow

type expr =
  | BinOp of binOp * expr * expr
  | ...

Тогда такие задачи, как извлечение подвыражений:

let subExprs = function
  | Add(f, g)
  | Sub(f, g)
  | Mul(f, g)
  | Div(f, g)
  | Pow(f, g) -> [f; g]
  | ...

, могут выполняться более просто:

let subExprs = function
  | BinOp(_, f, g) -> [f; g]
  | ...

Наконец, не забывайте, что вы можете дополнять типы F # (например, типы объединения) конструкциями ООП, такими как реализация общих интерфейсов.Это также может быть использовано для выражения общности, например, если у вас есть два перекрывающихся требования для двух типов, вы можете заставить их оба реализовать один и тот же интерфейс для демонстрации этой общности.

4 голосов
/ 30 августа 2011

Если вы можете вносить изменения в свою структуру данных, ниже приведено описание, которое облегчит сопоставление с образцом.

type ListOperations = 
    Select | Where | Sum | Concat


type ListMethod =
    | ListOp of ListOperations * string * Expr
    | SomethingElse of int

let test t = 
    match t with
    | ListOp (a,b,c) -> (b,c)
    | _ -> ....

Структура данных должна быть разработана с учетом требуемой операциивыполнить на нем.

3 голосов
/ 30 августа 2011

Если есть моменты, когда вы захотите относиться ко всем своим случаям одинаково, и в другие моменты, когда вы захотите относиться к ним по-разному в зависимости от того, обрабатываете ли вы Select, Where, Sum и т. Д.., тогда одним из решений было бы использование активного шаблона:

let (|OperatorExpression|_|) = function
| Select(var, expr) -> Some(Select, var, expr)
| Where (var, expr) -> Some(Where, var, expr)
| Sum (var, expr) -> Some(Sum, var, expr)
| Concat (var, expr) -> Some(Concat, var, expr)
| _ -> None

Теперь вы все равно можете нормально сравнивать, если вам нужно обрабатывать случаи индивидуально, но вы также можете сопоставлять, используя активный шаблон:

let varDecl, listExp = 
    match listMethod with
    | OperatorExpression(_, v, e) -> v, e
    | _ -> // whatever you do for other cases...
...