2009-10-31 2 views
22

Я новичок в Scala, и из того, что я понимаю, выход в Scala не похож на доходность на C#, это больше похоже на выбор.Имеет ли Scala эквивалент выхода C#?

Имеет ли Scala что-то похожее на доходность C#? Выход C# велик, потому что он очень удобен для написания итераторов.

Update: вот пример псевдо-код из C# Я хотел бы быть в состоянии осуществить в Scala:

public class Graph<T> { 
    public IEnumerable<T> BreadthFirstIterator() { 
     List<T> currentLevel = new List<T>(); 
     currentLevel.add(_root); 

     while (currentLevel.count > 0) { 
     List<T> nextLevel = new List<T>(); 
     foreach(var node in currentLevel) { 
      yield return node; 
      nextLevel.addRange(node.Children); 
     } 
     currentLevel = nextLevel; 
     } 
    } 
} 

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

graph.BreadthFirstIterator().foreach(n => Console.WriteLine(n)); 

в C#, выход просто синтаксический сахар, чтобы сделать его легко писать итератор (IEnumerable<T> в .Net, аналогичный до Iterable на Java). Как итератор, его оценивают лениво.

Обновление II: Возможно, я ошибаюсь, но я думаю, что вся точка урожая в C# заключается в том, что вам не нужно писать функцию более высокого порядка. Например. вы можете написать регулярный цикл или использовать метод, например select/map/filter/where, вместо того, чтобы передавать функцию, которая затем пересекает последовательность.

E.g. graph.iterator().foreach(n => println(n)) вместо graph.iterator(n => println(n)).

Таким образом, вы можете легко их сцепить, например, graph.iterator().map(x => x.foo).filter(y => y.bar >= 2).foreach(z => println(z)).

+0

Этот вопрос также отвечает в двух других местах: http://stackoverflow.com/questions/2201882/implementing-yield-yield-return-using-scala-continuations http://stackoverflow.com/questions/2137619/scala-analog-to-python-generator Существует также ответ на вопрос, как сделать его совместимым с операторами 'for': http://stackoverflow.com/questions/8934226/continuations-and-for- Понимание-что-несовместимость –

ответ

3

Я думаю, что ответ (запрет на изменения в 2.8) заключается в том, что ответ отрицательный, у Scala нет синтаксического сахара, подобного выводу C# для записи итераторов (реализации IEumerable или Iterable).

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

Вот как я бы написать Траверс в C# без использования выхода:

public class Graph<T> { 
    public void BreadthFirstTraversal(Action<T> f) { 
     List<T> currentLevel = new List<T>(); 
     currentLevel.add(_root); 

     while (currentLevel.count > 0) { 
     List<T> nextLevel = new List<T>(); 
     foreach(var node in currentLevel) { 
      f(node); 
      nextLevel.addRange(node.Children); 
     } 
     currentLevel = nextLevel; 
     } 
    } 
} 

Вы могли бы использовать его как это:

graph.BreadthFirstTraversal(n => Console.WriteLine(n)); 

Или так:

graph.BreadthFirstTraversal(n => 
{ 
    Console.WriteLine(n); 
    DoSomeOtherStuff(n); 
}); 
+3

Это, конечно, более интуитивно понятно с выходом C#. –

+1

А также вы не могли цеплять вызовы таким образом, как в: graph.BreadthFirstTraversal(). Где (...). –

4

Да это, вы можете захотеть взглянуть на этот вопрос для ответа: What is Scala's yield?

Вот Документы от Scala для этого типа конструкции: http://www.scala-lang.org/node/111

UPDATE:

Этот блог рассказывает о урожае C# и Scala: http://hestia.typepad.com/flatlander/2009/01/scala-for-c-programmers-part-1-mixins-and-traits.html

Он подробно рассказывает о том, как расширения используются для работы IENumerable по сравнению с использованием Traits в Scala.

