Scala, эквивалентный где-пунктам Haskell?

Перейдите к предпочтениям Отладки XCode. Удостоверьтесь, что "Символы загрузки лениво" НЕ выбран.

16
задан Kim Stebel 16 August 2009 в 22:27
поделиться

3 ответа

В Hakell, где предложения содержат локальные определения функции. В Scala нет явных предложений where, но такая же функциональность может быть достигнута с помощью локальных var , val и def .

Локальные `var` и `val`

В Scala:

def foo(x: Int, y: Int): Int = {
  val a = x + y 
  var b = x * y
  a - b
}

В Haskell:

foo :: Integer -> Integer -> Integer 
foo x y = a - b
        where 
          a = x + y
          b = x * y

Локальный` def`

В Scala

def foo(x: Int, y: Int): Int = {
  def bar(x: Int) = x * x
  y + bar(x)
}

В Haskell

foo :: Integer -> Integer -> Integer 
foo x y = y + bar x
         where 
           bar x = x * x

Пожалуйста, поправьте меня, если я допустил какие-либо синтаксические ошибки в примере с Haskell, поскольку у меня в настоящее время не установлен компилятор Haskell на этом компьютере :).

Более сложные примеры могут быть получены аналогичными способами (например, с использованием сопоставления с образцом, которое поддерживают оба языка). Локальные функции имеют в точности синтаксис, как и любые другие функции, только их область видимости - это блок, в котором они находятся.

РЕДАКТИРОВАТЬ : Также см. Ответ Дэниела для получения такого примера и некоторых пояснений по этому вопросу.

РЕДАКТИРОВАТЬ 2 : добавлено обсуждение ленивых var s и val s.

Ленивый `var` и` val`

Ответ Эдварда Кметта правильно указал, что в предложении where в Haskell есть лень и чистота. Вы можете сделать что-то очень похожее в Scala, используя ленивые переменные. Они создаются только при необходимости. Рассмотрим следующий пример:

def foo(x: Int, y: Int) = { 
  print("--- Line 1: ");
  lazy val lazy1: Int = { print("-- lazy1 evaluated "); x^2}
  println();

  print("--- Line 2: ");
  lazy val lazy2: Int = { print("-- lazy2 evaluated "); y^2}
  println();

  print("--- Line 3: ");
  lazy val lazy3: Int = { print("-- lazy3 evaluated ")
    while(true) {} // infinite loop! 
    x^2 + y^2 }
  println();

  print("--- Line 4 (if clause): ");
  if (x < y) lazy1 + lazy2
  else lazy2 + lazy1
}

Здесь lazy1 , lazy2 и lazy3 - все ленивые переменные. lazy3 никогда не создается (поэтому этот код никогда не входит в бесконечный цикл), а порядок создания экземпляров lazy1 и lazy2 зависит от аргументов функции. Например, когда вы вызываете foo (1,2) , вы получаете lazy1 , созданный до lazy2 , и когда вы вызываете foo (2,1) получится обратное. Попробуйте выполнить код в интерпретаторе scala и посмотрите распечатку! (Я не буду помещать его здесь, поскольку этот ответ уже довольно длинный.)

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

РЕДАКТИРОВАТЬ 3: Добавлено обсуждение области видимости, см. Вопрос.

Объем локальных определений

Локальные определения имеют объем блока, в котором они объявлены, как и ожидалось (ну, в большинстве случаев, в редких ситуациях они могут избежать блока, например, когда с использованием привязки переменных в середине потока в циклах for). Поэтому для ограничения объема выражения можно использовать local var , val и def . Возьмем следующий пример:

object Obj {
  def bar = "outer scope"

  def innerFun() {
    def bar = "inner scope"
    println(bar) // prints inner scope
  }

  def outerFun() {
    println(bar) // prints outer scope
  }

  def smthDifferent() {
    println(bar) // prints inner scope ! :)
    def bar = "inner scope"
    println(bar) // prints inner scope
  }

  def doesNotCompile() {
    { 
      def fun = "fun" // local to this block
      42 // blocks must not end with a definition... 
    }
    println(fun)
  }

}

И innerFun () , и outerFun () ведут себя должным образом. Определение бара в innerFun () скрывает полосу , определенную во включающей области. Кроме того, функция fun является локальной по отношению к своему охватывающему блоку, поэтому ее нельзя использовать иначе. Метод doesNotCompile () ... не компилируется. Интересно отметить, что оба вызова println () из метода smthDifferent () print внутренней области видимости . Поэтому да, вы можете помещать определения после того, как они используются внутри методов! Я бы не рекомендовал, так как считаю это плохой практикой (по крайней мере, на мой взгляд). В файлах классов вы можете расположить определения методов по своему усмотрению, но я бы оставил все def внутри функции, прежде чем они будут использованы. И val s и var s ... ну ... мне неудобно ставить их после того, как они используются.

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

{
// some logic

// some defs

// some other logic, returning the result
}    

Как я уже сказал, вы не можете завершить блок только с помощью // some defs . В этом Scala немного отличается от Haskell :).

РЕДАКТИРОВАТЬ 4 : Разработано определение материала после их использования, подсказано комментарием Кима .

Определение «материала» после использования them

Это сложная вещь для реализации на языке, который имеет побочные эффекты. В мире без побочных эффектов порядок не имеет значения (методы не будут зависеть от побочных эффектов). Но поскольку Scala допускает побочные эффекты, то место, где вы определяете функцию , имеет значение . Кроме того, когда вы определяете val или var , правая часть должна быть вычислена на месте, чтобы создать экземпляр val . Рассмотрим следующий пример:

