Две, казалось бы, идентичные семантики: одна неявно связывается, другая нет - PullRequest
3 голосов
/ 30 мая 2011

Здравствуйте: я недавно изучал Scala (мой опыт работы в основном связан с шаблонами C ++), и я столкнулся с тем, чего в настоящее время не понимаю в Scala, и это сводит меня с ума.: (

(Кроме того, это мой первый пост в StackOverflow, где я заметил, что большинство действительно потрясающих людей из Scala тусуются, поэтому мне очень жаль, если я сделаю что-то ужасно глупое смеханизм.)

Моя конкретная путаница связана с неявным связыванием аргументов: я выдвинул конкретный случай, когда неявный аргумент отказывается связываться, но функция с, казалось бы, идентичной семантикой делает.Конечно, это может быть ошибка компилятора, но, учитывая, что я только начал работать со Scala, вероятность того, что я уже столкнулся с какой-то серьезной ошибкой, достаточно мала, и я ожидаю, что кто-то объяснит, что я сделал неправильно;P

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

1) упрощенный код, который не работает в ваy Я ожидал

import HList.::

trait HApplyOps {
    implicit def runNil
        (input :HNil)
        (context :Object)
        :HNil
    = {
        HNil()
    }

    implicit def runAll[Input <:HList, Output <:HList]
        (input :Int::Input)
        (context :Object)
        (implicit run :Input=>Object=>Output)
        :Int::Output
    = {
        HCons(0, run(input.tail)(context))
    }

    def runAny[Input <:HList, Output <:HList]
        (input :Input)
        (context :Object)
        (implicit run :Input=>Object=>Output)
        :Output
    = {
        run(input)(context)
    }
}

sealed trait HList

final case class HCons[Head, Tail <:HList]
    (head :Head, tail :Tail)
    extends HList
{
    def ::[Value](value :Value) = HCons(value, this)
}

final case class HNil()
    extends HList
{
    def ::[Value](value :Value) = HCons(value, this)
}

object HList extends HApplyOps {
    type ::[Head, Tail <:HList] = HCons[Head, Tail]
}

class Test {
    def main(args :Array[String]) {
        HList.runAny(   HNil())(null) // yay! ;P
        HList.runAny(0::HNil())(null) // fail :(
    }
}

Этот код, скомпилированный с Scala 2.9.0.1, возвращает следующую ошибку:

broken1.scala:53: error: No implicit view available from HCons[Int,HNil] => (java.lang.Object) => Output.
        HList.runAny(0::HNil())(null)

В этом случае я ожидаю, что runAll будетбыть привязанным к неявному аргументу run к runAny.

Теперь, если я изменю runAll, чтобы вместо непосредственного получения двух аргументов он возвращал функцию, которая в свою очередь принимает эти двааргументы (трюк, который я подумал попробовать, как я видел в чужом коде), он работает:

2) модифицированный код, который имеет то же поведение во время выполнения и фактически работает

    implicit def runAll[Input <:HList, Output <:HList]
        (implicit run :Input=>Object=>Output)
        :Int::Input=>Object=>Int::Output
    = {
        input =>
        context =>
        HCons(0, run(input.tail)(context))
    }

По сути, мой вопрос: почему это работает?; (Я ожидаю, что эти две функции имеют одинаковую общую сигнатуру типа:

1: [Input <:HList, Output <:HList] (Int::Input)(Object):Int::Output
2: [Input <:Hlist, Output <:HList] :Int::Input=>Object=>Int::Output

Если это помогает понять проблему, некоторые другие изменения также «работают» (хотя они изменяют семантику функции, иследовательно, не пригодные для использования решения):

3) жесткое кодирование runAll только для второго уровня путем замены Output на HNil

    implicit def runAll[Input <:HList, Output <:HList]
        (input :Int::Input)
        (context :Object)
        (implicit run :Input=>Object=>HNil)
        :Int::HNil
    = {
        HCons(0, run(input.tail)(context))
    }

