Haskell:使用接口和多态函数实现设计

时间:2013-12-03 12:21:26

标签: haskell interface polymorphism

我再次请求关于如何在Haskell中实现给定设计的评论。 在此先感谢所有提供有用评论的人。此外,我希望这可以帮助像我这样的其他Haskell新手,有一个实用的示例代码。

这一次,我们有一个多态函数doSampling(在模块样本中),它采用泛型函数f和 一个实数(索引)列表并返回Samples(索引,值= f(索引))。我们只希望实现doSampling一次,因为如果f是PolynomialSinus则无关紧要。为了那个原因, 我们引入了一个接口函数,并有多项式和Sinus类型实现它。 以下是正在实施的设计:

enter image description here

修改1

关于Function接口(Haskell中的类)存在争议。有人建议它实际上没有必要,因为doSampling可能会采用“裸体”功能(Double -> Double)。 但是,怎么做,如果你需要裸体功能中的一些额外状态(多项式的系数,窦+的放大+频率+相位?

修改2

kosmikus和Chris Taylor的非常好的答案。谢谢。 两者中的一个关键想法:有

doSampling :: (Double -> Double) -> [Double] -> Samples

这是:它需要一个函数(Double -> Double)(而不是Function)并列出并返回样本。

我的目的是保持PolynomialSinus es的状态。在克里斯的答案中没有考虑到这一点,但它在kosmikus中。另一方面,如果您无法访问源代码,kosmikus版本中的弱点可能是如何扩展其Function定义。

我也会指出:

  • Chris想要通过工厂函数(Double -> Double)mkPolynomial将多项式或窦封装到函数mkSinus中,该函数生成(使用currying?)所需的功能取适当的参数。 (虽然你以后不能参考这些参数)。

  • kosmikous'使用value转换(也使用currying?)Function(Double -> Double)

  • 的想法
  • 这两个答案都值得一读,因为他们有其他一些小的Haskell技巧来减少和简化代码。

总之

  • Chris答案不支持保持多项式或Sinus的状态

  • kosmikus答案不可扩展:添加新类型的函数(Cosinus ......)

  • 我的答案(详细)确实克服了以前的缺点,并且它允许(这不是问题所必需的)强制函数类型具有value之外的更多关联函数(在某种意义上) java-interfaces如何工作)。

我自己的方法

主要(用法)

import Polynomial
import Sinus
import Function
import Samples

-- ...............................................................
p1 = Polynomial [1, 0, 0.5]  -- p(x) =  1 + 0.5x^2 
s1 = Sinus 2 0.5 3 -- f(x) = 2 sin(0.5x + 3) 

-- ...............................................................

-- sample p1 from 0 to 5
m1 = doSampling p1  [0, 0.5 .. 5]
m2 = doSampling s1  [0, 0.5 .. 5]

-- ...............................................................
-- main
-- ...............................................................
main =  do
    putStrLn "Hello"
        print $ value p1 2
        print $ value s1 (pi/2)
        print $ pairs m1
        print $ pairs m2

功能

module Function where    
-- ...............................................................
-- "class type"  : the types belonging to this family of types
--    must implement the following functions:
--          + value : takes a function and a real and returns a real
-- ...............................................................
class Function f where 
    value :: f -> Double -> Double
        -- f is a type variable, this is:
        -- f is a type of the Function "family" not an actual function

样品

module Samples where

import Function

-- ...............................................................
-- Samples: new data type
-- This is the constructor and says it requieres
-- two list, one for the indexes (xs values) and another
-- for the values ( ys = f (xs) )
-- this constructor should not be used, instead use 
-- the "factory" function: new_Samples that performs some checks
-- ...............................................................
data Samples = Samples { indexes :: [Double] , values :: [Double] }
     deriving (Show)

-- ...............................................................
-- constructor: it checks lists are equal size, and indexes are sorted
new_Samples :: [Double] -> [Double] -> Samples
new_Samples ind val 
             | (length ind) /= (length val) = samplesVoid
             | not $ isSorted ind = samplesVoid
             | otherwise = Samples ind val

-- ...............................................................
-- sample a funcion
-- it takes a funcion f and a list of indexes and returns
-- a Samples calculating the values array as f(indexes)
doSampling :: (Function f) => f -> [Double] -> Samples
doSampling f ind = new_Samples ind vals
              where 
                    vals = [ value f x | x <- ind ]

-- ...............................................................
-- used as "error" in the construction
samplesVoid = Samples [] []

-- ...............................................................
size :: Samples -> Int
size samples = length (indexes samples)   
-- ...............................................................
-- utility function to get a pair (index,value) out of a Samples
pairs :: Samples -> [(Double, Double)]
pairs samples = pairs' (indexes samples) (values samples)

pairs' :: [Double] -> [Double] -> [(Double, Double)]
pairs' [] [] = []
pairs' [i] [v] = [(i,v)]
pairs' (i:is) (v:vs) = (i,v) : pairs' is vs


