haskell - Haskell可以建模吗?

标签 haskell

Haskell可以正确建模吗?我对Haskell的经验很少。我从未见过可以使用模型的通用命令式oop编程语言。

对于那些初学者来说,模型基本上是类型的集合。这是对象为类型的类别。然后使用自然变换对模型进行建模。

很简单:

class mB;
class mD : mB { mD foo(); };

然后我们的模型是{mB,mD}以及它们中的所有关系(对象和态射)。

我们想从完全平行的原始模型中获得一个新模型,
class MB;
class MD : MB { MD foo(); };

这个新模型是{MB,MD},应该与所有功能并行,可以这么说,它实质上是一个复制和粘贴的版本。

没有关系,但是我们可以使用继承充当函子:
class MB : mB
class MD : mD, MB  { MD foo(); };

因此,M可以替代m,模型M中的每个类型都可以从模型M中的类型派生而来。它是在更高的抽象层次上实现的。我们实际上正在尝试定义模型的继承,而不仅仅是标准类。

但是,问题在于M.foo不会覆盖m.foo。方差被打破!

不过,这对于模型来说不是问题,因为我们永远不会混合和匹配模型(但确实需要适当的构造)。

对于某些人可能尚不清楚,但这很容易。由于继承而存在自然转换:
mB -> MB
|      |
v      v
mD -> MD

可以用更具体的术语轻松地想到这一点。

假设有人拥有F16型,这是一架真正的F16战斗机。

现在,我们想创建一个1:16的玩具模型版本(反之亦然)。每个组件都将被建模,因此两个模型之间存在关联。真实组件和玩具组件。

如果真实的F16构成一个类别,则玩具将构成另一个类别,但是会有自然的关系。

我们要避免的是自然地在模型之间连接组件。例如,在玩具模型中使用真实的喷气发动机,反之亦然。

由于Haskell基于类别理论,因此我想它可以正确处理此问题,但我不知道其效果或效果。

本质上,我们需要采用一个模型(属于一个类别)并能够“复制它”,但仍然能够将新模型替换为原始模型(协方差/继承)。

由于所有事物都可以继承,因此新模型将替代旧模型。新模型只能扩展(因为它是协变派生的)。

由于复杂性,存在一些问题,但是其思想是我们正在采用一个模型并生成一个新的派生模型,这与我们采用一个类并生成一个新的派生类的情况没有什么不同。派生类可以替代原始类,因为它在所有结构中都包含原始类(继承是自然转换)。

