Как определяется задача sbt с использованием << = отличается от заданной с помощью: =, которая ссылается на другой параметр .value?

У меня есть следующий пример build.sbt, который использует sbt-assembly. (Мои assembly.sbt и project/assembly.sbt настроены, как описано в readme.)

import AssemblyKeys._

organization := "com.example"

name := "hello-sbt"

version := "1.0"

scalaVersion := "2.10.3"

val hello = taskKey[Unit]("Prints hello")

hello := println(s"hello, ${assembly.value.getName}")

val hey = taskKey[Unit]("Prints hey")

hey <<= assembly map { (asm) => println(s"hey, ${asm.getName}") }

//val hi = taskKey[Unit]("Prints hi")

//hi <<= assembly { (asm) => println(s"hi, $asm") }

Оба hello и hey являются функционально эквивалентными, и когда я запускаю любую задачу из sbt, они сначала запускают assembly и печатают сообщение с тем же именем файла. Есть ли значимая разница между двумя? (Похоже, что определение hello является "слегка магическим", поскольку зависимость от сборки подразумевается там, а не явно.)

Наконец, я пытаюсь понять, почему hey нужен вызов map. Очевидно, это приводит к тому, что другой объект передается в asm, но я не совсем уверен, как исправить ошибку этого типа в определении hi:

sbt-hello/build.sbt:21: error: type mismatch;
 found   : Unit
 required: sbt.Task[Unit]
hi <<= assembly { (asm) => println(s"hi, $asm") }
                                  ^
[error] Type error in expression

Похоже, что assembly здесь [sbt.TaskKey[java.io.File]][2], но я не вижу там определенного метода map, поэтому я не могу понять, что происходит в типе hey выше.

Ответ 1

sbt 0.12 синтаксис против синтаксиса sbt 0.13

Есть ли значимая разница между этими двумя?

По смысловой разнице, если вы подразумеваете семантическую разницу, как в наблюдаемой разнице в поведении скомпилированного кода, они одинаковы.

Если вы имеете в виду какие-либо намеченные различия в коде, это о разности стилей между синтаксисом sbt 0.13 синтаксиса sbt 0.12. Концептуально, я думаю, синтаксис sbt 0.13 упрощает изучение и кодирование, поскольку вы напрямую относитесь к T вместо Initialize[T]. Используя макрос, sbt 0.13 расширяет x.value на эквивалент sbt 0.12.

анатомия <= </h2 >

Я пытаюсь понять, зачем ему нужен вызов карты.

Фактически, один из макросов разницы теперь способен обрабатывать автоматически. Чтобы понять, почему map необходим в стиле sbt 0.12, вам нужно понять тип выражения DSL sbt, которое Setting[_]. Как Руководство по началу работы:

Вместо этого определение сборки создает огромный список объектов с типом Setting[T], где T - тип значения на карте. A Setting описывает преобразование в карту, например, добавление новой пары ключ-значение или добавление к существующему значению.

Для задач тип выражения DSL Setting[Task[T]]. Чтобы повернуть ключ настройки в Setting[T] или включить ключ задачи в Setting[Task[T]], вы используете метод <<=, определенный на соответствующих клавишах. Это реализовано в Structure.scala (база кода sbt 0.12 имеет более простую реализацию <<=, поэтому я буду использовать это как ссылку. ):

sealed trait SettingKey[T] extends ScopedTaskable[T] with KeyedInitialize[T] with Scoped.ScopingSetting[SettingKey[T]] with Scoped.DefinableSetting[T] with Scoped.ListSetting[T, Id] { ... }

sealed trait TaskKey[T] extends ScopedTaskable[T] with KeyedInitialize[Task[T]] with Scoped.ScopingSetting[TaskKey[T]] with Scoped.ListSetting[T, Task] with Scoped.DefinableTask[T] { ... }

object Scoped {
    sealed trait DefinableSetting[T] {
        final def <<= (app: Initialize[T]): Setting[T]  =  setting(scopedKey, app)
        ...
    }

    sealed trait DefinableTask[T] { self: TaskKey[T] =>
        def <<= (app: Initialize[Task[T]]): Setting[Task[T]]  =  Project.setting(scopedKey, app)
        ...
    }
}

Обратите внимание на типы параметров app. Клавиша настройки <<= принимает Initialize[T], тогда как клавиша задачи <<= принимает Initialize[Task[T]]. Другими словами, в зависимости от типа lhs выражения <<= изменяется тип rhs. Это требует, чтобы пользователи sbt 0.12 знали о разности настроек/задач в ключах.

Предположим, что у вас есть ключ настройки, например description на lhs, и предположим, что вы хотите зависеть от name и создать описание. Чтобы создать выражение зависимости параметра, вы используете apply:

description <<= name { n => n + " is good." }

apply для одного ключа реализовано в Settings.scala:

sealed trait Keyed[S, T] extends Initialize[T]
{
    def transform: S => T
    final def apply[Z](g: T => Z): Initialize[Z] = new GetValue(scopedKey, g compose transform)
}
trait KeyedInitialize[T] extends Keyed[T, T] {
    final val transform = idFun[T]
}

Далее, вместо description, предположим, что вы хотите создать параметр для jarName in assembly. Это ключ задачи, поэтому rhs <<= принимает Initialize[Task[T]], поэтому apply не является хорошим. Здесь map входит:

jarName in assembly <<= name map { n => n + ".jar" }

Это реализовано в Structure.scala:

final class RichInitialize[S](init: Initialize[S]) {
    def map[T](f: S => T): Initialize[Task[T]] = init(s => mktask(f(s)) )
}

Поскольку ключ установки расширяет KeyedInitialize[T], который равен Initialize[T], и потому что там неявное преобразование от Initialize[T] до RichInitialize[T], это доступно для name. Это нечетный способ определения map, так как карты обычно сохраняют структуру.

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

final class RichInitializeTask[S](i: Initialize[Task[S]]) extends RichInitTaskBase[S, Task] {...}

sealed abstract class RichInitTaskBase[S, R[_]] {
    def map[T](f: S => T): Initialize[R[T]] = mapR(f compose successM)
}

Итак, для задач map отображает из задачи типа S в T. Для настроек мы можем думать об этом как: map не задано в настройке, поэтому он неявно преобразуется в задачу и отображает ее. В любом случае, это позволит пользователям sbt 0.12 подумать: используйте apply для настроек, map для задач. Обратите внимание, что apply всегда исчезает для ключей задач, поскольку они расширяют Keyed[Task[T], Task[T]]. Это должно объяснить:

sbt-hello/build.sbt:21: error: type mismatch;
 found   : Unit
 required: sbt.Task[Unit]

Тогда возникает проблема с кортежем. До сих пор я обсуждал зависимости к одной настройке. Если вы хотите больше зависеть, sbt неявно добавляет apply и map в Tuple2..N для его обработки. Теперь он расширился до 15, но он использовался только до Tuple9. Увидев с новой точки зрения пользователя, идея вызова map в Tuple9 настроек, чтобы он генерировал задачу типа Initialize[Task[T]], выглядел бы как бы чужой. Без изменения основного механизма sbt 0.13 обеспечивает намного более чистую поверхность, чтобы начать работу.