Функция с гибким аргументом типа и возвращаемым значением? - PullRequest
3 голосов
/ 09 июля 2010

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

[<AbstractClass>]
type Spreader () =
    abstract Fn : unit -> unit

type Fire () =
    inherit Spreader ()
    override self.Fn () = ()

type Disease () =
    inherit Spreader ()
    override self.Fn () = ()

let spread (spr:#Spreader) : #Spreader =
    match spr with
    | :? Fire -> Fire ()
    | :? Disease -> Disease ()
    | _ -> failwith "I don't get it"

Очевидно, это не работает, но вы получаете то, что я пытаюсь сделать.

Сначала я реализовал абстрактную функцию в типе Spreader и переопределил (переопределил?) Ее в подпрограмме.типы, но это требовало апскейтинга, которого я пытаюсь избежать.

Это выполнимо?Я изучаю дженерики, но я не совсем понял их реализацию F #.

РЕДАКТИРОВАТЬ 2010.07.08 1730 PST

Что касается предположения, что яиспользовать дискриминационные союзы, я пробовал это раньше.Проблема, с которой я столкнулся, заключалась в том, что любая функция, которую я определил как член базового типа, должна была обрабатывать каждую ветвь объединения.Например:

type strength = float32

type Spreader =
    | Fire of strength
    | Disease of strength

    member self.Spread () =
        match self with
        | Fire str -> Fire str
        | Disease str -> Disease str

    member self.Burn () =
        match self with
        | Fire str -> Fire str
        | _ -> failwith "Only fire burns"

Здесь хорошо работает функция Spread, но если я хочу, чтобы Fire горел, то я должен также предусмотреть Disease, что не имеет смысла.

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

member self.Burn () =
    match self with
    | Fire str -> Some(Fire str)
    | _ -> None

Я бы предпочел просто оставить функцию Burn строго для Spreader, и даже не определять ее для Disease Spreader.

Кроме того, это касается и свойств;Есть некоторые члены, которые, как я хочу, чтобы у Fire был смысл, для Disease, и наоборот.Кроме того, я хотел бы иметь возможность получить доступ к значению силы Spreader с помощью точечной нотации, поскольку у меня будут другие члены, к которым я получаю доступ в любом случае, и, как ни странно, излишне иметь необходимость определять member.strength после того, как он ужезаключен в объекте.(например, | Disease str -> ...)

Другой вариант, конечно, состоит в том, чтобы просто отделить функции Spread и Burn от типа Spreader, но тогда я должен либо (1) предоставить некрасивую апскейтинг, либоуниверсальный код для функций (как уже описывали другие), или (2) имеют полностью отдельные функции для Fire и Disease, которые могут быть отстойными в случае Spread, поскольку я должен был бы назвать их SpreadFire и SpreadDisease (так как функция перегружает внешние типыкак ни странно, не допускается).

Как новичок в F #, я приветствую все критические замечания и предложения.:)

РЕДАКТИРОВАТЬ 2010.07.09 0845 PST

Джон Харроп: «Почему вы используете дополнения и элементы?»

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

Джон Харроп: «Почему вы хотите, чтобы запись горения применялась к Speader?типы, когда это применимо только к одному? "

Как я уже писал, я не хочу, чтобы Burn применился к Spreader.Я хочу, чтобы это относилось исключительно к Огню.Однако я разрывался между реализацией функции как члена Spreader и отделением ее от типа Spreader.(Запах непоследовательности.)

Джон Харроп: «Какова цель вашего члена Fn?»

Извините, это был просто посторонний код, пожалуйста, игнорируйте его.

Джон Харроп: «Почему вы использовали абстрактный интерфейс вместо интерфейса?»

Преимущественно читаемость и эффективность кода.Ненавижу переходить на интерфейс только для того, чтобы использовать один из его методов.

Джон Харроп: «Почему вы определили силу как псевдоним для float32?»

Читаемость кода.

Джон Харроп: «Какую конкретную проблему вы пытаетесь решить?!»

Совместное использование кода для связанных типов при сохранении читаемости.

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

РЕДАКТИРОВАТЬ 2010.07.09 2230 PST

На самом делеЯ начинаю не любить функциональное программирование (действительно согласен с автором в http://briancarper.net/blog/315/functional-programming-hurts-me - если я увижу еще один пример кодирования Фибоначчи или факториала в учебнике или учебнике по F #, я собираюсь нанести удар следующимботаник, я нахожу).

Я надеялся, что кто-то здесь ответит на мой последний ответ (объясняя, почему я делаю определенные вещи) с видом презрения, которое Джон продемонстрировал (ниже).Я хочу изучать FP у надменной элиты, кодеров, которые думают, что их дерьмо не воняет.

Ответы [ 3 ]

4 голосов
/ 09 июля 2010

Вам, вероятно, нужно явно отклонить возвращаемые значения:

let spread (spr: Spreader) =
  match spr with
  | :? Fire -> (Fire() :> Spreader)
  | :? Disease -> (Disease() :> Spreader)

Хотя это очень необычный стиль программирования.Что именно вы пытаетесь сделать?

РЕДАКТИРОВАТЬ

Я до сих пор не понимаю, чего вы пытаетесь достичь.Почему вы используете аугментации и участников?Почему вы хотите, чтобы burn применялся к Speader типам, когда он применим только к одному?Какова цель вашего Fn участника?Почему вы использовали абстрактный класс вместо интерфейса?Почему вы определили strength как псевдоним для float32?Какую именно конкретную проблему вы пытаетесь решить?!

Как насчет этого:

type fire = ...

let burn fire = ...

type spreader = Disease | Fire of fire

let spread = function
  | Disease -> ...
  | Fire fire -> ...

В основном, если у вас есть функции, которые применяются только к подмножеству распределителей, вам необходимоотдельные проблемы как-то.Либо путем обращения к новым типам (как указано выше), либо с помощью ООП и создания подмножества классов для реализации интерфейса.

4 голосов
/ 09 июля 2010

В F # люди не часто используют наследование реализации (базовые классы), поэтому было бы полезно узнать, что вы пытаетесь сделать (например, более реалистичный пример был бы полезен).

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

let foo< 'T when 'T :> Spreader >(arg:'T) : 'T =
  // some implementation here

Параметр типа 'T будет некоторым унаследованным классом Spreader, и, как показывает сигнатура типа, функция foo возвращает то же самоевведите как аргумент.

Реализация немного уродливая, потому что вам нужно добавить много динамических приведений типов:

let foo< 'T when 'T :> Spreader >(arg:'T) : 'T =
  match box arg with 
  | :? Fire -> (box (Fire())) :?> 'T
  | :? Disease -> (box (Disease())) :?> 'T

В любом случае, я согласен с Брайаном, что этопохоже на сценарий, где дискриминационные союзы будут лучшим выбором.Было бы здорово, если бы вы описали вашу проблему более подробно.


В отличие от решения от Jon и spread2 от Brian, приведенная выше версия возвращает тот же подтип, что и полученный в качестве аргумента.Это означает, что должно работать следующее:

let fire = new Fire()
let fire2 = foo fire       // 'fire2' has type 'Fire'
fire2.FireSpecificThing()
2 голосов
/ 09 июля 2010

Вы можете сделать либо spread1, либо spread2 ниже, но мне интересно, хотите ли вы использовать дискриминационные объединения, а не иерархию классов.

[<AbstractClass>] 
type Spreader () = 
    abstract Fn : unit -> unit 

type Fire () = 
    inherit Spreader () 
    override self.Fn () = () 

type Disease () = 
    inherit Spreader () 
    override self.Fn () = () 

let fire = new Fire()
let disease = new Disease()

let spread1<'t when 't :> Spreader>(spr:'t) : 't = 
    spr

printfn "%A" (spread1 fire)
printfn "%A" (spread1 disease)

let spread2(spr:Spreader) : Spreader = 
    match spr with
    | :? Fire -> upcast disease
    | :? Disease -> upcast fire
    | _ -> failwith "hmmm"

printfn "%A" (spread2 fire)
printfn "%A" (spread2 disease)

EDIT

После вашего обновления кажется, что вы действительно хотите иерархию классов, и вы просто будете делать вещи точно так же, как вы это делаете, например, в. C #. Так что я бы сделал это. Кстати, это

let spread (spr:Spreader) : Spreader = 
    match spr with 
    | :? Fire -> upcast Fire () 
    | :? Disease -> upcast Disease () 
    | _ -> failwith "I don't get it" 

отлично работает и близок к вашему оригиналу. Хотя я бы сделал Spread абстрактный метод для базового класса.

Кроме того, вам почти никогда не нужны «гибкие типы» (#type) в F #, часто это запах кода, если вы считаете, что вам нужно их использовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...