2012-02-14 2 views
2

Вот простой, баребонный пример того, как код, который я пытаюсь сделать, будет выглядеть в C++.Петля через набор функций с Haskell

while (state == true) { 
    a = function1(); 
    b = function2(); 
    state = function3(); 
} 

В программе я работаю, у меня есть некоторые функции, которые мне нужны проходной до BOOL состояния не равно ЛЖИ (или до одной из переменных, не скажем, переменные Ь, равно 0).

Как этот код будет выполнен в Haskell? Я искал здесь, Google и даже Bing, и не смог найти ясных, простых объяснений о том, как делать повторяющиеся действия с функциями.

Любая помощь будет оценена по достоинству.

+5

Код, который вы отправили, по своей сути является обязательным. Хотя в Haskell можно писать код в императивном стиле, это обычно не лучший подход. Я думаю, вы должны начать с более высокого уровня концепции того, что делает код, и попытаться перевести это в функциональное решение. –

+0

Вы правы ... Я все еще пытаюсь перевести мой образ мышления на C++ на функциональный язык. Но я не могу придумать другого способа сделать это без какой-то петли.В моей программе некоторые функции нужно вызывать несколько раз, пока не будут выполнены все условия. –

+2

Вы должны сказать нам, что вы _actually_ пытаетесь сделать здесь, чтобы мы могли помочь вам переработать его в функциональное решение. –

ответ

5

Принимая Daniels комментарий во внимание, это может выглядеть примерно так:

f = loop init_a init_b true 
    where 
     loop a b True = loop a' b' (fun3 a' b') 
      where 
       a' = fun1 .... 
       b' = fun2 ..... 
     loop a b False = (a,b) 
+1

Итак, у Haskell есть петля ?? Если функция есть, я могу ее использовать. : D Но я постараюсь лучше спланировать будущее для своего следующего проекта и построить программу таким образом, чтобы использовать рекурсию вместо циклов. Я хочу узнать, как использовать Haskell * правильно *. Спасибо. –

+2

@SubtleArray Я думаю, что вы неправильно истолковываете что-то, приведенный выше код определяет функцию, называемую loop. Он не использует никаких примитивных конструкций контура (кроме, быть может, рекурсии). – aelguindy

+0

Я только что узнал, что это трудный путь. Я подумал: «Почему этот цикл не работает?» : D Хорошо работает. Сейчас я пытаюсь превратить это во что-то еще Haskelly и рекурсивно, используя функцию сопоставления с образцом, которая вызывает другие функции. Я мог бы заставить его работать. –

1

Вы должны написать решение вашей проблемы в более функциональном подходе. Однако некоторый код в Haskell много работает как императивного зацикливание, взять, например, состояния монад, терминал рекурсии, until, foldr и т.д.

Простой пример факториала. В C вы должны написать цикл, в котором в haskell вы можете, например, написать fact n = foldr (*) 1 [2..n].

+0

Благодарим вас за ответ. Есть ли способ применить * до * или * foldr * для функций, а не целых чисел? –

+1

Здесь 'foldr' применяется к функции multiply' (*) '. Первое состояние представлено «1», а затем «текущий результат цикла» изменяется путем объединения предыдущего состояния и аргумента из списка. – Kru

+0

'unfoldr' - еще одна полезная операция« looping ». –

1

Если у Вас есть две функции f :: a -> b и g :: b -> c где a, b и c типы, как String или [Int], то вы можете создавать их просто написав f . b.

Если вы хотите, чтобы они перевернули список или вектор, вы могли бы написать map (f . g) или V.map (f . g), предположив, что вы сделали Import qualified Data.Vector as V.

Пример: Я хотел бы напечатать список уценки заголовков как ## <number>. <heading> ##, но мне нужен римские цифры, пронумерованных от 1 и моего список headings имеет тип типа [(String,Double)] где Double не имеет значения.