// does not compile :)
def foo(x: Int) = {

  // println *has* to execute now, but
  // cannot call f(10) as the closure 
  // that you call has not been created yet!
  // it's similar to calling a variable that is null
  println(f(10))

  var aVar = 1

  // the closure has to be created here, 
  // as it cannot capture aVar otherwise
  def f(i: Int) = i + aVar

  aVar = aVar + 1

  f(10)
}

Приведенный вами пример действительно работает, если значения val являются ленивыми или являются def .

def foo(): Int = {
  println(1)
  lazy val a = { println("a"); b }
  println(2)
  lazy val b = { println("b"); 1 }
  println(3)
  a + a
}

В этом примере также хорошо показано кеширование в действии (попробуйте изменить lazy val на def и посмотрите, что произойдет :)

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

- Flaviu Cipcigan

24
ответ дан 30 November 2019 в 16:50
поделиться

You can use var and val to provide local variables, but that is different than Haskell's where clause in two fairly important aspects: laziness and purity.

Haskell's where clause is useful because laziness and purity alows the compiler to only instantiate the variables in the where clause that are actually used.

This means you can write a big long local definition, and drop a where clause below it and there is no consideration needed for the order of effects (because of purity) and no consideration needed for if each individual code branch needs all of the definitions in the where clause, because laziness allows unused terms in the where clause to exist just as thunks, which purity allows the compiler to choose to elide from the resulting code when they aren't used.

Scala, unfortunately, has neither of these properties, and so cannot provide a full equivalent to Haskell's where clause.

You need to manually factor out the vars and vals that you use and put them in before the statements that use them, much like ML let statements.

4
ответ дан 30 November 2019 в 16:50
поделиться

Похоже, да. Я не буду вдаваться в подробности, как это уже сделал Flaviu , но приведу пример из Википедии.

Haskell:

calc :: String -> [Float]
calc = foldl f [] . words
  where 
    f (x:y:zs) "+" = (y + x):zs
    f (x:y:zs) "-" = (y - x):zs
    f (x:y:zs) "*" = (y * x):zs
    f (x:y:zs) "/" = (y / x):zs
    f xs y = read y : xs

Эти определения - просто определения, локальные для calc . Итак, в Scala мы бы сделали следующее:

def calc(s: String): List[Float] = {
  def f(s: List[Float], op: String) = (s, op) match {
    case (x :: y :: zs, "+") => (y + x) :: zs
    case (x :: y :: zs, "-") => (y - x) :: zs
    case (x :: y :: zs, "*") => (y * x) :: zs
    case (x :: y :: zs, "/") => (y / x) :: zs
    case (xs, y) => read(y) :: xs
  }

  s.words.foldLeft(List[Float]())(f)
}

Поскольку Scala не имеет эквивалента read , вы можете определить его, как показано ниже, с целью запуска этого конкретного примера:

def read(s: String) = s.toFloat

Scala не есть слов , к моему большому огорчению, хотя это легко определить:

implicit toWords(s: String) = new AnyRef { def words = s.split("\\s") }

Теперь определение Haskell более компактно по разным причинам:

  • Он имеет более мощный вывод типов, так что ничего, кроме типа of calc необходимо объявить. Scala не может этого сделать из-за сознательного дизайнерского решения быть объектно-ориентированным с моделью классов.

  • Он имеет неявное определение сопоставления с образцом, тогда как в Scala вы должны объявить функцию, а затем объявить сопоставление с образцом.

  • Его обработка каррирования просто превосходит Scala в плане лаконичности. Это результат различных решений, касающихся модели классов и обозначений операторов, в которых обработка каррирования не считалась важной.

  • В Haskell есть особая обработка списков, позволяющая иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть настолько компактным, насколько List может быть в Scala.

Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)

вы должны объявить функцию, а затем объявить совпадение с шаблоном.

  • Его обработка каррирования просто превосходит Scala в плане краткости. Это результат различных решений, касающихся модели классов и обозначений операторов, в которых обработка каррирования не считалась важной.

  • В Haskell есть особая обработка списков, позволяющая иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть настолько компактным, насколько List может быть в Scala.

  • Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)

    вы должны объявить функцию, а затем объявить совпадение с шаблоном.

  • Его обработка каррирования просто превосходит Scala в плане лаконичности. Это результат различных решений, касающихся модели классов и обозначений операторов, в которых обработка каррирования не считалась важной.

  • В Haskell есть особая обработка списков, позволяющая иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть настолько компактным, насколько List может быть в Scala.

  • Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)

  • Его обработка каррирования просто превосходит Scala по лаконичности. Это результат различных решений, касающихся модели классов и обозначений операторов, в которых обработка каррирования не считалась важной.

  • В Haskell есть особая обработка списков, позволяющая иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть настолько компактным, насколько List может быть в Scala.

  • Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)

  • Его обработка каррирования просто превосходит Scala по лаконичности. Это результат различных решений, касающихся модели классов и обозначений операторов, в которых обработка каррирования не считалась важной.

  • В Haskell есть особая обработка списков, позволяющая иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть таким же компактным, каким может быть List в Scala.

  • Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)

  • В Haskell есть особая обработка списков, что позволяет иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть таким же компактным, каким может быть List в Scala.

  • Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)

  • В Haskell есть особая обработка списков, что позволяет иметь для них более сжатый синтаксис. В Scala списки обрабатываются так же, как и любой другой класс, вместо этого прилагаются усилия, чтобы гарантировать, что любой класс может быть настолько компактным, насколько List может быть в Scala.

  • Таким образом, существуют различные причины для этого. почему Scala делает то, что делает, хотя мне бы хотелось неявного определения сопоставления с образцом. : -)

    5
    ответ дан 30 November 2019 в 16:50
    поделиться
    Другие вопросы по тегам:

    Похожие вопросы: