2014-03-01 2 views
27

Ruby hat eine nette Funktion, die es ermöglicht, Zahlen in andere Dinge umzuwandeln, z. für Iteration oder 3.to_s für die Umwandlung in eine Zeichenfolge.Wie kann ich Einheiten in menschlicher Sprache als Postfixes in Haskell schreiben, wie `3 Sekunden`?

Die Leute sagen, Haskell ist gut für das Schreiben von natürlichen DSL s.

Ist es möglich, Einheiten als Postfixes zu schreiben, z. timeout = 3 seconds?

+1

Vielleicht sollten Sie einen Blick auf http://hackage.haskell.org/package/dimensional. Sie benutzen etwas wie "timeout = 3 * ~ seconds" ... aber Sie erhalten auch alle anderen si-Einheiten und Präfixe. – fho

Antwort

71

Ja.

Sie können dies mit dem folgenden einfachen Trick:

{-# LANGUAGE FlexibleInstances #-} 

instance Num (Integer -> Integer) where 
    fromInteger n = \scale -> n * scale -- return a function that takes 
             -- a number and returns a number 

Dann können Sie schreiben:

seconds, minutes, hours, days :: Integer 

seconds = 1000000 -- base unit, e.g. microseconds 
minutes = 60 seconds 
hours = 60 minutes 
days = 24 hours 

soon :: Integer 
soon = 2 hours + 4 seconds 

Wie funktionierts?

Oben haben wir ein Num Beispiel für Integer -> Integer gegeben, das heißt für eine -Funktion, die eine ganze Zahl nimmt und gibt eine ganze Zahl.

Jeder Typ, der Num implementiert und dessen Funktion fromInteger definiert ist, darf durch ein numerisches Literal, z. 3.

Dies bedeutet, dass wir schreiben können 3 :: Integer -> Integer - hier 3 ist eine Funktion, die eine ganze Zahl nimmt und eine ganze Zahl zurückgibt!

Daher können wir eine ganze Zahl darauf anwenden, zum Beispiel seconds; wir können 3 seconds schreiben und der Ausdruck wird vom Typ Integer sein.


Eine typsichere Version

In der Tat, wir haben sogar 3 (3 :: Integer) jetzt schreiben könnte - dies wahrscheinlich viel Sinn macht allerdings nicht machen. Wir können dies einschränken, indem sie mehr typsicher:

newtype TimeUnit = TimeUnit Integer 
    deriving (Eq, Show, Num) 

instance Num (TimeUnit -> TimeUnit) where 
    fromInteger n = \(TimeUnit scale) -> TimeUnit (n * scale) 

seconds, minutes, hours, days :: TimeUnit 

seconds = TimeUnit 1000000 
minutes = 60 seconds 
hours = 60 minutes 
days = 24 hours 

Jetzt können wir nur Dinge vom Typ Literale TimeUnit Nummer anzuwenden.

Sie könnten das für alle Arten von anderen Einheiten, wie Gewichte oder Entfernungen oder Menschen tun.

+18

... es ist gleichzeitig schön und abscheulich. Herzliche Glückwünsche.Upvoted. –

+2

Es ist schön, die Antwort zu sehen, die ich geben würde. :) – augustss

+3

Um dies zu seiner logischen Schlussfolgerung zu bringen, siehe das Paket [unittyped] (https://bitbucket.org/xnyhps/haskell-unittyped/). Es ist wirklich genial, automatisch Einheiten für Sie zu verwalten, aber es muss eine Reihe von Erweiterungen verwenden und die Fehlermeldungen sind * absurd * schlecht. –

0

Wenn Ihre Einheiten in Kleinbuchstaben sind, können Sie den oben angegebenen TimeUnit-Typ verwenden. Wenn Ihre Einheiten jedoch in Großbuchstaben beginnen, müssen Sie für jede Einheit einen neuen Typ oder Daten definieren und die Num-Instanz für sie definieren. Ein Beispiel dafür ist im Basic-Interpreter
dokumentiert hier: http://hackage.haskell.org/package/BASIC-0.1.5.0/docs/Language-BASIC.html

Verwandte Themen