Соответствие шаблону F # при смешивании DU и других значений - PullRequest
2 голосов
/ 23 апреля 2010

Какой самый эффективный способ выразить следующий код?

match cond.EvalBool() with
| true ->                
    match body.Eval() with
    | :? ControlFlowModifier as e ->
        match e with
        | Break(scope) -> e :> obj //Break is a DU element of ControlFlowModifier
        | _ -> next() //other members of CFM should call next()
    | _ -> next() //all other values should call next()
| false -> null

cond.EvalBool возвращает логический результат, где false должно возвращать ноль и true должен либо запустить весь блок снова (он обернут в функцию с именем next) или если найдено специальное значение break, цикл должен выйти и вернуть значение break.

Есть ли способ сжать этот блок кода до чего-то меньшего?

Ответы [ 6 ]

4 голосов
/ 23 апреля 2010

Я думаю, что код, который вы написали, в порядке. Вот альтернатива, которую я немного предпочитаю:

let isBreak = function | Break(_) -> true | _ -> false
if cond.EvalBool() then
  match body.Eval() with
  | :? ControlFlowModifier as e when isBreak e -> e :> obj
  | _ -> next()
else
  null
3 голосов
/ 23 апреля 2010

Я хочу отметить, что, как представляется, существует иерархия подтипов для типа результата Eval, и если вместо этого также был DU, вы могли бы сделать что-то вроде

match body.Eval() with
| ControlFlowModifier(Break e) -> box e
| _ -> next()

Ура для вложенных шаблонов.

2 голосов
/ 23 апреля 2010

Я не слишком люблю сопоставлять логические значения вместо использования if-else. А как насчет

let isBreak = function Break _ -> true | _ -> false
...

if cond.EvalBool() then
    match body.Eval() with
    | :? ControlFlowModifier as e when isBreak e -> box e
    | _ -> next()
else null

Или, если вы считаете, что специальная функция isBreak не нужна (я бы это понял), давайте попробуем создать более общую функцию: оператор C # as оператор

let tryCast<'T> (o : obj) =
    match o with
    | :? 'T as x -> Some x
    | _ -> None
...

if cond.EvalBool() then
    match body.Eval() |> tryCast with
    | Some (Break _ as e) -> box e //Break is a DU element of ControlFlowModifier
    | _ -> next() //all other values should call next()
else null
1 голос
/ 24 апреля 2010

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

В противном случае вам понадобятся некоторые активные шаблоны, если вы хотите написать код лаконично, используя match (ssp опубликовал один пример, который показывает активные шаблоны специально для вашей проблемы). Однако вы можете сделать это, используя следующие два многократно используемых активных шаблона:

let (|TryCast|_|) a : 'res option =    
  match (box a) with 
  | :? 'res as r -> Some(r)
  | _ -> None

let (|Value|) (l:Lazy<_>) = l.Value  

Первый похож на :?, но он позволяет вам вкладывать другие шаблоны в соответствии со значением (что невозможно при as). Вторая заставляет вычислять ленивое значение (я полагаю, что они оба могут быть объявлены в библиотеках F #, поскольку они весьма полезны). Теперь вы можете написать:

match lazy cond.EvalBool(), lazy body.Eval() with
| Value(true), Value(TryCast((Break(scope) : ControlFlowModifier)) as e) -> 
    e :> obj //Break is a DU element of ControlFlowModifier  
| Value(true), _ -> 
    next() //all other values should call next()  
| _, _ -> null 

РЕДАКТИРОВАТЬ: Как указал Роджер в комментарии, эта версия кода может быть не очень читабельным. Я думаю, что лучшим вариантом было бы использовать только TryCast и немного отформатировать исходный код (хотя это не совсем стандартное отступление, это правильно, и компилятор F # прекрасно с этим справляется):

match cond.EvalBool() with 
| false -> null 
| true ->                 
match body.Eval() with 
| TryCast(Break(scope) as e) -> e :> obj
| _ -> next()

Вероятно, это наиболее читаемый вариант, основанный на сопоставлении с образцом, но вы также можете использовать if вместо первого match, как в версии от kvb, и комбинировать его с TryCast (это действительно зависит от личных предпочтений) ):

if cond.EvalBool() then
  match body.Eval() with 
  | TryCast(Break(scope) as e) -> e :> obj
  | _ -> next()
else null

В любом случае, я считаю, что TryCast делает код более читабельным, поскольку вы избегаете одного вложения (что в противном случае требуется из-за :? .. as ..).

1 голос
/ 23 апреля 2010

В итоге я создал активный шаблон для этого.Подобная логика существует в другом месте, поэтому я мог бы сделать ее многоразовой

let rec next() : obj =
if cond.EvalBool() then 
    match body.Eval() with
    | IsBreak(res) -> res
    | _ -> step.Eval() |> ignore ; next()
else null

Выглядит прилично?

0 голосов
/ 24 апреля 2010

Если в качестве кратчайшего кода вы имеете в виду «самый эффективный способ», я тоже голосую за AP:

let (|CondEval|_|) (c,_) = if c.EvalBool() then Some true else None
let (|BodyEval|_|) (_,b) =
  match b.Eval() with
  | ControlFlowModifier as e -> Some e
  | _ -> None

match cond,body with
| CondEval _ & BodyEval e -> e :> obj
| true -> next()
| false -> null
...