这是考虑它的另一种方式,以及我想将其用于什么目的:
  • 创建业务模型,除了该模型的纯业务端外,没有其他结构与之相关。也许是支票帐户。
  • 扩展模型(整个模型)以具有GUI。 GUI在一个模型中使用了业务模型,但是该业务模型不了解GUI。

  • 通常,这是使用modelViewModel类型的事物完成的,并且在模型级别上起作用(我们有两个模型,即基础模型和派生模型)。

    通常,对于大型复杂对象而言,它有些混乱。它应该是象征。
    model M : m;
    

    即,就像类一样,我们创建一个扩展了m的新模型M,然后添加新结构,但所有基本结构均被保留。问题是,在M中,没有直接评估m中的东西。即,在某种意义上我们可以违反方差,因为我们知道基本模型完全保留在其中,并且两者之间存在1:1的关系。也就是说,上面的示例中使用foo是有效的,并且foo可以返回一个变量对象,并且仍然覆盖原始基础模型类型。这通常是不好的,但是在这种情况下,MD.foo仅用于模型M中(当基本模型使用foo时,它总是在M中的对象上使用它)。

    几乎可以认为它就像采用复杂的模型,简单地复制并粘贴所有代码,然后以一致的方式重命名所有类型,然后仅添加新的结构。然后,该模型将包含原始结构,并且可以替代该结构。

    这里的想法是最大程度地分离代码...将所有依赖关系减少到最小限度..但是让编译器完成所有工作,所以我们不必担心在两个模型之间犯错误或创建意外的依赖关系。

    enter image description here

    可以插入https://run.dlang.io/的一些D源代码
    import std.stdio, std.traits;
    
    struct ModelA
    {
        // D only allows single inheritance, must use interfaces
        interface iAnimal 
        { 
            string Type();
            string Name();
            void Attack(iAnimal who); 
            iFood LikesWhichFood();
        }
    
        interface iCat : iAnimal
        {
            void Meow();
        }    
    
        interface iDog : iAnimal
        {
            void Bark();
        }    
        interface iFood
        {
    
        }
    
        class Animal : iAnimal 
        {
            void Attack(iAnimal who) { writeln(Name, " is attacking ", who.Name, "!"); }
            string Type() { return "Unknown Animal Type"; }        
            override string Name() { return "Unknown Animal"; }        
            iFood LikesWhichFood() { writeln("Food D Type: ", fullyQualifiedName!iFood); return null; }                 
        }
    
        class Cat : Animal, iCat
        {
            string name = "Unknown Cat";
            override string Type() { return "Cat"; }        
            override string Name() { return name; }
            void Meow() { writeln("Meow!"); }
            this() { }
            this(string n) { name = n; }
        }
    
        class Dog : Animal, iDog
        {
            string name = "Unknown Dog";
            override string Type() { return "Dog"; }        
            override string Name() { return name; }
            void Bark() { writeln("Bark!"); }
            this() { }
            this(string n) { name = n; }
        }
    
    
        class Food : iFood
        {
    
        }
    }
    
    
    // Model B, It is "derived" from A, meaning Model B could, in theory, substitute for Model A as long as everything is designed correctly
    // In this case we will create a ViewModel, a gui framework for ModelA. We actually cannot do this naturally in D since it does not support multiple inheritance.
    struct ModelB
    {   
        interface iAnimal : ModelA.iAnimal
        { 
            override iFood LikesWhichFood();
        }
    
        interface iCat : iAnimal, ModelA.iAnimal
        {
    
        }    
    
        interface iDog : iAnimal, ModelA.iAnimal
        {
    
        }    
        interface iFood : ModelA.iFood
        {
            void IsItTasty();
        }
    
        class Animal : ModelA.Animal, iAnimal
        {
            //
            override iFood LikesWhichFood() { return cast(iFood)super.LikesWhichFood; }
        }
    
        class Cat : ModelA.Cat, iAnimal, iCat // We need to derive from Animal, not iAnimal, to provide proper ModelB implementation of Animal
        {
            alias Attack = Animal.Attack;   // Required by D
    
            // In D, ModelA.Cat's implement is not provided as default, we have to reimplement everything. Or is Animal providing any implementation
            override string Type() { return super.Type; }       
            override string Name() { return super.Name; }
            override void Meow() { super.Meow; }
            void Attack(iAnimal who) { super.Attack(who); }
            override void Attack(ModelA.iAnimal who) { super.Attack(who); }
            override iFood LikesWhichFood() { writeln("Food D Type: ", fullyQualifiedName!iFood); return new Cabbage; }                 
            this() { }
            this(string n) { name = n; }
    
        }
    
        class Dog : ModelA.Dog, iAnimal, iDog
        {
            alias Attack = Animal.Attack;   
            override string Type() { return super.Type; }       
            override string Name() { return super.Name; }
            override void Bark() { super.Bark; }
            void Attack(iAnimal who) { super.Attack(who); }
            override void Attack(ModelA.iAnimal who) { super.Attack(who); }
            override iFood LikesWhichFood() { writeln("Food D Type: ", fullyQualifiedName!iFood); return new Donuts; }                  
            this() { }
            this(string n) { name = n; }
        }
    
    
        class Food : iFood
        {
            void IsItTasty() { writeln("Unknown Food"); }
        }
    
        class Donuts : Food
        {
            override void IsItTasty() { writeln("YUK!"); }
        }
    
        class Cabbage : Food
        {
            override void IsItTasty() { writeln("YUM!"); }
        }
    }
    void main()
    {
    
        {
            ModelA.iAnimal animal1 = new ModelA.Cat("Mittens");
            ModelA.iAnimal animal2 = new ModelA.Dog("Sparky");
    
            writeln(animal1.Name);
            writeln(animal2.Name);
            animal1.Attack(animal2);
            animal1.LikesWhichFood;
        }
    
        writeln("\n----------\n");
    
        {
            ModelB.iAnimal animal1 = new ModelB.Cat("Super Mittens");
            ModelB.iAnimal animal2 = new ModelB.Dog("Super Sparky");
    
            writeln(animal1.Name);
            writeln(animal2.Name);
            animal1.Attack(animal2);
            auto f = animal1.LikesWhichFood;
            //f.IsItTasty;      // Error: no property `IsItTasty` for type `Models.ModelA.iFood`. It should return a ModelB.iFood, we are inside ModelB, never any risk
            (cast(ModelB.iFood)f).IsItTasty;        // We can, of course, force it, but that is the rub, we don't have to, that is why we want to have a concept of a model, it tells the compiler that there is something more going on and it can reduce all this overhead. We can't even override this because of the contravariance rule.
    
        }
    
        writeln("\n----------\n");
    
        // This is the magic, ModelB is now substituted in Model A. It's basically still oop but our entire derived model is(or should be) used.
        // We can substitute the new model in all places where the old was used. This is the easy way to do ModelViewModel, we simply extend the model and add the view, no complex bridging, adapting, maintance, dependencies, etc.
        {
            ModelA.iAnimal animal1 = new ModelB.Cat("Super Mittens");
            ModelA.iAnimal animal2 = new ModelB.Dog("Super Sparky");
    
            writeln(animal1.Name);
            writeln(animal2.Name);
            animal1.Attack(animal2);
            animal1.LikesWhichFood;
            auto f = animal2.LikesWhichFood;
            //f.IsItTasty;  // This Error is ok, we are inside ModelA, ModelA would never use IsItTasty and it would be wrong to do so(it's only wrong because it should be impossible for ModelA to know about ModelB, else we create a dependency between models and really end up with one combined model rather than two separate models). But note that we could cast
            (cast(ModelB.iFood)f).IsItTasty;        // We can, of course, force it though(only because we know for a fact we are actually dealing with a ModelB disugised as a ModelA, this is generally not the case), but this then shows a dependency. Note that it is exactly like the above model though... but there is a huge difference. In the first case it is afe, in this case it is not.. and the only difference is the model we are working in.
        }
    
    }
    

    这里要注意的是D有一些问题,我们有很多冗长的地方。为了保持模型的一致性,我们必须添加许多方法。理想情况下,ModelB中的每个类和接口(interface)都可以为空,并且可以编译。实际上,理想情况下我们可以做类似的事情

    型号ModelB:ModelA
    {

    }

    并提供我们想要扩展或修改的内容。 (就像我们对基类和派生类所做的那样。模型本质上是一个稍微抽象一些的类,其中成员是类。)

    最佳答案

    您应该了解的第一件事是Haskell没有对象,继承,覆盖或任何类似性质的东西。既然您已经根据这些概念定义了问题,简单的答案是:不,Haskell不允许您为对象继承图定义模板,然后再橡皮图章几次,因为Haskell不会。甚至没有继承权。

    但是,如果我通过抛弃所有OOP概念来对您的问题进行非常详尽的解释,那么我会得出:Haskell是否有一种方法来定义统一的接口(interface)(如函数集合;不是OOP接口(interface))可以与某些固定的数据类型集合多态使用?答案是肯定的,使用type families

    这是受动物/食物D代码启发而使用类型家族的示例:

    {-# LANGUAGE TypeFamilies #-}
    
    data Animal = Cat String | Dog String deriving (Eq, Show)
    data Food = Donut | Cabbage deriving (Eq, Show)
    
    data Widget a = Widget a Int -- let's say that the Int is a handle to some graphics object...
    
    class AnimalModel animal where
      type FoodFor animal
      animalName :: animal -> String
      likesWhichFood :: animal -> FoodFor animal
      eat :: animal -> FoodFor animal -> IO ()
    
    -- So here we'll define the "business model" functions:
    instance AnimalModel Animal where
      type FoodFor Animal = Food
    
      animalName (Cat name) = name
      animalName (Dog name) = name
    
      likesWhichFood (Cat _) = Cabbage
      likesWhichFood (Dog _) = Donut
    
      eat animal food = print message
        where
          message = if likesWhichFood animal == food then show animal ++ " eats the " ++ show food else show animal ++ " refuses the " ++ show food
    
    -- And here we'll define *just* the parts of the "view model" functions that don't depend on the specifics of the underlying model:
    instance (AnimalModel animal) => AnimalModel (Widget animal) where
      type FoodFor (Widget animal) = Widget (FoodFor animal)
      animalName (Widget a _) = animalName a
      likesWhichFood (Widget a _) = Widget (likesWhichFood a) (-1) -- because the widget hasn't been initialized yet??? IDK, this is a silly example
      eat (Widget a _) (Widget f _) = eat a f
    
    
    main = eat (Widget (Cat "Sparky") 2) (Widget Donut 3)
    

    关于haskell - Haskell可以建模吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56837648/

    相关文章:

    haskell - 类型推导在 Haskell 中是如何工作的?

    haskell - 针对只有一个构造函数的类型进行模式匹配

    function - 对 Haskell 中的函数组合感到困惑

    haskell - 使用 cabal 安装库时无法解决依赖关系

    haskell - 如何提升函数在镜片上的作用?

    haskell - 将函数的输出作为 Haskell 中另一个函数的输入传递

    haskell - 如何避免 "‘main’ 未在模块 ‘Main’ 中定义“使用合成时

    代表有效类型的 Haskell 数据类型

    haskell - 为什么 Haskell 范围在使用 [LT .. GT] 时需要空格?

    Haskell 折叠和堆栈溢出?