Import Data.List 
Import Text.Numeral.Roman 
let fun = zipWith (\a b -> a ++ ". " ++ b ++ "##\n") (map toRoman [1..]) . map fst 
fun [("Foo",3.5),("Bar",7.1)] 

Что, черт возьми, это делает?

toRoman превращает число в строку, содержащую римскую цифру. map toRoman делает это для каждого элемента цикла. map toRoman [1..] делает это к каждому элементу ленивым бесконечного списка [1,2,3,4,..], получая ленивый бесконечный список римских числовых строк

fst :: (a,b) -> a просто извлекают первый элемент кортежа. map fst отбрасывает наш глупый Meow информацию по всему списку.

\a b -> "##" ++ show a ++ ". " ++ b ++ "##" - это лямбда-выражение, которое принимает две строки и объединяет их в нужные строки форматирования.

zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] принимает две функции аргументов, такие как наше лямбда-выражение, и передает ее парам элементов из своих собственных вторых и третьих аргументов.

Вы должны заметить, что zip, zipWith и т. Д. Читают столько же ленивого бесконечного списка римских цифр, сколько необходимо для списка заголовков, то есть я имею номер моих заголовков, не поддерживая никакой переменной счетчика.

Наконец, я объявил fun, не называя его аргументом, потому что компилятор может понять это из того факта, что map fst требует один аргумент. Вы заметите, что поставили . перед моей второй картой. Я мог бы написать (map fst h) или $ map fst h, если бы я написал fun h = ..., но оставив аргумент выключен fun, это означало, что мне нужно было составить его с помощью zipWith после применения zipWith к двум аргументам из трех аргументов zipWith хочет.

Я надеюсь, что компилятор объединяет zipWith и map s в один контур через встраивание.

4

Ну, вот предложение о том, как сопоставить понятия здесь:

  • C++ цикл некоторая форма работы списка в Haskell.
  • Одна итерация цикла = обработка одного элемента списка.
  • Цитирование до определенного условия становится истинным = базовый случай функции, которая повторяется в списке.

Но есть кое-что критически отличается от императивных петель и функциональных функций списка: петли описывают как итерировать; функции верхнего порядка описывают структуру вычисления. Так, например, map f [a0, a1, ..., an] может быть описана этой схеме:

[a0, a1, ..., an] 
|  |   | 
f  f   f 
|  |   | 
v  v   v 
[f a0, f a1, ..., f an] 

Обратите внимание, что это описывает, каким образом результат связан с аргументами f и [a0, a1, ..., an], не так, как итерации выполняется шаг за шагом.

Аналогично, foldr f z [a0, a1, ..., an] соответствует этому:

f a0 (f a1 (... (f an z))) 

filter не вполне поддается диаграмм, но это легко утверждать много правил, что она удовлетворяет:

  • length (filter pred xs) <= length xs
  • Для каждый элемент x от filter pred xs, pred x является True.
  • Если x является элементом filter pred xs, то x является элементом xs
  • Если x не является элементом xs, то x не является элементом filter pred xs
  • Если x появляется перед x' в filter pred xs, то x появляется перед x' в xs
  • Если x появляется перед x' в xs, и оба x и x' появляются в filter pred xs, то x появляется перед x' в filter pred xs

В классической императивной программе, все три из этих случаев записываются в виде петель, и разница между ними сводится к тому, что корпус цикла делает. Функциональное программирование, напротив, настаивает на том, что этот тип структурной схемы не принадлежит к «телам петли» (функции f и pred в этих примерах); скорее, эти шаблоны лучше всего абстрагируются от функций более высокого порядка, таких как map, foldr и filter. Таким образом, каждый раз, когда вы видите одну из этих функций списка, вы мгновенно знаете некоторые важные факты о том, как связаны аргументы и результат, без необходимости читать какой-либо код; тогда как в типичной императивной программе вы должны прочитать тела петель, чтобы понять это.

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

3

Прежде всего, давайте подумаем о нескольких вещах.

  • Есть ли у function1 побочные эффекты?
  • Есть ли у function2 побочные эффекты?
  • Есть ли у function3 побочные эффекты?