Итак, вы правы, что выход не будет работать таким же образом в Scala, как и C#, но это потому, что они очень разные, поэтому, если вы хотите сделать это BreadthFirst как признак, вы можете позвонить по номеру map() и filter и foreach, так же, как и на C#, но эта черта поможет решить проблему пересечения коллекции.

+0

Спасибо Джеймс, я проверю их. –

+2

Нет, они не то же самое. Scala 2.7.x не имеет эквивалентной конструкции как «выход» C#. Однако в Scala 2.8.x, благодаря плагину компилятора Delimited Continuation, можно запрограммировать конструкцию с продолжением, чтобы довольно легко имитировать C# «yield». –

+0

Любые мысли о том, как я примиряю противоречивые ответы Джеймса и Уолтера? –

3

Несмотря на то, что у Scala есть ключевое слово yield, это сильно отличается от C# yield, а Ruby's yield отличается от обоих. Похоже, это слишком сильно используемое ключевое слово. Использование yield в C# выглядит очень ограниченным на первый взгляд.

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

принять Microsoft's example, вот метод Scala:

object Powers { 
    def apply(number:Int, exponent:Int) (f:(Double) => Any) = { 
    (new Range(1,exponent+1,1)).map{exponent => f(Math.pow(number, exponent))} 
    } 
} 

Теперь у вас есть "итератор":

scala> Powers(2,8){ println(_) } 
2.0 
4.0 
8.0 
16.0 
32.0 
64.0 
128.0 
256.0 

Примечания:

  • Powers(2,8) такая же, как Powers.apply(2,8). Это всего лишь компилятор.
  • Этот метод определяется двумя списками параметров, которые могут быть запутывающими. Он просто позволяет делать: Powers(2, 8){ println(_) } вместо Powers(2, 8, {println(_)})

Scala: 1, C#: 0


Update:

Для вашего только что добавлен, например, написать traverse, что делает обход вы хотите, не задумываясь о том, как вы собираетесь его использовать. Затем добавьте дополнительный параметр, добавив (f(Node) => Any) после списка параметров traverse, например.

def traverse(node:Node, maxDepth:Int)(f(Node) => Any)) { ... } 

На момент traverse где у вас есть значение, которое вы бы yield с в C#, вызов f(yieldValue).

Если вы хотите использовать этот «итератор», позвоните traverse и передайте ему функцию, которая делает то, что вы хотите сделать для каждого элемента итератора.

traverse(node, maxDepth) { (yieldValue) => 
    // this is f(yieldValue) and will be called for each value that you call f with 
    println(yieldValue) 
} 

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

+0

Спасибо, Алекс, это отличный пример. Вопрос 1: Как действуют Силы (2,8)? Вопрос 2: в C#, с выходом, я могу легко написать итератор для любой структуры данных, просто «уступив» каждому элементу. Я могу легко сделать итератор, который идет по глубине с помощью DAG. как я могу это сделать в Scala? –

+2

Алекс, прочитав его снова, я думаю, что ваш пример немного от основания. 1. Его не ленивый (я не думаю), как пример C# от Microsoft. 2. Вы можете реализовать его так же в C#: (e) => Перечислить. Изменить (e, e + 1). Выбрать (n => f (Math.Power (e, n))) –

+0

a) Как работают Силы (2,8)? Я не уверен, что вы спрашиваете. Контекст карты проходит через каждый элемент диапазона (который по существу является List (1,2,3,4,5,6,7,8)) и вызывает метод, который передается с f (число, показатель степени), где показатель текущий элемент диапазона. b) Конечно, я думаю, что вы можете сделать это, что может сделать доходность C#, и многое другое. c) Это лениво, если вы имеете в виду, что он вызывает println для каждого результата, как он рассчитывается. d) Конечно, вы можете сделать это на C#, но он не демонстрирует индивидуальную итерацию. –

10

Уловка слова доход здесь отвлекает от обычного nt: как маркер входа/выхода в coroutine.C# BreadthFirstIterator в приведенном выше примере, похоже, использует yield в своем сопроводительном выражении; после того, как значение возвращается yield, следующий звонок на активное BreadthFirstIterator в IEnumerable будет продолжен со следующего заявления после yield.

