2013-06-18 23 views
10

Aynı argümanları alan çeşitli işlevlere sahip bir program yazıyorum. Haskell: Hepsi aynı argümanları alan bir işlevler grubu nasıl organize edilir

buildPhotoFileName time word stamp = show word ++ "-" ++ show time ++ show stamp 
buildAudioFileName time word = show word ++ "-" ++ show time ++ ".mp3" 
buildDirectoryName time word = show word ++ "_" ++ show time 

Ben zamanında time ve word parametrelerini almak için IO bir kaynak üzerinde döngü ediyorum ki: Burada basitlik için biraz yapmacık bir örnektir.

let photo = buildPhotoFileName time word stamp 
    audio = buildAudioFileName time word 
    dir = buildDirectoryName time word 
in .... 

Bu ilkeyi "Do not tekrar" ihlali gibi görünüyor: Bu döngü olarak, bunu yapmak o kadar fazla işlem için yukarıda fonksiyonların sonuçlarını katılması gerekir.

let wrd = processWord word 
    photo = buildPhotoFileName time wrd stamp 
    audio = buildAudioFileName time wrd 
    dir = buildDirectoryName time wrd 
in .... 

ve ben word yazdım her seferinde değiştirmek zorunda kalacak: Ben word alarak bir işleve word değiştirmek istiyorum bulmak yolda, ben şöyle let ifadenin başına bağlayıcı yeni bir hale getirebileceğini wrd numaralı telefona, bazı işlev çağrılarını değiştirmeyi hatırlıyorum, ancak diğerlerini değil, hatalara yol açıyor.

OOP'da, yukarıdaki işlevleri, kurucusu time ve word argümanları olarak alacak bir sınıfa yerleştirerek çözerim. Anlatılan nesne esas olarak, time ve word'a bağlanan üç işlev olacaktır. Ardından, işlevlerin "argüman" olarak word yerine processWord word aldığından emin olmak için, kurucuda processWord'u arayabilirim.

İşlevsel Programlama ve Haskell için daha uygun olacak daha iyi bir yolu nedir?

cevap

11

Bunun için bir OO sarmalayıcı sınıfı oluşturmaya hazır olduğunuzu söylediğinizden, işlevlerinizi değiştirmeye açık olduğunuzu kabul ediyorum. Sonuçlardan herhangi gerekmiyorsa

let wrd = processWord word 
    (photo, audio, dir) = buildFileNames time wrd stamp 
    in .... 

Ve:

buildFileNames time word stamp = 
    (show word ++ "-" ++ show time ++ show stamp, 
    show word ++ "-" ++ show time ++ ".mp3", 
    show word ++ "_" ++ show time) 

Öyle gibi kullanmak mümkün olacak: İstediğin her üç sonuçların bir demet üretimi bir işlev aşağıdadır , sadece bu yüzden gibi onları atlayabilirsiniz:

let wrd = processWord word 
    (_, audio, _) = buildFileNames time wrd stamp 
    in .... 

o tembel olacağından, Haskell kullanmadığınız değerlerinin hesaplanması kaynakları israf konusunda endişelenmenize gerek yok dikkati çekiyor.

+1

tarafından basitleştirmek başladı. Bu hileyi daha önce hiç görmemiştim, ancak bu, arka görüşte oldukça açık. Küçük bir not: üç sonuç dizgisi parçaları paylaşır, bu yüzden bazılarını açıkça paylaşmaları için izin verebilirsiniz diye düşünüyorum. –

+0

@ AndrásKovács [Ortak alt-çıkarma eleme] işlemini gerçekleştiren derleyiciden dolayı gerek yoktur (http://stackoverflow.com/q/15084162/485115). –

7

OOP arazisinden tarif ettiğiniz çözüm, FP arazisinde bana iyi bir şey gibi geldi. Şöyle ki:

data UID = UID 
    { _time :: Integer 
    , _word :: String 
    } 

bu kayıtta "damgası" dahil dahil ya da değil muhtemelen burada cevaplamak için yeterli bilgiye sahip olmadığı bir tasarım karardır. Bir kendi modülünde bu veri türü koyun ve bir "akıllı yapıcı" ve "akıllı erişimcileri" tanımlayabiliriz:

uid = UID 
time = _time 
word = _word 

Sonra modül sınırında, örneğin gerçek yapıcı ve erişimcileri gizlemek UID türünü uid akıllı yapıcı ve time ve word akıllı erişimcileri, ancak UID yapıcısı veya _time ve _word erişimcileri ihracat.

module UID (UID, uid, time, word) where 

daha sonra akıllı yapıcı bazı işlem yapması gerektiğini tespit edersek, uid tanımını değiştirebilirsiniz: Nikita Vokov cevabı üzerine

uid t w = UID t (processWord w) 
6

Bina, bazı düzgün için record wild cards kullanabilirsiniz küçük tekrarı ile sözdizimi: birden eğlenceli aynı parametreler etrafında geçiyoruz

{-# LANGUAGE RecordWildCards #-} 

data FileNames = FileNames { photo :: String, audio :: String, dir :: String } 

buildFileNames :: Word -> Time -> Stamp -> FileNames 
buildFileNames time word stamp = FileNames 
    (show word ++ "-" ++ show time ++ show stamp) 
    (show word ++ "-" ++ show time ++ ".mp3") 
    (show word ++ "_" ++ show time) 

let FileNames {...} = buildFileNames time wrd stamp 
in ... photo ... audio ... dir... 
+0

@ user1436026 Bu örnekte, OOP nesnesiniz olarak 'FileNames' ve kurucunuz olarak' buildFileNames' düşünün. 'Photo',' audio' ve 'dir' üç kaydı genel özelliklerdir. Her biri, ilk kez eriştiğinizde hesaplanır. –

4

Sadece size başka örnek vermek gerekirse seksiyonlar, bunun yerine Okuyucu monad kullanabilirsiniz:

import Control.Monad.Reader 

runR = flip runReader 

type Params = (String, String, String) 

buildPhotoFileName :: Reader Params String 
buildPhotoFileName = do 
    (time, word, stamp) <- ask 
    return $ show word ++ "-" ++ show time ++ show stamp 

main = do 
    runR (time, word, stamp) $ do 
    photo <- buildPhotoFileName 
    audio <- buildAudioFileName 
    dir <- buildDirectoryName 
    processStuff photo audio dir 
+0

İşlevinizi Reader'a dayanarak yapmak, körelme vb. Gibi birçok işlevsellik özelliğini ortadan kaldırır. – Ankur

+0

Elbette, ama karşılığında çok güzel özellikler elde edersiniz. –

0

David Wagner'in çözümü, ve OO hedefler üzerine inşa etmek ayrı bir modüle buildxxx işlev veya fonksiyonlarını hareket etmelidir (NameBuilders?) İşte size tam kontrol verecek .

Bu yaklaşımda bile, David'in önerdiği gibi, değişkenleri modülün içindeki işlevlerle "sarmalısınız".

Değişkenleri ve buildxxx yapıcısını (üçlü döndüren) veya yapıcıları (üç ayrı işlev) dışa aktarırsınız.

ayrıca çok güzel

buildDirectoryName time word = show word ++ "_" ++ show time 
    buildPhotoFileName stamp = buildDirectoryName + show stamp 
    buildAudioFileName =  buildDirectoryName ++ ".mp3"