如何在函数式编程中建模继承关系

15

面向对象编程范例使用继承来建模遵循泛化-特殊化关系的实体之间的关系。在这里,基类用于封装一组实体的通用(一般)属性和行为,派生类通过添加附加属性和/或添加/修改现有行为来扩展基类。

作为一个新手函数式编程,我需要指导如何在像F#这样的函数式语言中建模这样的关系。

例如,如何最好地建模以下简单情况:

abstract class Tutorial { 
  private String topic;
  abstract public void learn();
}

class VideoTutorial extends Tutorial {
  private float duration;
  public void learn () {
    System.out.println ("Watch Video");
  }
}

class PDFTutorial extends Tutorial {
  private int pageCount;
  public void learn () {
    System.out.println ("Read PDF");
  }
}

然后稍后使用一系列 教程 并调用 learn 来观察多态行为。

1个回答

21
在功能设计中,您需要以略微不同的方式思考问题,因此思路不会完全匹配。通常,功能设计更侧重于表示您正在处理的实体的数据类型。在您的情况下,您可以使用判别联合定义TutorialKind,它可以是视频或PDF,然后Tutorial将是由种类和主题组成的记录:
type TutorialKind = 
  | VideoTutorial of duration:float
  | PDFTutorial of pageCount:int

type Tutorial = 
  { Kind : TutorialKind
    Topic : string }

注意,这只保留有关教程数据的信息。任何功能都可以在模式匹配的函数中实现,以匹配教程的种类:

let learn tutorial = 
  match tutorial.Kind with
  | VideoTutorial _ -> printfn "Watch video"
  | PDFTutorial _ -> printfn "Read PDF"

请确认翻译是否正确:

请注意,这种可扩展性与OO版本不同。在OO中,您可以轻松地添加新的子类;而在这里,您可以轻松地添加新的函数。实际上,功能人员通常对这种变化感到满意,但F#是一种混合语言,如果需要“面向对象的可扩展性”,则可以轻松使用接口。


谢谢,现在我明白了思维过程的变化。但是采用这种方法,是否意味着如果我以后想要添加AudioTutorial,我需要修改两种类型以及learn函数。请指导我,面向对象的方法是否更适合这种情况。 - Yelena
2
@S.Singh - 是的,这意味着如果您想添加 AudioTutorial,则需要修改 TutorialKind 类型和 learn 函数。但是,如果您想添加执行教程其他操作的函数,则只需要编写一个函数(而不是修改所有类,在面向对象编程中必须这样做)。因此,透视角度的变化可以双向工作... 在 F# 中,函数式风格是一个很好的默认选择,但如果您正在构建具有插件的东西并且确实需要 OO 类型的可扩展性,则可以使用 OO 和接口。 - Tomas Petricek
1
是的,要添加新类型的教程,您确实需要修改该类型及所有与其配合的函数。另一方面,在C#中,要添加新的操作,您确实需要修改每个现有类型。通常认为,编程语言很难同时具备这两种方式。这个问题非常普遍,它有自己的名字——表达式问题。C#只解决了问题的一半,而F#则解决了两个问题,但不能同时解决。 - Fyodor Soikin
@FyodorSoikin - 你能解释一下“F#可以同时解决这两个问题,但不能同时解决”吗?这是指语言的混合特性,我们可以用函数式方式或面向对象的方式来建模数据吗? - Yelena
1
是的,在F#中,您可以使用类和接口来建模您的领域,也可以使用判别联合来建模。前者为添加新类型提供了简单的方法,后者则为添加新操作提供了简单的方法。但是您不能同时使用两种建模方式。 - Fyodor Soikin
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接