Ответ на все из них является звучно очевидно ДА, потому что они не берут входы, и, предположительно, есть обстоятельства, которые заставляют вас идти вокруг петли в то время как более чем один раз (а не def function3(): return false). Теперь давайте переделаем эти функции с явным состоянием.

s = initialState 
sentinel = true 
while(sentinel): 
    a,b,s,sentinel = function1(a,b,s,sentinel) 
    a,b,s,sentinel = function2(a,b,s,sentinel) 
    a,b,s,sentinel = function3(a,b,s,sentinel) 
return a,b,s 

Ну, это довольно уродливо. Мы абсолютно ничего не знаем о том, из каких средств извлекается каждая функция, и мы ничего не знаем о том, как эти функции могут влиять на переменные a, b и sentinel, а также «любое другое состояние», которое я просто смоделировал как s.

Итак, давайте сделаем несколько предположений. Во-первых, я собираюсь предположить, что эти функции напрямую не зависят и никак не влияют на значения a, b и sentinel. Однако они могут изменить «другое государство».Итак, вот что мы получим:

s = initState 
sentinel = true 
while (sentinel): 
    a,s2 = function1(s) 
    b,s3 = function2(s2) 
    sentinel,s4 = function(s3) 
    s = s4 
return a,b,s 

Примечание Я использовал временные переменные s2, s3 и s4 указать изменения, что «другое государство» проходит. Время Хаскелла. Нам нужна управляющая функция, которая ведет себя как петля while.

myWhile :: s     -- an initial state 
     -> (s -> (Bool, a, s)) -- given a state, produces a sentinel, a current result, and the next state 
     -> (a, s)    -- the result, plus resultant state 
