Подтвердить что ты не робот

Тип системы в Haskell

У меня возникли проблемы с пониманием того, почему следующий код приводит к ошибке. Если второе поле Foo изменяется на тип Int, код запускается без ошибок.

Prelude> data Foo = Foo {foo_a :: Int, foo_b :: Float}
Prelude> let x = Foo 2 3.4
Prelude> unwords $ map (\fn -> (show . fn) x) [foo_a, foo_b]

<interactive>:4:46:
    Couldn't match type `Float' with `Int'
    Expected type: Foo -> Int
      Actual type: Foo -> Float
    In the expression: foo_b
    In the second argument of `map', namely `[foo_a, foo_b]'
    In the second argument of `($)', namely
      `map (\ fn -> (show . fn) x) [foo_a, foo_b]'

Почему show не может принимать аргументы разных типов? Следующие, конечно же, работают:

Prelude> show $ foo_a x
"2"
Prelude> show $ foo_b x
"3.4"

Кроме того, учитывая, что это не сработает, каков рекомендуемый способ применения show к различным полям типа данных?

Спасибо.

4b9b3361

Ответ 1

Проблема состоит в том, что списки в Haskell однородны (все элементы имеют один и тот же тип). И в [foo_a, foo_b] вы пытаетесь создать список с двумя разными типами: Foo -> Int и Foo -> Float.

Одним из решений является перемещение show внутри списка и создание списка Foo -> String:

Prelude> data Foo = Foo {foo_a :: Int, foo_b :: Float}
Prelude> let x = Foo 2 3.4
Prelude> unwords $ map (\fn -> fn x) [show . foo_a, show . foo_b]
"2 3.4"

((\fn -> fn x) может быть записано как ($ x))


Другая возможность - создать тип данных для унификации нескольких типов, которые вы хотите поместить в список, имитируя гетерогенную коллекцию . В вашем случае это может быть примерно так:

{-# LANGUAGE ExistentialQuantification #-}

data Showable b = forall a . Show a => MkShowable (b -> a)

pack :: Show a => (b -> a) -> Showable b
pack = MkShowable

unpack :: Showable b -> b -> String
unpack (MkShowable f) = show . f

Затем вы можете сделать:

*Main> let x = Foo 2 3.4
*Main> unwords $ map (\fn -> unpack fn x) [pack foo_a, pack foo_b]
"2 3.4"

[Обновление]

Я играл с Data.Dynamic, и он кажется более перспективным, чем тот, который я создал выше.

Начнем с:

{-# LANGUAGE DeriveDataTypeable #-}

import Control.Applicative
import Data.Dynamic
import Data.Maybe

data Foo = Foo {foo_a :: Int, foo_b :: Float}
  deriving Typeable

get_a :: Dynamic -> Maybe (Foo -> Int)
get_a = fromDynamic

get_b :: Dynamic -> Maybe (Foo -> Float)
get_b = fromDynamic

getAsString :: Dynamic -> (Foo -> String)
getAsString dyn = fromJust $  (show .) <$> get_a dyn
                          <|> (show .) <$> get_b dyn
                          <|> error "Type mismatch"

В этом случае мы можем сделать:

*Main> let x = Foo 2 3.4
*Main> unwords $ map (\fn -> getAsString fn x) [toDyn foo_a, toDyn foo_b]
"2 3.4"

Кажется, нам пришлось написать больше кода для достижения того же результата, но на самом деле это дает нам гораздо большую гибкость. Хотя экзистенциальный тип был сфокусирован только на показе полей как String, здесь мы не ограничиваемся этим. Что, если теперь мы хотим видеть все поля как Int (в случае Float мы хотим округлить значение)?

getAsInt :: Dynamic -> (Foo -> Int)
getAsInt dyn = fromJust $                get_a dyn
                       <|> (round .) <$> get_b dyn
                       <|> error "Type mismatch"

Теперь мы можем сделать:

*Main> let x = Foo 2 3.4
*Main> map (\fn -> getAsInt fn x) [toDyn foo_a, toDyn foo_b]
[2,3]

Ответ 2

Это ограничение системы типа Хиндли-Милнера. Только определения в let (и, эквивалентно, в where и на верхнем уровне) могут быть полиморфными. В частности, аргументы функций не могут быть полиморфными. fn должен иметь тип Foo -> Int или Foo -> Float - не существует типа Foo -> (Int or Float) или Foo -> Showable.

Если вам нужно, вы можете определить тип Showable, который известен как экзистенциальный тип, но вы должны дать некоторую помощь typechecker чтобы использовать его, и в большинстве кодексов идея не используется, потому что ее неудобство перевешивает ее полезность, и у нас вообще нет никаких проблем, выражая то, что мы хотим без нее.