В C#, yield есть coupled to the idea of iteration, а не как более общий оператор потока управления, но в пределах этой ограниченной области его поведение соответствует поведению сопрограммы. Ограничения от Scala могут позволять определять сопрограммы. До тех пор Scala не обладает такой способностью, особенно с учетом ее альтернативного значения для yield.

+2

Я думаю, что ты ударил ноготь по голове Се. Похоже, что Java получает собственные сопрограммы, которые могут сделать это доступным и в Scala: http://weblogs.java.net/blog/forax/archive/2009/11/19/holy-crap-jvm-has-coroutinecontinuationfiber-etc –

2

Вы можете сделать это в Scala> = 2.8, используя реализацию генераторов в терминах разграниченных продолжений. Вы будете нуждаться в continuations plugin и потом что-то вдоль этих линий,

import scala.continuations._ 
import scala.continuations.ControlContext._ 

object Test { 

    def loopWhile(cond: =>Boolean)(body: =>(Unit @suspendable)): Unit @suspendable = { 
    if (cond) { 
     body 
     loopWhile(cond)(body) 
    } else() 
    } 

    abstract class Generator[T] { 
    var producerCont : (Unit => Unit) = null 
    var consumerCont : (T => Unit) = null 

    protected def body : Unit @suspendable 

    reset { 
     body 
    } 

    def generate(t : T) : Unit @suspendable = 
     shift { 
     (k : Unit => Unit) => { 
      producerCont = k 
      if (consumerCont != null) 
      consumerCont(t) 
     } 
     } 

    def next : T @suspendable = 
     shift { 
     (k : T => Unit) => { 
      consumerCont = k 
      if (producerCont != null) 
      producerCont() 
     } 
     } 
    } 

    def main(args: Array[String]) { 
    val g = new Generator[Int] { 
     def body = { 
     var i = 0 
     loopWhile(i < 10) { 
      generate(i) 
      i += 1 
     } 
     } 
    } 

    reset { 
     loopWhile(true) { 
     println("Generated: "+g.next) 
     } 
    } 
    } 
} 
2

Как уже упоминалось вы можете создать генератор, используя продолжений-плагин для создания выход, который точно ведет себя как C#:

import scala.util.continuations._ 

object GenTest { 

    val gen = new Generator[Int] { def produce = { 
     yieldValue(1) 
     yieldValue(2) 
     yieldValue(3) 
     Thread.sleep(1000) 
     yieldValue(42) 
    }} 


    def main(args: Array[String]): Unit = { 
     for (v <- gen) { 
      println(v) 
     } 
    } 
} 

abstract class Generator[E] { 

    var loopFn: (E => Unit) = null 

    def produce(): Unit @cps[Unit] 

    def foreach(f: => (E => Unit)): Unit = { 
     loopFn = f 
     reset[Unit,Unit](produce) 
    } 

    def yieldValue(value: E): Unit @cps[Unit] = 
    shift { genK: (Unit => Unit) => 
     loopFn(value) 
     genK(()) 
    () 
    } 

} 
+0

Для этого требуется scala 2.8? –

+0

Да, он использует новый CPS-Compiler-Plugin с «scalac -P: continue: enable». Я понятия не имею, будет ли плагин интегрирован по умолчанию. – hotzen

+0

первоклассная поддержка генераторов будет приятной, может быть, в один прекрасный день. –

0

Исходя из фона C# и отлаживая код Scala от hotzen (адаптированный к Scala 2.11.6), я должен сказать, что использование этих продолжений приближается к эквиваленту C# -уровня. Я не знаю, будут ли продолжения по-прежнему работать аналогично, если потребуется несколько генераторов, все они будут работать в одних и тех же методах или, возможно, будут распространяться по разным методам, но я счастлив, что продолжения существуют, поэтому я не вынужден работать с несколькими потоками для достижения аналогично, или пройти по обратным вызовам.

Смежные вопросы