myWhile s f = case f s of 
    (False, a, s') -> (a, s') 
    (True, _, s') -> myWhile s' f 

Теперь как можно использовать такую ​​функцию? Ну, учитывая, мы имеем функции:

function1 :: MyState -> (AType, MyState) 
function2 :: MyState -> (BType, MyState) 
function3 :: MyState -> (Bool, MyState) 

Мы построим искомый код следующим образом:

thatCodeBlockWeAreTryingToSimulate :: MyState -> ((AType, BType), MyState) 
thatCodeBlockWeAreTryingToSimulate initState = myWhile initState f 
    where f :: MyState -> (Bool, (AType, BType), MyState) 
     f s = let (a, s2) = function1 s 
        (b, s3) = function2 s2 
        (sentinel, s4) = function3 s3 
       in (sentinel, (a, b), s4) 

Обратите внимание, как это похоже на не-некрасиво питона-подобный код, указанный выше.

Вы можете проверить, что код, который я представил хорошо набраны путем добавления function1 = undefined и т.д. для трех функций, а также следующее в верхней части файла:

{-# LANGUAGE EmptyDataDecls #-}  
data MyState 
data AType 
data BType 

Так сообщение о выносливости таково: в Haskell вы должны явно моделировать изменения состояния. Вы можете использовать «Государственную Монаду», чтобы сделать вещи немного красивее, но сначала вы должны понять идею передачи состояния вокруг.

+0

Упражнение для читателя: вместо использования 'myWhile', попробуйте написать' thisCodeBlockWeAreTryingToSimulate', используя 'unfoldr :: (b -> Maybe (a, b)) -> b -> [a]'. Подсказки: тип 'b' соответствует типу' s', а 'Maybe' занимает место' Bool'. Какую дополнительную информацию вы получите, используя 'unfoldr'? Какую информацию вы теряете? –

2

Давайте посмотрим на вашем C++ цикл:

while (state == true) { 
    a = function1(); 
    b = function2(); 
    state = function3(); 
} 

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

Давайте начнем с этой структурой

while (state == true) { 
    <<do stuff that updates state>> 
} 

В Haskell мы, очевидно, не собирается проверять переменную против true как условие цикла, потому что он не может изменить его значение [1] и мы d либо оценивать тело цикла навсегда, либо никогда. Поэтому вместо того, мы хотим, чтобы оценить функцию, которая возвращает логическое значение, на каком-то аргументе:

while (check something == True) { 
    <<do stuff that updates state>> 
} 

Ну, теперь мы не имеем state переменных, так что «делать такие вещи, которые обновляют состояние» выглядя довольно бессмысленно. И у нас нет something, чтобы перейти на check. Давайте подумаем об этом немного больше. Мы хотим, чтобы something был проверен в зависимости от того, что делает бит «do stuff». У нас нет побочных эффектов, поэтому означает, что something должен быть (или быть полученным), возвращенным из «do stuff». «делать вещи» также необходимо взять что-то, что меняется в качестве аргумента, или оно просто вернет ту же самую вещь навсегда, что также бессмысленно. Нам также нужно вернуть значение из всего этого, иначе мы просто сжигаем циклы процессора (опять же, без побочных эффектов нет смысла запускать какую-либо функцию, если мы не будем использовать ее выход каким-либо образом, и есть еще меньше очков функция, если мы никогда не будем использовать ее выход).

Так как о чем-то вроде этого:

while check func state = 
    let next_state = func state in 
    if check next_state 
     then while check func next_state 
     else next_state 

Давайте попробуем в GHCi:

*Main> while (<20) (+1) 0 
20 

Это результат применения (+1) несколько раз, пока результат меньше 20, начиная с 0.

*Main> while ((<20) . length) (++ "zob") "" 
"zobzobzobzobzobzobzob" 

Это результат повторного объединения «zob», в то время как результат th меньше 20, начиная с пустой строки.

Итак, вы можете видеть, что я определил функцию, которая является (вроде бит) аналогичной петле while с императивных языков. Нам даже не нужен специальный синтаксис цикла! (что является реальной причиной того, что у Haskell нет такого синтаксиса, если вам нужна такая вещь, вы можете выразить ее как функцию). Это не единственный способ сделать это, и опытные программисты Haskell, вероятно, будут использовать другие стандартные библиотечные функции для выполнения такой работы, вместо того, чтобы писать while.

Но я думаю, что полезно узнать, как вы можете выразить подобные вещи в Haskell. Это показывает, что вы не можете переводить такие вещи, как императивные петли напрямую в Haskell; Я не закончил перевод вашего цикла с точки зрения моего while, потому что он заканчивается довольно бессмысленным; вы никогда не используете результат function1 или function2, они вызываются без аргументов, поэтому они всегда возвращают одно и то же на каждой итерации, и function3 также всегда возвращает то же самое, и может возвращать только true или false, чтобы вызвать while для продолжения цикла или остановки, без получения информации.

Предположительно, в программе на C++ все они используют побочные эффекты, чтобы на самом деле выполнить определенную работу. Если они работают в памяти, тогда вам нужно перевести большую часть вашей программы сразу в Haskell для перевода этого цикла, чтобы иметь смысл. Если эти функции выполняют IO, вам нужно сделать это в монаде IO в Haskell, для которого моя функция while не работает, но вы можете сделать что-то подобное.


[1] Как и в сторону, стоит попробовать, чтобы понять, что «вы не можете изменить переменные» в Haskell это не просто произвольное ограничение, равно как и его просто приемлемым компромиссом для преимуществ чистота, это концепция, что не имеет смысла, как Haskell хочет, чтобы вы подумали о коде Haskell. Вы записываете выражения, которые являются результатом оценки функций по определенным аргументам: в f x = x + 1 вы говорите, что f xisx + 1. Если вы действительно думаете об этом так, а не думаете, что «f принимает x, затем добавляет его к нему, а затем возвращает результат», тогда понятие «иметь побочные эффекты» даже не применяется; как что-то существующее и равное чему-то другому каким-то образом меняет переменную или имеет какой-то другой побочный эффект?

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