4Удаление аргумента контекста из неявных функций: (

(В настоящее время мое лучшее предположение состоит в том, что порядок неявного аргумента по отношению к другим аргументам является ключевым фактором, который я пропускаю, но тот, который меня смущает: runAnyтакже имеет неявный аргумент в конце, поэтому очевидное «implicit def не очень хорошо работает с завершающим implicit» не имеет смысла для меня.)

Ответы [ 2 ]

6 голосов
/ 30 мая 2011

Когда вы объявляете implicit def следующим образом:

implicit def makeStr(i: Int): String = i.toString

, тогда компилятор может автоматически создать неявный объект Function из этого определения для вас и вставит его туда, где неявный тип Int => String ожидается.Это то, что происходит в вашей строке HList.runAny(HNil())(null).

Но когда вы определяете implicit def s, которые сами принимают неявные параметры (например, ваш runAll метод), он больше не работает, как компиляторне может создать объект Function, чей метод apply потребовал бы неявного - гораздо меньше гарантий того, что такой неявный будет доступен на сайте вызова.

Решение этого состоит в том, чтобы определить что-то подобное вместоrunAll:

implicit def runAllFct[Input <: HList, Output <: HList]
    (implicit run: Input => Object => Output):
        Int :: Input => Object => Int :: Output =
  { input: Int :: Input =>
    context: Object =>
      HCons(0, run(input.tail)(context))
  }

Это определение немного более явное, поскольку компилятору теперь не нужно пытаться создать объект Function из вашего def, а вместо этого будет вызовите ваш def напрямую, чтобы получить необходимый функциональный объект.И, вызывая его, автоматически вставит необходимый неявный параметр, который он может разрешить сразу.

По моему мнению, всякий раз, когда вы ожидаете неявные функции этого типа, вы должны предоставить implicit def, который действительновернуть Function объект.(Другие пользователи могут не согласиться ... кто-нибудь?) Тот факт, что компилятор способен создавать Function обертки вокруг implicit def, в основном, я полагаю, поддерживает неявные преобразования с более естественным синтаксисом, например, используя представление связывает вместе с простыми implicit def s, как мое первое преобразование Int в String.

3 голосов
/ 31 мая 2011

(Примечание: это краткое изложение дискуссии, которая состоялась, возможно, более подробно в разделе комментариев другого ответа на этот вопрос.)

Оказывается, проблема здесь заключается в , что параметр implicit не является первым в runAny, но не потому, что механизм неявного связывания игнорирует его: вместо этого проблема заключается в том, что параметр типа Output не связан ни с чем и должен быть косвенно выведен из типа неявного параметра run, который происходит "слишком поздно".

По сути, код для "неопределенных параметров типа" (что и есть Output в данном случае) используется только в ситуациях, когда рассматриваемый метод считается «неявным», что определяется его прямым списком параметров: в этом случае список параметров runAnyна самом деле просто (input :Input) и не является "неявным".

Таким образом, параметр типа для Input удается работать (получая значение Int::HNil), но Output это простодля ly устанавливается значение Nothing, которое «залипает» и приводит к тому, что тип аргумента run равен Int::HNil=>Object=>Nothing, что не может быть выполнено runNil, что приводит к сбою вывода типа runAny, дисквалифицируя его дляиспользование в качестве неявного аргумента для runAll.

Путем реорганизации параметров, как это сделано в моем модифицированном примере кода # 2, мы делаем runAny самим «неявным», что позволяет ему сначала полностью получить параметры своего типаопределяется перед применением оставшихся аргументов: это происходит потому, что его неявный аргумент сначала будет привязан к runNil (или runAny снова для более чем двух уровней), тип возвращаемого значения будет принят / связан.

Toсвязать свободные концы: причина, по которой пример кода № 3 работал в этой ситуации, заключается в том, что параметр Output даже не требовался: тот факт, что он был связан с Nothing, не влиял на любые последующие попытки связыванияего можно использовать для чего-либо или использовать его для чего-либо, и runNil был легко выбран для привязки к его версии неявного параметра run.

Наконец, пример кода№ 4 был на самом деле вырожденным, и его даже не следовало считать «работающим» (я только проверил, что он скомпилирован, а не генерировал соответствующий вывод): типы данных его неявных параметров были настолько упрощенными (Input=>Outputгде Input и Output фактически были предназначены для того же типа), что он просто был бы связан с conforms:<:<[Input,Output]: функция, которая, в свою очередь, действовала как идентичность в этом случае.

(Для получения дополнительной информации по случаю № 4, посмотрите этот явно мертвый вопрос: проблема с неявной неоднозначностью между моим методом и соответствиями в Predef .)

...