-- ...............................................................
-- to check whether a list is sorted (<)
isSorted :: (Ord t) => [t] -> Bool
isSorted [] = True
isSorted [e] = True
isSorted (e1:(e2:tail))
         | e1 < e2 = isSorted (e2:tail)
         | otherwise = False

module Sinus where

-- ...............................................................
import Function

-- ...............................................................
-- Sinus: new data type
-- This is the constructor and says it requieres
-- a three reals
-- ...............................................................
data Sinus = Sinus { amplitude :: Double, frequency :: Double, phase :: Double }
     deriving (Show)

-- ...............................................................
-- we say that a Sinus is a Function (member of the class Function)
-- and then, how value is implemented
instance Function Sinus where
         value s x = (amplitude s) * sin ( (frequency s)*x + (phase s))

多项式

module Polynomial where

-- ...............................................................
import Function

-- ...............................................................
-- Polynomial: new data type
-- This is the constructor and says it requieres
-- a list of coefficients
-- ...............................................................
data Polynomial = Polynomial { coeffs :: [Double] }
     deriving (Show)

-- ...............................................................
degree :: Polynomial -> Int
degree p = length (coeffs p)  - 1

-- ...............................................................
-- we say that a Polynomial is a Function (member of the class Function)
-- and then, how value is implemented
instance Function Polynomial where
         value p x = value' (coeffs p) x 1

--  list of coeffs -> x -> pw (power of x) -> Double
value' :: [Double] -> Double -> Double -> Double
value' (c:[]) _ pw =  c * pw
value' (c:cs) x pw =  (c * pw) + (value' cs x x*pw)

2 个答案:

答案 0 :(得分:12)

您当然不需要Function课程。所有这些重量级的类,实例,成员变量fluff都是Haskell旨在避免的事情之一。纯函数可以更灵活。

这是一种做你想做的事情的简单方法。

type Sample = ([Double], [Double])

newSample xs vs
  | isSorted xs && length xs == length vs = (indices, values)
  | otherwise                             = ([], [])

pairs = uncurry zip

doSampling :: (Double -> Double) -> [Double] -> Sample
doSampling f xs = newSample xs (map f xs)

mkPolynomial :: [Double] -> (Double -> Double)
mkPolynomial coefs x = go coefs
  where
    go  []    = 0
    go (c:cs) = c + x * go cs

mkSinus :: Double -> Double -> Double -> (Double -> Double)
mkSinus amp freq phase x = amp * sin (freq * x + phase)

p1 = mkPolynomial [1, 0, 0.5] -- 1 + 0.5x^2
s1 = mkSinus 2 0.5 3          -- 2 sin(0.5x + 3)

m1 = doSampling p1 [0, 0.5 .. 5]
m2 = doSampling s1 [0, 0.5 .. 5]

main :: IO ()
main = do
  print $ p1 2
  print $ s1 (pi/2)
  print $ pairs m1
  print $ pairs m2

答案 1 :(得分:3)

[扩大了我对请求的评论。]

我可能大致如下:

import Data.Functor

-- Use a datatype rather than a class. Yes, this makes it harder to
-- add new types of functions later, and in turn easier to define new
-- operations. ("expression problem")
data Function =
    Sinus { amplitude :: Double, frequency :: Double, phase :: Double }
  | Polynomial { coeffs :: [Double] }
  deriving (Show)

-- Interpreting a Function as an actual function.
value :: Function -> (Double -> Double)
value (Sinus amp freq ph) x = amp * sin (freq * x + ph)
value (Polynomial cs)     x = value' cs x

-- Rewrite value' to not require non-empty lists. This can also be
-- nicely written as a fold.
value' :: [Double] -> Double -> Double
value' []     _ = 0
value' (c:cs) x = c + x * value' cs x

data Samples = Samples { indexes :: [Double] , values :: [Double] }
  deriving (Show)

-- Use Maybe to detect error conditions, instead of strange values
-- such as voidSamples.
newSamples :: [Double] -> [Double] -> Maybe Samples
newSamples ind val 
  | length ind /= length val = Nothing
  | not $ isSorted ind       = Nothing
  | otherwise                = Just (Samples ind val)

doSampling :: (Double -> Double) -> [Double] -> Maybe Samples
doSampling f ind = newSamples ind (map f ind)

isSorted :: (Ord t) => [t] -> Bool
isSorted []  = True
isSorted [e] = True
isSorted (e1:e2:es)
  | e1 < e2   = isSorted (e2:es)
  | otherwise = False

-- This is just zip.
pairs :: Samples -> [(Double, Double)]
pairs (Samples idxs vals) = zip idxs vals

p1 = Polynomial [1, 0, 0.5]  -- p(x) =  1 + 0.5x^2 
s1 = Sinus 2 0.5 3 -- f(x) = 2 sin(0.5x + 3) 

m1 = doSampling (value p1) [0, 0.5 .. 5]
m2 = doSampling (value s1) [0, 0.5 .. 5]

-- The <$> maps over a Maybe.
main =  do
  putStrLn "Hello"
  print $ value p1 2
  print $ value s1 (pi/2)
  print $ pairs <$> m1
  print $ pairs <$> m2