Написание версии С# функции бесконечной последовательности Фибоначчи Haskell
Примечание.. Этот вопрос больше связан с любопытством. Я хочу узнать из любопытства, возможно ли даже транслитерировать реализацию Haskell в функциональный эквивалент С#.
Итак, я был учить себя Haskell за отличную игру и при решении Project Euler проблемы, с которыми я столкнулся в этой прекрасной программе Haskell Fibonacci:
fibs :: [Integer]
fibs = 1:1:zipWith (+) fibs (tail fibs)
Конечно, у меня возникло соблазн написать такую версию С#, чтобы:
-
Если я это сделаю:
IEnumerable<int> fibs =
Enumerable.Zip(Enumerable.Concat(new int[] { 1, 1 }, fibs),
//^^error
fibs.Skip(1), (f, s) => f + s);
Ошибка говорит о использовании неназначенной локальной переменной fibs
.
-
Поэтому я пошел немного повелительно, в то время как это компилируется...
public static IEnumerable<int> Get()
{
return Enumerable.Zip(Enumerable.Concat(new int[] { 1, 1 }, Get()),
Get().Skip(1), (f, s) => f + s);
}
Он ломается с исключением! Поэтому я пришел сюда.
Вопросы:
- Кто-нибудь может подумать о функциональном эквиваленте С#, который работает?
- Мне хотелось бы узнать, почему мои решения не работают.
Ответы
Ответ 1
Я должен предупредить вас, что я пытаюсь исправить ваши попытки, а не делать продуктивный код.
Кроме того, это решение полезно, чтобы мозг взорвался, а может быть и компьютер.
В вашем первом фрагменте вы пытались вызвать рекурсивное поле или локальную переменную, что невозможно. Вместо этого мы можем попробовать с лямбдой, которая может быть более похожа на это. Мы знаем из Церкви, что также невозможно, по крайней мере, традиционным способом. Лямбда-выражения не обозначены; вы не можете назвать их по имени (внутри реализации). Но вы можете использовать фиксированную точку для рекурсии. Если у вас здравомыслящий ум есть большой шанс не знать, что это такое, в любом случае вам следует попробовать эту ссылку, прежде чем продолжить с этим.
fix :: (a -> a) -> a
fix f = f (fix f)
Это будет реализация С# (что неверно)
A fix<A>(Func<A,A> f) {
return f(fix(f));
}
Почему не так? Потому что fix (f) представляет собой прекрасный stackoverflow. Поэтому нам нужно сделать это ленивым:
A fix<A>(Func<Func<A>,A> f) {
return f(()=>fix(f));
}
Теперь лениво! На самом деле вы увидите много этого в следующем коде.
В вашем втором фрагменте, а также в первом, у вас есть проблема, что второй аргумент Enumerable.Concat не ленив, и вы получите исключение stackoverflow или бесконечный цикл идеалистическим способом. Так что пусть ленится.
static IEnumerable<T> Concat<T>(IEnumerable<T> xs,Func<IEnumerable<T>> f) {
foreach (var x in xs)
yield return x;
foreach (var y in f())
yield return y;
}
Теперь у нас есть целая "структура" для реализации того, что вы пробовали функциональным способом.
void play() {
Func<Func<Func<IEnumerable<int>>>, Func<IEnumerable<int>>> F = fibs => () =>
Concat(new int[] { 1, 1 },
()=> Enumerable.Zip (fibs()(), fibs()().Skip(1), (a,b)=> a + b));
//let see some action
var n5 = fix(F)().Take(5).ToArray(); // instant
var n20 = fix(F)().Take(20).ToArray(); // relative fast
var n30 = fix(F)().Take(30).ToArray(); //this will take a lot of time to compute
//var n40 = fix(F)().Take(40).ToArray(); //!!! OutOfMemoryException
}
Я знаю, что подпись F является уродливой, как ад, но вот почему такие языки, как haskell, существуют и даже F #. С# не делается для того, чтобы эта работа выполнялась следующим образом.
Теперь вопрос в том, почему haskell может достичь чего-то подобного? Почему? потому что всякий раз, когда вы говорите что-то вроде
a:: Int
a = 4
Самый похожий перевод на С#:
Func<Int> a = () => 4
На самом деле это гораздо больше задействовано в реализации haskell, но это та идея, почему подобный метод решения проблем выглядит настолько разным, если вы хотите записать его на обоих языках
Ответ 2
Ответ на ваш первый вопрос: вот как это сделать на С#:
using System;
using System.Collections.Generic;
using System.Linq;
class P
{
static IEnumerable<int> F()
{
yield return 1;
yield return 1;
foreach(int i in F().Zip(F().Skip(1), (a,b)=>a+b))
yield return i;
}
static void Main()
{
foreach(int i in F().Take(10))
Console.WriteLine(i);
}
}
Ответ на второй вопрос: С# по умолчанию нетерпеливо, поэтому ваш метод имеет неограниченную рекурсию. Итераторы, которые используют yield
, однако возвращают перечислитель немедленно, но не строят каждый элемент до тех пор, пока это не потребуется; они ленивые. В Haskell все лениво автоматически.
ОБНОВЛЕНИЕ: Комментарий Yitz правильно указывает, что это неэффективно, потому что, в отличие от Haskell, С# автоматически не запоминает результаты. Мне не сразу понятно, как это исправить, сохраняя при этом этот причудливый рекурсивный алгоритм.
Конечно, вы никогда бы не написали fib, как это в С#, когда это намного проще:
static IEnumerable<BigInteger> Fib()
{
BigInteger prev = 0;
BigInteger curr = 1;
while (true)
{
yield return curr;
var next = curr + prev;
prev = curr;
curr = next;
}
}
Ответ 3
В отличие от версии С#, предоставленной в ответе Эрика Липперта, эта версия F # позволяет избежать повторных вычислений элементов и поэтому имеет сопоставимую эффективность с Haskell:
let rec fibs =
seq {
yield 1
yield 1
for (a, b) in Seq.zip fibs (Seq.skip 1 fibs) do
yield a + b
}
|> Seq.cache // this is critical for O(N)!
Ответ 4
Здесь он предназначен для Java, в зависимости от функциональной Java:
final Stream<Integer> fibs = new F2<Integer, Integer, Stream<Integer>>() {
public Stream<Integer> f(final Integer a, final Integer b) {
return cons(a, curry().f(b).lazy().f(a + b));
}
}.f(1, 2);
В С# вы можете зависеть от XSharpX
Ответ 5
Возьмем ответ Эрика, который имеет эквивалентную производительность Haskell, но все еще имеет другие проблемы (безопасность потоков, без возможности освобождения памяти).
private static List<int> fibs = new List<int>(){1,1};
static IEnumerable<int> F()
{
foreach (var fib in fibs)
{
yield return fib;
}
int a, b;
while (true)
{
a = fibs.Last();
b = fibs[fibs.Count() - 2];
fibs.Add(a+b);
yield return a + b;
}
}
Ответ 6
Перевод из среды Haskell в среду .NET намного проще, если вы используете функциональный декларативный язык F #, Microsoft, похожий на Haskell.