1

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

Метод, который в настоящее время обрабатывает параметры выглядит следующим образом:

def writeOptionalXml[T](content: Option[T], mapFn: T => Xml): Xml = 
    content match { 
    case Some(c) => mapFn(c) 
    case None => NodeSeq.Empty 
} 

И он прекрасно работает. Но у меня также есть другие входы, которые не являются параметрами, но все равно могут быть пустыми, например пустая строка, пустой узел xml или некоторый класс case.

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

Где я застрял - это функция преобразования (плохо названная в качестве mapFn). В случае отсутствия опции я хочу, чтобы подпись метода: (содержимое: опция [T], mapFn: T => Xml): Xml , тогда как в других случаях: (вход: A, mapFn: A => Xml) : Xml

Я боролся с изменением подписей типа, используя [_], чтобы попытаться получить то, что я хочу, но безрезультатно.

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

import scala.annotation.implicitNotFound 

object writableTypes extends App { 

    type Xml = String 
    @implicitNotFound("No member of type class in scope for ${T}") 
    trait WritableLike[A] { 
    def toXml[B](input: A, mapFn: ((_$1) forSome {type _$1}) => Xml): Xml 
    } 

    object WritableLike { 

    implicit object WritableLikeString extends WritableLike[String] { 
     override def toXml[B](input: String, mapFn: ((_$1) forSome {type _$1}) => Xml): Xml = 
     mapFn(input) 
    } 

    implicit def OptionFormat[T: WritableLike]: Object = new WritableLike[Option[T]] { 
     override def toXml[B](input: Option[T], mapFn: ((_$1) forSome {type _$1}) => Xml): Xml = 
     mapFn(input.get) 
    } 

    def writeXml[X](input: X, mapFn: ((_$1) forSome {type _$1}) => Xml)(implicit ev: WritableLike[X]): Xml = 
     ev.toXml[X](input, mapFn) 
    } 

    println(WritableLike.writeXml(Option(SomeCaseClass(5)), transformToXml)) 

    case class SomeCaseClass(content: Int) { def someMethod = ""} 

    def transformToXml[T](input: SomeCaseClass): String = input.someMethod 

} 

К сожалению, это не компилируется, потому что в этом вызов метода WritableLike.writeXml (вариант (SomeCaseClass (5)), transformToXml)

Функция transformToXml не удовлетворяет требуемой сигнатуре метода.

Я пробовал так много перестановок этого и не могу найти решение, элегантное в противном случае.

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

Я не уверен, что объяснил это очень хорошо, это моя первая попытка написать класс типа, я думал, что это будет просто, но похоже, что конкретная проблема, которую я пытаюсь решить, имеет некоторые дополнительные сложности.

Буду признателен, если вы услышите от кого-то более глубокое понимание общего программирования с системой типа Scala.

Благодаря

ответ

0

Я думаю, что вы получили немного повесил трубку в своем классе типа с mapFn, который вы использовали в обычной функции. Вы можете структурировать класс типа, чтобы полагаться только на входные данные. Остальная часть работы выполняется с использованием экземпляров класса type. Вот пример определения класса WritableLike типа, опирающейся только входные данные:

trait WritableLike[A] { 
    def toXml(input: A): String 
} 

Для этого примера я использую String вместо типа XML.Используя это определение, мы можем определить некоторые простые экземпляры класса типа:

implicit val stringWritableLike: WritableLike[String] = new WritableLike[String] { 
    def toXml(input: String): String = s"<text>$input</text>" 
} 

implicit val intWritableLike: WritableLike[Int] = new WritableLike[Int] { 
    def toXml(input: Int): String = s"<integer>$input</integer>" 
} 

В приведенном выше коде, я определил экземпляры классов типа для String и Int типов. Оба экземпляра напрямую определяют, как вход для метода toXml преобразуется в строку XML, не полагаясь на другую функцию для обеспечения преобразования. Это означает, что когда мы применяем тип типа для любого из типов, мы всегда будем использовать одни и те же преобразования независимо от значения.

Если мы хотим перегрузить преобразование для определенных значений, мы хотим обернуть эти значения каким-либо другим типом и предоставить экземпляр класса типа для этого типа.

