2010-07-08 4 views
3

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

[<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 #.

EDIT 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 работает отлично, но если я хочу огня Ожог, то я должен обеспечить болезни, тоже, что не имеет никакого смысла.

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

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

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

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

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

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

EDIT 2010.07.09 0845 PST

Джон Harrop: "Почему вы используете аугментаций и члены?"

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

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

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

Jon Harrop:. "Какова цель вашего члена Fn?"

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

.

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

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

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

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

Jon Harrop: «Какова конкретная конкретная проблема, которую вы пытаетесь решить ?!»

Обмен кодами для родственных типов при сохранении читаемости.

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

EDIT 2010.07.09 2230 PST

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

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

+0

Я DON» у меня есть много чего сказать, чем у Джона, Брайана и Томаса, но для чего стоит, если вы обнаружите, что сражаетесь и взламываете язык, чтобы заставить что-то работать, это почти всегда означает, что вы делаете что-то неправильно. В этом случае наследование не является обычно используемой идиомой в C#, вы определенно хотите использовать что-то более идиоматическое, вроде подобных союзов, которое вы можете сопоставить вместо классов.Не обескураживайте, почти каждый пишет C# с синтаксисом F # при первом использовании (http://stackoverflow.com/questions/1883246/). Как только вы освоите идиомы, использование языка легко *. – Juliet

+0

Спасибо за совет, Джульетта. Я буду помнить ваши предупреждения. На данный момент я пошел с предложением Джона (что иронически является самым первым решением, которое я произвел в этом проекте). – MiloDC

ответ

4

Вы, вероятно, нужны явно базовый типа ваших возвращенных значений:

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

Это очень необычный стиль программирования, хотя. Что именно ты пытаешься сделать?

EDIT

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

Что об этом:

type fire = ... 

let burn fire = ... 

type spreader = Disease | Fire of fire 

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

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

+0

«Что именно вы пытаетесь сделать?» Пожалуйста, см. Выше, с исправленной почтой. – MiloDC

+0

Я обновил свой ответ соответственно. –

+0

Отвечено на ваше обновление. – MiloDC

2

Вы можете сделать любой из 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#. Поэтому я бы это сделал. BTW, это

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

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

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

4

В 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 

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


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

let fire = new Fire() 
let fire2 = foo fire  // 'fire2' has type 'Fire' 
fire2.FireSpecificThing() 
+0

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

+0

@MiloDC: Спасибо за разъяснение. Итак, у вас есть два объекта для представления огня и болезни, и оба они могут иметь силу. Вы хотите легко добавить другие типы этих вещей? Как вы собираетесь их использовать? Если у вас есть член 'Burn' в вашем пожарном объекте, откуда это будет вызвано? Разве вы не всегда будете работать с этими вещами, используя базовый класс? Это, например, что-то вроде игры? ... Это такие вопросы высокого уровня, которые вы можете использовать, чтобы решить, использовать ли OO (иерархию классов) или стиль FP (дискриминационные союзы). –

+0

Tomas, просто хотел сказать, что я недавно купил вашу книгу _Real World Functional Programming_, и пока это хорошо написано. Я собираюсь решить свою проблему по-другому, основываясь главным образом на том, что я прочитал в вашей книге. Приветствия. – MiloDC

Смежные вопросы