Nano Hash - криптовалюты, майнинг, программирование

Как указать Scala не повышать абстрактный тип?

С учетом бульдога:

trait Animal {
  type Food

  def defaultFood(): Food
}
class Bulldog extends Animal {
  type Food = Steak

  ... implementations ...
}

Функция Bulldog.defaultFood() прекрасно работает для компилятора (хотя моя подсветка синтаксиса выдала ошибку, в этом нет ничего страшного):

val bulldog = new Bulldog()
val df: bulldog.Food = bulldog.defaultFood()

Однако, если бульдог заключен внутри другого класса, все вырвется наружу:

class Kennel(val animal: Animal) {
}
def getSupply(kennel: Kennel): kennel.animal.Food = {
  ... implementation ...
}

val kennel = new Kennel(bulldog)
val df2: bulldog.Food = getSupply(kennel)

Компилятор Scala выдаст ошибку компиляции:

type mismatch;
 found   : Option[kennel.animal.V] where val kennel: Kennel
 required: bulldog.Food

Эта функция в настоящее время отсутствует в Scala? Есть ли способ заставить его работать?


  • Вопрос: вы не собираетесь писать type Food = Steak в реализации бульдога, а не object Food? 28.02.2017
  • извините мой плохой, исправлено. 28.02.2017
  • Вы не определили, что getSupply() делает, только то, что он имеет возвращаемый тип. 28.02.2017
  • Да, это не абстрактно и должно быть определено, закреплено. Извините за медленный ответ, так как у меня не было возможности попробовать ни один из них. 02.03.2017

Ответы:


1

У вашего кода есть проблема с компиляцией - kennel.animal неразрешима, поскольку класс Kennel не предоставляет animal как общедоступное поле; это легко решается добавлением val перед animal.

Что, кажется, беспокоит вас, так это то, что Kennel ничего не знает о лежащем в основе животном, кроме того, что это Animal. Прохождение мимо бульдога воспринимается как прохождение мимо какого-то животного.

Попробуйте добавить дополнительную информацию о типе в Kennel:

class Kennel[A <: Animal](val animal: A) {  }

def getSupply[A <: Animal](kennel: Kennel[A]): kennel.animal.Food

val kennel = new Kennel(bulldog)
val df2: bulldog.Food = getSupply(kennel)

Теперь Kennel знает точный тип animal (например, Bulldog), а getSupply может вернуть точный тип пищи для этого животного.

Вот полный рабочий код, который вы можете попробовать:

trait Steak {
  override def toString() = "steak"
}

trait Animal {
  type Food
  def defaultFood(): Food
}

class Bulldog extends Animal {
  type Food = Steak
  def defaultFood() = new Steak {}
}

class Kennel[A <: Animal](val animal: A)

object Test extends App {

  def getSupply[A <: Animal](kennel: Kennel[A]): kennel.animal.Food = kennel.animal.defaultFood()

  val bulldog = new Bulldog()
  val kennel = new Kennel(bulldog)
  val df2: bulldog.Food = getSupply(kennel)

  println(df2) // prints "steak"
}
27.02.2017
  • У меня небольшой побочный вопрос. Зачем идти на такие ухищрения? Почему для трейта Animal недостаточно просто объявить метод defaultFood(), который возвращает экземпляр другого трейта, Food? И почему важно, чтобы Kennel и getSupply() были общими, когда передаваемые параметры обязательно будут реализацией трейта (если, конечно, Animal задан как тип параметра)? 28.02.2017
  • @AleksandarStojadinovic Это зависит от того, что вам нужно. Я думаю, что @tribbloid хочет иметь df2 типа Steak, который является не просто едой, а любимой едой бульдога. Точно так же, если вы говорите, что параметр имеет тип Animal, вы знаете, что переданный параметр будет его реализацией, но вы не знаете, какой именно. Вы теряете различие между разными животными. Используя A <: Animal, вы можете обратиться к точному A, который использовался. Таким образом, питомник не только содержит какой-то тип животного, но и знает, какой именно тип животного (в нашем примере Bulldog). 28.02.2017
  • Хорошо, имеет смысл, спасибо. Я предполагаю, что это также связано со школой мысли, из которой вы исходите (или количеством влияния Java/С#, которое вы имеете в себе :-)) 28.02.2017
  • Да, я думаю :) Конечно, если бы нам не понадобился A позже (что мы и сделали; мы повторно использовали его для определения типа животного), у нас не было бы никакой выгоды от записи типа параметра как A <: Animal. Благодаря полиморфизму подтипов из ООП, мы всегда можем передать этому методу любой конкретный Animal (бульдог, носорог, курица). Но если мы хотим захватить этот тип и использовать его впоследствии, мы делаем это следующим образом. В Java тоже есть похожие приемы, только менее гибкие (особенно когда вы включаете в историю вариативность). 28.02.2017
  • Ух ты шустрый, скопировал из кейс класса и забыл добавить вал, поправил 28.02.2017
  • @tribbloid Отлично! Так тебе удалось заставить все это работать? 01.03.2017
  • Это сработало! Большое спасибо. Хотя я все еще думаю, что общий тип слишком многословен, и компилятор scala должен быть достаточно умным, чтобы справиться с этим. 22.03.2017

  • 2

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

    @annotation.implicitNotFound(
      "Couldn't confirm that ${Animal}'s favourite food is ${Food}")
    trait DefaultFood[Animal, Food] { def apply(animal: Animal): Food }
    object DefaultFood {
      /** Helper to easily implement typeclass instances. */
      class Impl[Animal, Food](getFood: Animal => Food)
        extends DefaultFood[Animal, Food] {
        override def apply(animal: Animal): Food = getFood(animal)
      }
    }
    
    class Bulldog
    object Bulldog {
      type Steak = String // Or whatever.
    
      implicit val defaultFood: DefaultFood[Bulldog, Steak] =
        new DefaultFood.Impl(_ => "Steak")
    }
    
    class Kennel[Animal, Food](
      animal: Animal)(implicit defaultFood: DefaultFood[Animal, Food]) {
      def getSupply: Food = defaultFood(animal)
    }
    
    object Test {
      val kennel = new Kennel(new Bulldog) // Kennel[Bulldog, Bulldog.Steak]
    }
    

    Это распространяет универсальные типы вперед в класс Kennel, но из-за вывода типов Scala вам фактически не нужно указывать типы. Кроме того, аннотация @implicitNotFound дает вам хорошее сообщение об ошибке компиляции, если нет экземпляра класса типов для определенного животного и его пищи.

    Обратите внимание, что типы Animal и Food на самом деле не обязательно должны быть животными и продуктами питания; это просто семантика, которую мы здесь используем. Если вы посмотрите на это с самой общей точки зрения, этот класс типов на самом деле является просто функцией типа A => B для некоторых A и B.

    28.02.2017
    Новые материалы

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

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

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

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

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