case class Name(name: String) extends AnyVal 

implicit val nameWritableLike: WritableLike[Name] = new WritableLike[Name] { 
    def toXml(input: Name): String = s"<name>${input.name}</name>" 
} 

Здесь мы определяем класс case для представления имен и определенного типа класса для преобразования имен в XML.

Я определил эти экземпляры как значения, но я мог бы также использовать def или object. Важная часть состоит в том, что экземпляры являются неявными, независимо от того, какое из этих ключевых слов используется. Имплицитивность позволяет нам позже приобретать эти экземпляры, не обращаясь к ним явно.

Есть случаи, когда вам нужно использовать def, а не val для определения экземпляра класса типа: это необходимо, если ваш экземпляр класса типа зависит от экземпляра класса типа. Мы можем использовать Option в качестве примера:

implicit def optionWritableLike[A](implicit instanceForA: WritableLike[A]): WritableLike[Option[A]] = 
    new WritableLike[Option[A]] { 
    def toXml(input: Option[A]): String = input match { 
     case Some(a) => instanceForA.toXml(a) 
     case None => "<empty />" 
    } 
    } 

В приведенной выше коде мы определили экземпляр класса типа для типа Option, который работает на все значения где параметр Option типа также имеет экземпляр класса типа. Здесь мы представляем внутренний тип как параметр типа функции A. Поскольку мы не знаем, что такое A, нам нужно каким-то образом получить экземпляр класса типа для этого типа. Мы можем добиться этого, добавив неявный параметр для точного экземпляра класса типа, который нам нужен. Наконец, используя неявный экземпляр, мы можем преобразовать базовое значение в XML.

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

Существует альтернативный синтаксис мы можем использовать, чтобы объявить зависимость класса типа:

implicit def optionWritableLike[A: WritableLike]: WritableLike[Option[A]] = 
    new WritableLike[Option[A]] { 
    def toXml(input: Option[A]): String = input match { 
     case Some(a) => implicitly[WritableLike[A]].toXml(a) 
     case None => "<empty />" 
    } 
    } 

Здесь мы объявляем зависимость класса типа как часть определения параметра типа. Объявление A: WritableLike подразумевает, что для работы параметра type должен иметь экземпляр класса типа для класса класса WritableLike. Мы можем получить доступ к экземпляру, используя ключевое слово Scala implicitly.implicitly вызова немного многословный, поэтому принято определять вспомогательную функцию в объекте компаньона Класса типа по:

object WritableLike { 
    def apply[A: WritableLike]: WritableLike[A] = implicitly[WritableLike[A]] 
} 

Теперь мы можем получить доступ к экземпляру класса типа, как это: WritableLike[A].toXml(a)

помощник метод также может помочь при использовании класса типа вне определений экземпляра:

WritableLike[String].toXml("foobar")    // <text>foobar</text> 
WritableLike[Int].toXml(123)      // <integer>123</integer> 
WritableLike[Name].toXml(Name("Alan"))    // <name>Alan</name> 
WritableLike[Option[String]].toXml(Some("foobar")) // <text>foobar</text> 
WritableLike[Option[Int]].toXml(None)    // <empty /> 
+0

Надеюсь, этот ответ поможет. Если у вас есть какие-либо вопросы, просто прокомментируйте здесь, и я увижу, могу ли я расширить свой ответ. :) –

+0

Привет Яко спасибо за ваш ответ. В более широком контексте имеется несколько форматировщиков, каждый из которых генерирует xml NodeSeqs из комбинации классов case, строк, дат, других nodeseq и т. Д. (Как NodeSeq). Иногда есть пустые строки, nodeseqs или Option [caseclass] of None. Иногда класс case используется в нескольких местах (для форматирования) для генерации разных частей документа xml с различными тегами xml, поэтому гибкость генерации узла с переданной функцией имеет решающее значение. –

+0

Деловое дело в порядке, однако я хотел исследовать мощность и ограничения типа класса в Scala. Возможно, это предел языка или рисунка? Просто кажется, что это должно быть возможно. –