Devleti atamadan sürdürme


10

İşlevsel programlamayı öğreniyorum ve bazı özel senaryoların ödev kullanmadan nasıl uygulandığını anlamada sorun yaşıyorum. Aşağıdaki basit problem kafa karışıklığımı özetliyor.

Belirli bir veri yapısındaki değişikliklerle ilgili olayları alan ve bu veri yapısı belirli bir duruma ulaştığında olayları yayan bir program yazın.

Bu yüzden benim koruduğum veri yapısının bir kopyası var

datastructure_copy::DataStructure 

Ben değiştiğinde ateş olayların akışı var:

datastructure_changes::Stream Change

Veri yapısında bir değişiklik uygulayan ve yeni bir kopyasını döndüren bir işlevi var:

apply_change::Change -> DataStructure -> DataStructure

Ve veri durumunun istenen duruma ulaşıp ulaşmadığını kontrol eden bir yüklemim var.

is_ready::DataStructure ->Boolean

Başka bir deyişle, akışlarda çalışan 'azaltma' gibi bir şeye ihtiyacım var.

Bunu uygulamanın bir yolunun her değişiklik geldiğinde durumu yeniden hesaplamak olduğunu biliyorum, ancak bu pratik görünmüyor. Devlet monad'ıyla biraz oynadım, ama bana farklı bir problemi çözmek gibi geliyor.

Bunu yapmanın başka bir yolu var mı?

Sorumun tamamen kavramsal olduğunu ve Haskell'i çok iyi bilmediğimi unutmayın.


Öyle ya da böyle Haskell'de asla 'ödev' görmeyeceksiniz (ödev ve bağlanma arasındaki farka dikkat edin) çünkü 'Haskell'in “atama”, “değişebilir durum” ya da “değişkenler” kavramı yoktur ve “saf” işlevsel dil "" . Devlet Monad'ı aradığınız şey olmalı, onu nasıl kullanacağınızı öğrenmeniz yeterli. İsterseniz bugün daha kapsamlı bir cevap verebilirim.
Francesco Gramano

Yanıtlar:


2

Bunu uygulamanın bir yolunun her değişiklik geldiğinde durumu yeniden hesaplamak olduğunu biliyorum, ancak bu pratik görünmüyor.

Bir olay meydana geldiğinde uygulanan değişiklikler dağıtıcı değilse, şu ya da bu şekilde, her olay gerçekleştiğinde durumu yeniden hesaplamanız gerekir, çünkü son durum başlangıç ​​durumundan başka ardışık değişikliklerden başka bir şey değildir. Değişiklikler dağıtıcı olsa bile, genellikle belirli bir duruma bir sonraki duruma bir geçiş yapmak istersiniz, çünkü işleminizi belirli bir duruma ulaştığınız anda durdurmak istediğinizden ve bir sonraki durumu hesaplamak zorunda olduğunuzdan emin olun. yenisi aranan devlettir.

Fonksiyonel programlamada, durum değişiklikleri tipik olarak fonksiyon çağrıları ve / veya fonksiyon parametreleri ile temsil edilir.

Son durumun ne zaman hesaplanacağını tahmin edemeyeceğiniz için, kuyruk olmayan özyinelemeli işlev kullanmamalısınız. Her bir devletin öncekine dayandığı bir devlet akışı iyi bir alternatif olabilir.

Yani sizin durumunuzda, soruyu Scala'da aşağıdaki kodla cevaplayacağım:

import scala.util.Random

val initState = 0.0
def nextState(state: Double, event: Boolean): Double = if(event) state + 0.3 else state - 0.1 // give a new state
def predicate(state: Double) = state >= 1

// random booleans as events
// nb: must be a function in order to force Random.nextBoolean to be called for each  element of the stream
def events(): Stream[Boolean] = Random.nextBoolean #:: events()  

val states: Stream[Double] = initState #:: states.zip(events).map({ case (s,e) => nextState(s,e)}) // a stream of all the successive states

// stop when the state is >= 1 ;
// display all the states computed before it stopped
states takeWhile(! predicate(_)) foreach println 

Hangi verebilir, örneğin (Ben çıktı basitleştirilmiş):

0.0
0.3
0.2
0.5
0.8

val states: Stream[Double] = ... ardışık durumların hesaplandığı satırdır.

Bu akışın ilk elemanı sistemin başlangıç ​​durumudur. zipdurum akışını olay akışıyla tek bir çift öğe akışında birleştirir, her çift bir (durum, olay) olur. mapher çifti, eski durumun ve ilişkili olayın fonksiyonu olarak hesaplanan yeni durum olan tek bir değere dönüştürür. Bu nedenle, yeni bir durum önceden hesaplanmış bir durumun yanı sıra durumu "değiştiren" ilişkili olaydır.

Temel olarak, potansiyel olarak sonsuz bir durum akışı tanımlarsınız, her yeni durum son hesaplanan durumun bir fonksiyonu ve yeni bir olaydır. Scala'da (diğerlerinin yanı sıra) akışlar tembel olduğu için sadece talep üzerine hesaplanır, bu nedenle işe yaramaz durumları hesaplamak zorunda değilsiniz ve istediğiniz kadar eyaleti hesaplayabilirsiniz.

Yalnızca yüklemlere uyan ilk durumla ilgileniyorsanız, son kod satırını şu şekilde değiştirin:

states find predicate get

Hangi alır:

res7: Double = 1.1

Sihri yapan çizgi hakkında fikir val states: Stream[Double]...
verebilir misiniz

Elbette. Lütfen düzenlememe bak.
mgoeminne

1

2 fonksiyonunuz olduğunu söylüyorsunuz:

apply_change::Change -> DataStructure -> DataStructure
is_ready::DataStructure ->Boolean

ve eğer seni anlıyorsam is_ready, o zaman oldukça pahalıdır, bu yüzden bunu her değişiklik olayı için tekrar tekrar yapmak istemezsin.

İhtiyacınız olan şey, bir fonksiyonun ilk DataStructure'ı alıp basit bir duruma yoğunlaştırması ve yoğunlaştırılmış bir durumu, Değiştirmeyi ve yeni bir yoğunlaştırılmış durumu çıkaran bir fonksiyonu yoğunlaştırmasıdır.

Diyelim ki DataStructure bir üçlüdür x,y,zve x, y ve z'nin asal sayılar olmasını bekliyorsunuz. Bu durumda yoğunlaşmış durumunuz x, y, z'nin asal olmayan bir kümesi olabilir. X üssünü değiştiren bir değişiklik x'i kümeden kaldırır. X'i prime yapmaz hale getiren bir değişiklik sete x ekler (yoksa). DataStructure hazır, sonra küme boş.

Fikir, yoğunlaştırılmış durumu güncellemenin DataStructure'ı güncellemekten çok daha ucuz olması ve bilgi işlemin sıfırdan yapılmasıdır.

Not: Daha iyi bir yaklaşım, hangisinin asal olup olmadığını kontrol ettiği yerde x, y, z'den hangilerinin nerede olup olmadığını takip etmek olabilir. Her Değişiklik için ilgili alanı işaretlenmemiş olarak işaretlersiniz. Sonra is_ready çağrıldığında kontrol edin ve hatırlayın. Her değişiklikten sonra is_ready'i kontrol etmezseniz, x birden çok kez değişebileceğinden ve yalnızca bir kez kontrol ederseniz, bu daha iyi olur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.